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 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,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ okcdn/__init__.py
5
+ okcdn/client.py
6
+ okcdn/exceptions.py
7
+ okcdn.egg-info/PKG-INFO
8
+ okcdn.egg-info/SOURCES.txt
9
+ okcdn.egg-info/dependency_links.txt
10
+ okcdn.egg-info/requires.txt
11
+ okcdn.egg-info/top_level.txt
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
okcdn-4.0.0/setup.py ADDED
@@ -0,0 +1,11 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="okcdn",
5
+ version="4.0.0",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ "aiohttp>=3.8.0",
9
+ ],
10
+ python_requires=">=3.8",
11
+ )