okcdn 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- okcdn-4.0.0/PKG-INFO +96 -0
- okcdn-4.0.0/README.md +74 -0
- okcdn-4.0.0/okcdn/__init__.py +23 -0
- okcdn-4.0.0/okcdn/client.py +416 -0
- okcdn-4.0.0/okcdn/exceptions.py +26 -0
- okcdn-4.0.0/okcdn.egg-info/PKG-INFO +96 -0
- okcdn-4.0.0/okcdn.egg-info/SOURCES.txt +11 -0
- okcdn-4.0.0/okcdn.egg-info/dependency_links.txt +1 -0
- okcdn-4.0.0/okcdn.egg-info/requires.txt +1 -0
- okcdn-4.0.0/okcdn.egg-info/top_level.txt +1 -0
- okcdn-4.0.0/pyproject.toml +30 -0
- okcdn-4.0.0/setup.cfg +4 -0
- okcdn-4.0.0/setup.py +11 -0
okcdn-4.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: okcdn
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: High-performance Python client for OKCdn video & media streaming engine
|
|
5
|
+
Author: OKCdn Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-username/OKCdn
|
|
8
|
+
Project-URL: Documentation, https://github.com/your-username/OKCdn#readme
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Multimedia :: Video
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
|
|
23
|
+
# OkCdn Python Client SDK
|
|
24
|
+
|
|
25
|
+
Легковесный, быстрый и асинхронный клиент для взаимодействия с сервером **OKCdn**.
|
|
26
|
+
|
|
27
|
+
## ✨ Особенности
|
|
28
|
+
- **Zero-Disk RAM Streaming**: мгновенная перекачка MPEG-DASH (.mpd), HLS (.m3u8), Direct MP4 в OK.ru.
|
|
29
|
+
- **Мгновенное обновление ссылок**: актуализация протухших CDN-ссылок (`<0.5s`).
|
|
30
|
+
- **Пакетная параллельная загрузка**: отправка списков видео с контролируемым `concurrency`.
|
|
31
|
+
- **Автоматический Retry**: устойчивость к временным сетевым сбоям с экспоненциальной задержкой.
|
|
32
|
+
- **Поддержка мультиаккаунтов**: сессии кэшируются на сервере с 30-минутным умным авто-отключением сокетов.
|
|
33
|
+
|
|
34
|
+
## 📦 Установка
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install okcdn
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🚀 Быстрый старт
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import asyncio
|
|
44
|
+
from okcdn import OkCdn
|
|
45
|
+
|
|
46
|
+
async def main():
|
|
47
|
+
# Инициализация клиента (можно передать параметры или задать переменные окружения)
|
|
48
|
+
async with OkCdn(
|
|
49
|
+
server_url="https://your-okcdn-server.koyeb.app",
|
|
50
|
+
auth_token="YOUR_OK_AUTHCODE_HERE",
|
|
51
|
+
default_chat_id=123456789
|
|
52
|
+
) as cdn:
|
|
53
|
+
|
|
54
|
+
# 1. Отправка одиночного видео (HLS / DASH / MP4)
|
|
55
|
+
video = await cdn.send_video(
|
|
56
|
+
link="https://example.com/stream.m3u8",
|
|
57
|
+
caption="Мой видеострим",
|
|
58
|
+
title="video_01.mp4"
|
|
59
|
+
)
|
|
60
|
+
print("Прямой MP4:", video["mp4"])
|
|
61
|
+
print("HLS ссылка:", video["hls"])
|
|
62
|
+
print("Video ID:", video["video_id"])
|
|
63
|
+
|
|
64
|
+
# 2. Мгновенное обновление ссылок
|
|
65
|
+
fresh = await cdn.refresh_links(video_id=video["video_id"])
|
|
66
|
+
print("Обновленная ссылка:", fresh["mp4"])
|
|
67
|
+
print("Истекает через:", fresh["expires_in_sec"], "сек")
|
|
68
|
+
|
|
69
|
+
# 3. Загрузка локального файла
|
|
70
|
+
uploaded = await cdn.upload_file(
|
|
71
|
+
file_path="local_video.mp4",
|
|
72
|
+
caption="Прямая загрузка"
|
|
73
|
+
)
|
|
74
|
+
print("Загружено:", uploaded["direct"])
|
|
75
|
+
|
|
76
|
+
# 4. Пакетная параллельная отправка
|
|
77
|
+
batch = await cdn.send_videos(
|
|
78
|
+
links=[
|
|
79
|
+
"https://site.com/video1.mp4",
|
|
80
|
+
"https://site.com/video2.mp4"
|
|
81
|
+
],
|
|
82
|
+
concurrency=2
|
|
83
|
+
)
|
|
84
|
+
for url, data in batch.items():
|
|
85
|
+
print(f"{url} -> {data.get('mp4')}")
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## ⚙️ Переменные окружения
|
|
92
|
+
|
|
93
|
+
Вы можете задать переменные окружения, чтобы не указывать их в коде:
|
|
94
|
+
- `OKCDN_SERVER_URL` — адрес запущенного сервера (по умолчанию `http://localhost:8000`)
|
|
95
|
+
- `ACCOUNT_TOKEN` или `OKCDN_AUTH_TOKEN` — токен авторизации OK.ru (`AUTHCODE`)
|
|
96
|
+
- `CHAT_ID` или `OKCDN_CHAT_ID` — ID чата по умолчанию
|
okcdn-4.0.0/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# OkCdn Python Client SDK
|
|
2
|
+
|
|
3
|
+
Легковесный, быстрый и асинхронный клиент для взаимодействия с сервером **OKCdn**.
|
|
4
|
+
|
|
5
|
+
## ✨ Особенности
|
|
6
|
+
- **Zero-Disk RAM Streaming**: мгновенная перекачка MPEG-DASH (.mpd), HLS (.m3u8), Direct MP4 в OK.ru.
|
|
7
|
+
- **Мгновенное обновление ссылок**: актуализация протухших CDN-ссылок (`<0.5s`).
|
|
8
|
+
- **Пакетная параллельная загрузка**: отправка списков видео с контролируемым `concurrency`.
|
|
9
|
+
- **Автоматический Retry**: устойчивость к временным сетевым сбоям с экспоненциальной задержкой.
|
|
10
|
+
- **Поддержка мультиаккаунтов**: сессии кэшируются на сервере с 30-минутным умным авто-отключением сокетов.
|
|
11
|
+
|
|
12
|
+
## 📦 Установка
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install okcdn
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 🚀 Быстрый старт
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
from okcdn import OkCdn
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
# Инициализация клиента (можно передать параметры или задать переменные окружения)
|
|
26
|
+
async with OkCdn(
|
|
27
|
+
server_url="https://your-okcdn-server.koyeb.app",
|
|
28
|
+
auth_token="YOUR_OK_AUTHCODE_HERE",
|
|
29
|
+
default_chat_id=123456789
|
|
30
|
+
) as cdn:
|
|
31
|
+
|
|
32
|
+
# 1. Отправка одиночного видео (HLS / DASH / MP4)
|
|
33
|
+
video = await cdn.send_video(
|
|
34
|
+
link="https://example.com/stream.m3u8",
|
|
35
|
+
caption="Мой видеострим",
|
|
36
|
+
title="video_01.mp4"
|
|
37
|
+
)
|
|
38
|
+
print("Прямой MP4:", video["mp4"])
|
|
39
|
+
print("HLS ссылка:", video["hls"])
|
|
40
|
+
print("Video ID:", video["video_id"])
|
|
41
|
+
|
|
42
|
+
# 2. Мгновенное обновление ссылок
|
|
43
|
+
fresh = await cdn.refresh_links(video_id=video["video_id"])
|
|
44
|
+
print("Обновленная ссылка:", fresh["mp4"])
|
|
45
|
+
print("Истекает через:", fresh["expires_in_sec"], "сек")
|
|
46
|
+
|
|
47
|
+
# 3. Загрузка локального файла
|
|
48
|
+
uploaded = await cdn.upload_file(
|
|
49
|
+
file_path="local_video.mp4",
|
|
50
|
+
caption="Прямая загрузка"
|
|
51
|
+
)
|
|
52
|
+
print("Загружено:", uploaded["direct"])
|
|
53
|
+
|
|
54
|
+
# 4. Пакетная параллельная отправка
|
|
55
|
+
batch = await cdn.send_videos(
|
|
56
|
+
links=[
|
|
57
|
+
"https://site.com/video1.mp4",
|
|
58
|
+
"https://site.com/video2.mp4"
|
|
59
|
+
],
|
|
60
|
+
concurrency=2
|
|
61
|
+
)
|
|
62
|
+
for url, data in batch.items():
|
|
63
|
+
print(f"{url} -> {data.get('mp4')}")
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
asyncio.run(main())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## ⚙️ Переменные окружения
|
|
70
|
+
|
|
71
|
+
Вы можете задать переменные окружения, чтобы не указывать их в коде:
|
|
72
|
+
- `OKCDN_SERVER_URL` — адрес запущенного сервера (по умолчанию `http://localhost:8000`)
|
|
73
|
+
- `ACCOUNT_TOKEN` или `OKCDN_AUTH_TOKEN` — токен авторизации OK.ru (`AUTHCODE`)
|
|
74
|
+
- `CHAT_ID` или `OKCDN_CHAT_ID` — ID чата по умолчанию
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OkCdn Client - Высокопроизводительный асинхронный клиент для работы с сервером OKCdn.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .client import OkCdn
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
OkCdnError,
|
|
8
|
+
OkCdnAuthError,
|
|
9
|
+
OkCdnServerError,
|
|
10
|
+
OkCdnBadRequestError,
|
|
11
|
+
OkCdnTimeoutError
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"OkCdn",
|
|
16
|
+
"OkCdnError",
|
|
17
|
+
"OkCdnAuthError",
|
|
18
|
+
"OkCdnServerError",
|
|
19
|
+
"OkCdnBadRequestError",
|
|
20
|
+
"OkCdnTimeoutError",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
__version__ = "4.0.0"
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
import aiohttp
|
|
4
|
+
from typing import Optional, List, Dict, Any, Union
|
|
5
|
+
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
OkCdnError,
|
|
8
|
+
OkCdnAuthError,
|
|
9
|
+
OkCdnServerError,
|
|
10
|
+
OkCdnBadRequestError,
|
|
11
|
+
OkCdnTimeoutError
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OkCdn:
|
|
16
|
+
"""
|
|
17
|
+
Легковесный асинхронный клиент для работы с сервером OKCdn.
|
|
18
|
+
|
|
19
|
+
Поддерживает:
|
|
20
|
+
- Загрузку видео и потоков (DASH, HLS, MP4) в OK CDN
|
|
21
|
+
- Пакетную параллельную загрузку видео
|
|
22
|
+
- Мгновенное обновление прямых ссылок
|
|
23
|
+
- Загрузку локальных файлов (direct/multipart)
|
|
24
|
+
- Отправку фото и аудио
|
|
25
|
+
- Автоматические повторные попытки (Retry) с экспоненциальной задержкой
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
server_url: str,
|
|
31
|
+
auth_token: str,
|
|
32
|
+
default_chat_id: Optional[Union[int, str]] = None,
|
|
33
|
+
timeout: float = 180.0,
|
|
34
|
+
max_retries: int = 3,
|
|
35
|
+
headers: Optional[Dict[str, str]] = None
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Инициализация клиента OkCdn.
|
|
39
|
+
|
|
40
|
+
:param server_url: URL запущенного сервера OKCdn (например, 'https://api.yourdomain.com' или 'http://localhost:8000').
|
|
41
|
+
:param auth_token: Токен авторизации OK.ru (AUTHCODE). Обязателен.
|
|
42
|
+
:param default_chat_id: ID чата по умолчанию (опционально, если передается в каждый метод).
|
|
43
|
+
:param timeout: Таймаут запросов в секундах (по умолчанию 180с для больших стримов).
|
|
44
|
+
:param max_retries: Количество повторных попыток при сетевых сбоях (по умолчанию 3).
|
|
45
|
+
:param headers: Дополнительные HTTP заголовки.
|
|
46
|
+
"""
|
|
47
|
+
if not server_url or not str(server_url).strip():
|
|
48
|
+
raise ValueError("server_url обязателен для инициализации OkCdn")
|
|
49
|
+
if not auth_token or not str(auth_token).strip():
|
|
50
|
+
raise ValueError("auth_token (AUTHCODE от OK.ru) обязателен для инициализации OkCdn")
|
|
51
|
+
|
|
52
|
+
self.server_url = str(server_url).strip().rstrip("/")
|
|
53
|
+
self.auth_token = str(auth_token).strip()
|
|
54
|
+
self.default_chat_id = int(default_chat_id) if default_chat_id is not None else None
|
|
55
|
+
|
|
56
|
+
self.timeout = timeout
|
|
57
|
+
self.max_retries = max(1, max_retries)
|
|
58
|
+
self.custom_headers = headers or {}
|
|
59
|
+
|
|
60
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
61
|
+
self._session_lock = asyncio.Lock()
|
|
62
|
+
|
|
63
|
+
async def __aenter__(self):
|
|
64
|
+
await self._ensure_session()
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
68
|
+
await self.close()
|
|
69
|
+
|
|
70
|
+
async def _ensure_session(self) -> aiohttp.ClientSession:
|
|
71
|
+
if self._session and not self._session.closed:
|
|
72
|
+
return self._session
|
|
73
|
+
|
|
74
|
+
async with self._session_lock:
|
|
75
|
+
if self._session and not self._session.closed:
|
|
76
|
+
return self._session
|
|
77
|
+
|
|
78
|
+
headers = {
|
|
79
|
+
"User-Agent": "OkCdn-Python-Client/4.0.0",
|
|
80
|
+
"Accept": "application/json",
|
|
81
|
+
"X-OK-Token": self.auth_token,
|
|
82
|
+
**self.custom_headers
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
connector = aiohttp.TCPConnector(ssl=False)
|
|
86
|
+
self._session = aiohttp.ClientSession(
|
|
87
|
+
connector=connector,
|
|
88
|
+
headers=headers,
|
|
89
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
|
90
|
+
)
|
|
91
|
+
return self._session
|
|
92
|
+
|
|
93
|
+
async def close(self):
|
|
94
|
+
"""Закрывает клиентскую HTTP-сессию"""
|
|
95
|
+
if self._session and not self._session.closed:
|
|
96
|
+
await self._session.close()
|
|
97
|
+
self._session = None
|
|
98
|
+
|
|
99
|
+
def _resolve_chat_id(self, chat_id: Optional[Union[int, str]]) -> int:
|
|
100
|
+
target = chat_id if chat_id is not None else self.default_chat_id
|
|
101
|
+
if target is None:
|
|
102
|
+
raise ValueError("chat_id не указан. Передайте chat_id в метод или укажите default_chat_id при создании OkCdn")
|
|
103
|
+
try:
|
|
104
|
+
return int(target)
|
|
105
|
+
except ValueError:
|
|
106
|
+
raise ValueError(f"Некорректный chat_id: {target}. Должно быть число.")
|
|
107
|
+
|
|
108
|
+
async def _request(
|
|
109
|
+
self,
|
|
110
|
+
method: str,
|
|
111
|
+
path: str,
|
|
112
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
113
|
+
params: Optional[Dict[str, Any]] = None,
|
|
114
|
+
data: Optional[Any] = None,
|
|
115
|
+
custom_timeout: Optional[float] = None
|
|
116
|
+
) -> Dict[str, Any]:
|
|
117
|
+
session = await self._ensure_session()
|
|
118
|
+
url = f"{self.server_url}{path}"
|
|
119
|
+
req_timeout = aiohttp.ClientTimeout(total=custom_timeout or self.timeout)
|
|
120
|
+
|
|
121
|
+
last_error = None
|
|
122
|
+
for attempt in range(self.max_retries):
|
|
123
|
+
try:
|
|
124
|
+
async with session.request(
|
|
125
|
+
method=method,
|
|
126
|
+
url=url,
|
|
127
|
+
json=json_data,
|
|
128
|
+
params=params,
|
|
129
|
+
data=data,
|
|
130
|
+
timeout=req_timeout
|
|
131
|
+
) as resp:
|
|
132
|
+
status_code = resp.status
|
|
133
|
+
try:
|
|
134
|
+
res_json = await resp.json()
|
|
135
|
+
except Exception:
|
|
136
|
+
text = await resp.text()
|
|
137
|
+
res_json = {"error": text}
|
|
138
|
+
|
|
139
|
+
if status_code == 200:
|
|
140
|
+
return res_json
|
|
141
|
+
elif status_code == 401:
|
|
142
|
+
raise OkCdnAuthError(
|
|
143
|
+
res_json.get("detail") or res_json.get("error") or "Ошибка авторизации OK.ru",
|
|
144
|
+
status_code=status_code,
|
|
145
|
+
response=res_json
|
|
146
|
+
)
|
|
147
|
+
elif 400 <= status_code < 500:
|
|
148
|
+
raise OkCdnBadRequestError(
|
|
149
|
+
res_json.get("detail") or res_json.get("error") or f"Некорректный запрос (HTTP {status_code})",
|
|
150
|
+
status_code=status_code,
|
|
151
|
+
response=res_json
|
|
152
|
+
)
|
|
153
|
+
elif status_code >= 500:
|
|
154
|
+
last_error = OkCdnServerError(
|
|
155
|
+
res_json.get("detail") or res_json.get("error") or f"Ошибка сервера (HTTP {status_code})",
|
|
156
|
+
status_code=status_code,
|
|
157
|
+
response=res_json
|
|
158
|
+
)
|
|
159
|
+
except (asyncio.TimeoutError, aiohttp.ServerTimeoutError):
|
|
160
|
+
last_error = OkCdnTimeoutError(f"Превышен таймаут {self.timeout}с при обращении к {url}")
|
|
161
|
+
except (aiohttp.ClientError, aiohttp.ClientConnectionError) as e:
|
|
162
|
+
last_error = OkCdnError(f"Сетевая ошибка подключения к {url}: {e}")
|
|
163
|
+
|
|
164
|
+
if attempt < self.max_retries - 1:
|
|
165
|
+
await asyncio.sleep(1.0 * (2 ** attempt))
|
|
166
|
+
|
|
167
|
+
if last_error:
|
|
168
|
+
raise last_error
|
|
169
|
+
raise OkCdnError("Неизвестная ошибка запроса")
|
|
170
|
+
|
|
171
|
+
# -------------------------------------------------------------------------
|
|
172
|
+
# 1. ОТПРАВКА ОДИНОЧНОГО ВИДЕО (send_video)
|
|
173
|
+
# -------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
async def send_video(
|
|
176
|
+
self,
|
|
177
|
+
link: str,
|
|
178
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
179
|
+
caption: Optional[str] = None,
|
|
180
|
+
title: Optional[str] = None
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
"""
|
|
183
|
+
Загружает видео/поток (DASH .mpd, HLS .m3u8, direct MP4) в OK.ru через сервер
|
|
184
|
+
и возвращает прямые CDN-ссылки, метаданные и метрики.
|
|
185
|
+
|
|
186
|
+
:param link: URL видеофайла или потока (.mpd, .m3u8, .mp4).
|
|
187
|
+
:param chat_id: ID чата OK.ru (если не указан default_chat_id).
|
|
188
|
+
:param caption: Подпись к видео.
|
|
189
|
+
:param title: Имя/название файла.
|
|
190
|
+
:return: Словарь со всеми CDN ссылками и метаданными.
|
|
191
|
+
"""
|
|
192
|
+
cid = self._resolve_chat_id(chat_id)
|
|
193
|
+
payload = {
|
|
194
|
+
"link": link,
|
|
195
|
+
"chat_id": cid,
|
|
196
|
+
"caption": caption,
|
|
197
|
+
"title": title
|
|
198
|
+
}
|
|
199
|
+
return await self._request("POST", "/api/v1/video/send", json_data=payload)
|
|
200
|
+
|
|
201
|
+
# -------------------------------------------------------------------------
|
|
202
|
+
# 2. ПАКЕТНАЯ ОТПРАВКА СПИСКА ВИДЕО (send_videos) -> { url: video_data }
|
|
203
|
+
# -------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
async def send_videos(
|
|
206
|
+
self,
|
|
207
|
+
links: List[str],
|
|
208
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
209
|
+
captions: Optional[List[str]] = None,
|
|
210
|
+
titles: Optional[List[str]] = None,
|
|
211
|
+
concurrency: int = 3
|
|
212
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
213
|
+
"""
|
|
214
|
+
Параллельно отправляет массив видео в OK.ru и возвращает словарь { url: video_data }.
|
|
215
|
+
|
|
216
|
+
:param links: Список URL видеофайлов/потоков.
|
|
217
|
+
:param chat_id: ID чата OK.ru.
|
|
218
|
+
:param captions: Список подписей.
|
|
219
|
+
:param titles: Список названий.
|
|
220
|
+
:param concurrency: Количество параллельных потоков перекачки на сервере.
|
|
221
|
+
:return: Словарь вида { "https://...": { "success": True, "mp4": "...", ... } }
|
|
222
|
+
"""
|
|
223
|
+
cid = self._resolve_chat_id(chat_id)
|
|
224
|
+
payload = {
|
|
225
|
+
"links": links,
|
|
226
|
+
"chat_id": cid,
|
|
227
|
+
"captions": captions,
|
|
228
|
+
"titles": titles,
|
|
229
|
+
"concurrency": concurrency
|
|
230
|
+
}
|
|
231
|
+
return await self._request("POST", "/api/v1/video/send-batch", json_data=payload)
|
|
232
|
+
|
|
233
|
+
# -------------------------------------------------------------------------
|
|
234
|
+
# 3. МГНОВЕННОЕ ОБНОВЛЕНИЕ ПРЯМЫХ ССЫЛОК (refresh_links)
|
|
235
|
+
# -------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
async def refresh_links(
|
|
238
|
+
self,
|
|
239
|
+
video_id: Union[int, str],
|
|
240
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
241
|
+
message_id: Optional[Union[int, str]] = None
|
|
242
|
+
) -> Dict[str, Any]:
|
|
243
|
+
"""
|
|
244
|
+
Мгновенно (<0.5с) синхронно обновляет и возвращает свежие прямые CDN-ссылки (MP4, HLS, DASH, Poster).
|
|
245
|
+
|
|
246
|
+
:param video_id: ID видео в OK.ru.
|
|
247
|
+
:param chat_id: ID чата OK.ru.
|
|
248
|
+
:param message_id: ID сообщения OK.ru (опционально).
|
|
249
|
+
:return: Словарь со свежими ссылками и датой истечения expires_at.
|
|
250
|
+
"""
|
|
251
|
+
cid = self._resolve_chat_id(chat_id)
|
|
252
|
+
payload = {
|
|
253
|
+
"video_id": video_id,
|
|
254
|
+
"chat_id": cid,
|
|
255
|
+
"message_id": message_id
|
|
256
|
+
}
|
|
257
|
+
return await self._request("POST", "/api/v1/video/refresh", json_data=payload)
|
|
258
|
+
|
|
259
|
+
# -------------------------------------------------------------------------
|
|
260
|
+
# 4. DIRECT UPLOAD (ПРЯМАЯ ЗАГРУЗКА В CDN OK.RU)
|
|
261
|
+
# -------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
async def prepare_direct_upload(
|
|
264
|
+
self,
|
|
265
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
266
|
+
media_type: str = "VIDEO"
|
|
267
|
+
) -> Dict[str, Any]:
|
|
268
|
+
"""
|
|
269
|
+
Запрашивает у сервера прямую ссылку upload_url, токен и video_id от OK CDN.
|
|
270
|
+
"""
|
|
271
|
+
cid = self._resolve_chat_id(chat_id)
|
|
272
|
+
payload = {
|
|
273
|
+
"chat_id": cid,
|
|
274
|
+
"media_type": media_type
|
|
275
|
+
}
|
|
276
|
+
return await self._request("POST", "/api/v1/upload/direct-prepare", json_data=payload)
|
|
277
|
+
|
|
278
|
+
async def finalize_direct_upload(
|
|
279
|
+
self,
|
|
280
|
+
token: str,
|
|
281
|
+
video_id: Optional[Union[int, str]] = None,
|
|
282
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
283
|
+
caption: Optional[str] = None,
|
|
284
|
+
title: Optional[str] = None,
|
|
285
|
+
media_type: str = "VIDEO"
|
|
286
|
+
) -> Dict[str, Any]:
|
|
287
|
+
"""
|
|
288
|
+
Публикует загруженный токен в чат OK.ru и извлекает прямые ссылки.
|
|
289
|
+
"""
|
|
290
|
+
cid = self._resolve_chat_id(chat_id)
|
|
291
|
+
payload = {
|
|
292
|
+
"token": token,
|
|
293
|
+
"video_id": video_id,
|
|
294
|
+
"chat_id": cid,
|
|
295
|
+
"caption": caption,
|
|
296
|
+
"title": title,
|
|
297
|
+
"media_type": media_type
|
|
298
|
+
}
|
|
299
|
+
return await self._request("POST", "/api/v1/upload/direct-finalize", json_data=payload)
|
|
300
|
+
|
|
301
|
+
# -------------------------------------------------------------------------
|
|
302
|
+
# 5. УНИВЕРСАЛЬНАЯ ЗАГРУЗКА ЛОКАЛЬНОГО ФАЙЛА (upload_file)
|
|
303
|
+
# -------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
async def upload_file(
|
|
306
|
+
self,
|
|
307
|
+
file_path: str,
|
|
308
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
309
|
+
caption: Optional[str] = None,
|
|
310
|
+
title: Optional[str] = None,
|
|
311
|
+
media_type: str = "VIDEO"
|
|
312
|
+
) -> Dict[str, Any]:
|
|
313
|
+
"""
|
|
314
|
+
Универсальный метод загрузки локального файла с диска:
|
|
315
|
+
1. Получает direct-upload токен и URL от сервера.
|
|
316
|
+
2. Напрямую выгружает байты в OK CDN без нагрузки на сервер.
|
|
317
|
+
3. Финализирует публикацию в чате и возвращает готовые CDN-ссылки.
|
|
318
|
+
В случае сбоя direct upload делает автоматический fallback на серверный multipart endpoint.
|
|
319
|
+
"""
|
|
320
|
+
if not os.path.exists(file_path):
|
|
321
|
+
raise FileNotFoundError(f"Файл не найден: {file_path}")
|
|
322
|
+
|
|
323
|
+
cid = self._resolve_chat_id(chat_id)
|
|
324
|
+
fname = title or os.path.basename(file_path)
|
|
325
|
+
|
|
326
|
+
# 1. Попытка Direct Upload
|
|
327
|
+
try:
|
|
328
|
+
prep = await self.prepare_direct_upload(chat_id=cid, media_type=media_type)
|
|
329
|
+
if prep.get("success") and prep.get("upload_url"):
|
|
330
|
+
upload_url = prep["upload_url"]
|
|
331
|
+
token = prep.get("token")
|
|
332
|
+
vid = prep.get("video_id")
|
|
333
|
+
|
|
334
|
+
session = await self._ensure_session()
|
|
335
|
+
data = aiohttp.FormData()
|
|
336
|
+
with open(file_path, "rb") as f:
|
|
337
|
+
file_bytes = f.read()
|
|
338
|
+
data.add_field("file", file_bytes, filename=fname, content_type="video/mp4" if media_type == "VIDEO" else "image/jpeg")
|
|
339
|
+
if token:
|
|
340
|
+
data.add_field("token", token)
|
|
341
|
+
|
|
342
|
+
async with session.post(upload_url, data=data, timeout=aiohttp.ClientTimeout(total=300)) as resp:
|
|
343
|
+
if resp.status == 200:
|
|
344
|
+
return await self.finalize_direct_upload(
|
|
345
|
+
token=token,
|
|
346
|
+
video_id=vid,
|
|
347
|
+
chat_id=cid,
|
|
348
|
+
caption=caption,
|
|
349
|
+
title=fname,
|
|
350
|
+
media_type=media_type
|
|
351
|
+
)
|
|
352
|
+
except Exception:
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
# 2. Fallback: Загрузка через Server Multipart Endpoint
|
|
356
|
+
session = await self._ensure_session()
|
|
357
|
+
data = aiohttp.FormData()
|
|
358
|
+
with open(file_path, "rb") as f:
|
|
359
|
+
file_bytes = f.read()
|
|
360
|
+
data.add_field("file", file_bytes, filename=fname, content_type="video/mp4")
|
|
361
|
+
data.add_field("chat_id", str(cid))
|
|
362
|
+
if caption:
|
|
363
|
+
data.add_field("caption", caption)
|
|
364
|
+
if fname:
|
|
365
|
+
data.add_field("title", fname)
|
|
366
|
+
data.add_field("media_type", media_type)
|
|
367
|
+
|
|
368
|
+
return await self._request("POST", "/api/v1/upload/file", data=data)
|
|
369
|
+
|
|
370
|
+
# -------------------------------------------------------------------------
|
|
371
|
+
# 6. ОТПРАВКА ФОТО И АУДИО (send_photo, send_audio)
|
|
372
|
+
# -------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
async def send_photo(
|
|
375
|
+
self,
|
|
376
|
+
link: str,
|
|
377
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
378
|
+
caption: Optional[str] = None
|
|
379
|
+
) -> Dict[str, Any]:
|
|
380
|
+
"""Отправляет фото по ссылке в чат OK.ru"""
|
|
381
|
+
cid = self._resolve_chat_id(chat_id)
|
|
382
|
+
payload = {
|
|
383
|
+
"link": link,
|
|
384
|
+
"chat_id": cid,
|
|
385
|
+
"caption": caption
|
|
386
|
+
}
|
|
387
|
+
return await self._request("POST", "/api/v1/media/photo/send", json_data=payload)
|
|
388
|
+
|
|
389
|
+
async def send_audio(
|
|
390
|
+
self,
|
|
391
|
+
link: str,
|
|
392
|
+
chat_id: Optional[Union[int, str]] = None,
|
|
393
|
+
caption: Optional[str] = None,
|
|
394
|
+
title: Optional[str] = None
|
|
395
|
+
) -> Dict[str, Any]:
|
|
396
|
+
"""Отправляет аудиофайл с автоматической переупаковкой в MP4 Zero-Disk"""
|
|
397
|
+
cid = self._resolve_chat_id(chat_id)
|
|
398
|
+
payload = {
|
|
399
|
+
"link": link,
|
|
400
|
+
"chat_id": cid,
|
|
401
|
+
"caption": caption,
|
|
402
|
+
"title": title
|
|
403
|
+
}
|
|
404
|
+
return await self._request("POST", "/api/v1/media/audio/send", json_data=payload)
|
|
405
|
+
|
|
406
|
+
# -------------------------------------------------------------------------
|
|
407
|
+
# 7. СТАТИСТИКА И HEALTHCHECK (get_stats, health)
|
|
408
|
+
# -------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
411
|
+
"""Получает статистику аккаунта и статус пула сессий сервера"""
|
|
412
|
+
return await self._request("GET", "/api/v1/stats")
|
|
413
|
+
|
|
414
|
+
async def health(self) -> Dict[str, Any]:
|
|
415
|
+
"""Проверяет доступность сервера"""
|
|
416
|
+
return await self._request("GET", "/health")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class OkCdnError(Exception):
|
|
2
|
+
"""Базовое исключение библиотеки OkCdn"""
|
|
3
|
+
def __init__(self, message: str, status_code: int = 0, response: dict = None):
|
|
4
|
+
super().__init__(message)
|
|
5
|
+
self.status_code = status_code
|
|
6
|
+
self.response = response or {}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OkCdnAuthError(OkCdnError):
|
|
10
|
+
"""Ошибка авторизации токена OK.ru"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OkCdnServerError(OkCdnError):
|
|
15
|
+
"""Ошибка сервера OKCdn (5xx)"""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OkCdnBadRequestError(OkCdnError):
|
|
20
|
+
"""Ошибка параметров запроса (4xx)"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OkCdnTimeoutError(OkCdnError):
|
|
25
|
+
"""Превышено время ожидания ответа от сервера"""
|
|
26
|
+
pass
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: okcdn
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: High-performance Python client for OKCdn video & media streaming engine
|
|
5
|
+
Author: OKCdn Team
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-username/OKCdn
|
|
8
|
+
Project-URL: Documentation, https://github.com/your-username/OKCdn#readme
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Multimedia :: Video
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
|
|
23
|
+
# OkCdn Python Client SDK
|
|
24
|
+
|
|
25
|
+
Легковесный, быстрый и асинхронный клиент для взаимодействия с сервером **OKCdn**.
|
|
26
|
+
|
|
27
|
+
## ✨ Особенности
|
|
28
|
+
- **Zero-Disk RAM Streaming**: мгновенная перекачка MPEG-DASH (.mpd), HLS (.m3u8), Direct MP4 в OK.ru.
|
|
29
|
+
- **Мгновенное обновление ссылок**: актуализация протухших CDN-ссылок (`<0.5s`).
|
|
30
|
+
- **Пакетная параллельная загрузка**: отправка списков видео с контролируемым `concurrency`.
|
|
31
|
+
- **Автоматический Retry**: устойчивость к временным сетевым сбоям с экспоненциальной задержкой.
|
|
32
|
+
- **Поддержка мультиаккаунтов**: сессии кэшируются на сервере с 30-минутным умным авто-отключением сокетов.
|
|
33
|
+
|
|
34
|
+
## 📦 Установка
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install okcdn
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🚀 Быстрый старт
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import asyncio
|
|
44
|
+
from okcdn import OkCdn
|
|
45
|
+
|
|
46
|
+
async def main():
|
|
47
|
+
# Инициализация клиента (можно передать параметры или задать переменные окружения)
|
|
48
|
+
async with OkCdn(
|
|
49
|
+
server_url="https://your-okcdn-server.koyeb.app",
|
|
50
|
+
auth_token="YOUR_OK_AUTHCODE_HERE",
|
|
51
|
+
default_chat_id=123456789
|
|
52
|
+
) as cdn:
|
|
53
|
+
|
|
54
|
+
# 1. Отправка одиночного видео (HLS / DASH / MP4)
|
|
55
|
+
video = await cdn.send_video(
|
|
56
|
+
link="https://example.com/stream.m3u8",
|
|
57
|
+
caption="Мой видеострим",
|
|
58
|
+
title="video_01.mp4"
|
|
59
|
+
)
|
|
60
|
+
print("Прямой MP4:", video["mp4"])
|
|
61
|
+
print("HLS ссылка:", video["hls"])
|
|
62
|
+
print("Video ID:", video["video_id"])
|
|
63
|
+
|
|
64
|
+
# 2. Мгновенное обновление ссылок
|
|
65
|
+
fresh = await cdn.refresh_links(video_id=video["video_id"])
|
|
66
|
+
print("Обновленная ссылка:", fresh["mp4"])
|
|
67
|
+
print("Истекает через:", fresh["expires_in_sec"], "сек")
|
|
68
|
+
|
|
69
|
+
# 3. Загрузка локального файла
|
|
70
|
+
uploaded = await cdn.upload_file(
|
|
71
|
+
file_path="local_video.mp4",
|
|
72
|
+
caption="Прямая загрузка"
|
|
73
|
+
)
|
|
74
|
+
print("Загружено:", uploaded["direct"])
|
|
75
|
+
|
|
76
|
+
# 4. Пакетная параллельная отправка
|
|
77
|
+
batch = await cdn.send_videos(
|
|
78
|
+
links=[
|
|
79
|
+
"https://site.com/video1.mp4",
|
|
80
|
+
"https://site.com/video2.mp4"
|
|
81
|
+
],
|
|
82
|
+
concurrency=2
|
|
83
|
+
)
|
|
84
|
+
for url, data in batch.items():
|
|
85
|
+
print(f"{url} -> {data.get('mp4')}")
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## ⚙️ Переменные окружения
|
|
92
|
+
|
|
93
|
+
Вы можете задать переменные окружения, чтобы не указывать их в коде:
|
|
94
|
+
- `OKCDN_SERVER_URL` — адрес запущенного сервера (по умолчанию `http://localhost:8000`)
|
|
95
|
+
- `ACCOUNT_TOKEN` или `OKCDN_AUTH_TOKEN` — токен авторизации OK.ru (`AUTHCODE`)
|
|
96
|
+
- `CHAT_ID` или `OKCDN_CHAT_ID` — ID чата по умолчанию
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp>=3.8.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
okcdn
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "okcdn"
|
|
7
|
+
version = "4.0.0"
|
|
8
|
+
description = "High-performance Python client for OKCdn video & media streaming engine"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "OKCdn Team" }]
|
|
11
|
+
license = "MIT"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.8",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Topic :: Multimedia :: Video",
|
|
22
|
+
"Topic :: Internet :: WWW/HTTP"
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"aiohttp>=3.8.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/your-username/OKCdn"
|
|
30
|
+
Documentation = "https://github.com/your-username/OKCdn#readme"
|
okcdn-4.0.0/setup.cfg
ADDED