Обновить документацию: уточнить формат скачиваемых видео, добавить примеры использования и требования к session file
CI / Quality Checks (push) Successful in 13s
CI / Test Suite (push) Successful in 10s
CI / Publish to Gitea Packages (push) Successful in 11s

This commit is contained in:
ВяткинАртём
2026-05-27 17:59:21 +03:00
parent f0e635ebe2
commit 0a3cf97200
+211 -43
View File
@@ -1,11 +1,33 @@
# yt-shorts-downloader # yt-shorts-downloader
Библиотека и CLI для скачивания YouTube Shorts через Netscape cookie session. Библиотека и CLI для скачивания YouTube Shorts в формате MP4 через Netscape cookie session file.
Целевой контракт публичного API: Основной контракт проекта:
- вход: ссылка YouTube и путь к session file - вход: ссылка YouTube и путь к cookie-файлу
- выход: бинарный MP4 в памяти для интеграции в API - выход для API: MP4 в памяти
- выход для CLI: путь к сохранённому MP4-файлу
## Возможности
- типизированный Python API
- CLI для сохранения файла на диск
- предварительная валидация YouTube URL
- предварительная валидация session file
- интеграция с yt-dlp
- strict mypy и локальные stubs для нетипизированных зависимостей
- автоматическая публикация пакета в Gitea Packages после успешного CI
## Требования
- Python 3.12+
- валидный Netscape cookie file из авторизованной YouTube-сессии
- один поддерживаемый JavaScript runtime:
- Deno
- Node.js 22+
- опционально, но желательно: ffmpeg
Если `ffmpeg` отсутствует, проект использует более простой MP4 format selector.
## Установка ## Установка
@@ -21,93 +43,239 @@ uv pip install .
uv sync --group dev uv sync --group dev
``` ```
## Использование библиотеки ## Быстрый старт
### Python API
```python ```python
from yt_shorts_downloader import download from yt_shorts_downloader import download
video = download( video = download(
"https://www.youtube.com/shorts/VIDEO_ID", 'https://www.youtube.com/shorts/VIDEO_ID',
"cookies.txt", 'cookies.txt',
) )
assert video.media_type == "video/mp4" assert video.media_type == 'video/mp4'
assert video.filename.endswith('.mp4')
binary_mp4 = video.content binary_mp4 = video.content
filename = video.filename
``` ```
Если нужен файл на диске, используйте вспомогательную функцию: Если нужен файл на диске:
```python ```python
from yt_shorts_downloader import download_to_path from yt_shorts_downloader import download_to_path
file_path = download_to_path( file_path = download_to_path(
"https://www.youtube.com/shorts/VIDEO_ID", 'https://www.youtube.com/shorts/VIDEO_ID',
"cookies.txt", 'cookies.txt',
output_dir="downloads", output_dir='downloads',
) )
``` ```
## CLI ### CLI
```bash ```bash
uv run yt-shorts-downloader \ uv run yt-shorts-downloader \
"https://www.youtube.com/shorts/VIDEO_ID" \ 'https://www.youtube.com/shorts/VIDEO_ID' \
cookies.txt \ cookies.txt \
--output-dir downloads --output-dir downloads
``` ```
CLI печатает в stdout итоговый путь к скачанному файлу.
## Пример интеграции с FastAPI
```python
from fastapi import FastAPI, HTTPException
from fastapi.responses import Response
from yt_shorts_downloader import download
from yt_shorts_downloader.exceptions import YtShortsDownloaderError
app = FastAPI()
@app.get('/download')
def download_short(url: str) -> Response:
try:
video = download(url, 'cookies.txt')
except YtShortsDownloaderError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return Response(
content=video.content,
media_type=video.media_type,
headers={
'Content-Disposition': f'attachment; filename="{video.filename}"',
},
)
```
## Публичный API
### `download(url, session_path) -> DownloadedVideo`
Скачивает видео во временную директорию, проверяет, что итоговый артефакт имеет формат MP4, и возвращает объект `DownloadedVideo`.
Поля `DownloadedVideo`:
- `filename`: имя файла
- `content`: MP4-байты
- `media_type`: всегда `video/mp4`
### `download_short(url, session_path) -> DownloadedVideo`
Алиас для `download`.
### `download_to_path(url, session_path, output_dir=None) -> Path`
Скачивает итоговый MP4 на диск и возвращает `Path` к файлу.
### `validate_session_file(path) -> SessionValidation`
Проверяет session file до попытки скачивания. Поля результата:
- `exists`
- `structurally_valid`
- `fresh`
- `is_usable`
- `message`
## Требования к session file
Проект ожидает Netscape cookie file, например экспортированный из браузера.
Проверяется:
- существование файла
- структура Netscape cookie format с 7 tab-separated columns
- наличие cookie для YouTube или Google
- наличие достаточного количества актуальных auth-cookie
Ключевые cookie-имена:
- `SID`
- `HSID`
- `SSID`
- `APISID`
- `SAPISID`
- `__Secure-1PSID`
- `__Secure-3PSID`
- `LOGIN_INFO`
Если файл структурно валиден, но auth-cookie устарели или неполные, библиотека выбрасывает `InvalidSessionError` до запуска yt-dlp.
## Ошибки
Пакет экспортирует следующие исключения:
- `YtShortsDownloaderError`: базовое исключение библиотеки
- `InvalidUrlError`: неподдерживаемая или некорректная ссылка
- `InvalidSessionError`: невалидный или устаревший session file
- `JsRuntimeUnavailableError`: не найден Deno или Node.js 22+
- `VideoDownloadError`: yt-dlp не смог получить валидный MP4
## Диагностика и troubleshooting
Частые причины проблем:
- `InvalidUrlError`: ссылка не относится к YouTube или имеет неверную схему
- `InvalidSessionError`: cookie-файл не найден, повреждён или содержит устаревшие auth-cookie
- `JsRuntimeUnavailableError`: в системе нет Deno и нет Node.js 22+
- `VideoDownloadError`: yt-dlp не смог скачать видео или итоговый файл не оказался MP4
- публикация в Gitea Packages пропускается: версия из `pyproject.toml` уже существует в registry
Практические рекомендации:
- переэкспортируйте cookies после переавторизации в браузере
- установите `ffmpeg`, если хотите лучший вариант объединения аудио и видео
- увеличивайте `project.version` в `pyproject.toml` перед новой публикацией пакета
- не коммитьте cookie-файлы в репозиторий
## Архитектура ## Архитектура
- src/yt_shorts_downloader/api.py: публичный API библиотеки Структура проекта разделена по зонам ответственности:
- src/yt_shorts_downloader/models: доменные модели
- src/yt_shorts_downloader/services: валидация и разбор session file
- src/yt_shorts_downloader/core: интеграция с yt-dlp и инфраструктурные функции
- stubs: локальные mypy stubs для внешних зависимостей без полной типизации
## Качество - `src/yt_shorts_downloader/api.py`: публичный API библиотеки
- `src/yt_shorts_downloader/cli.py`: CLI entrypoint
- `src/yt_shorts_downloader/models`: доменные модели и типизированные возвращаемые объекты
- `src/yt_shorts_downloader/services`: логика валидации session file
- `src/yt_shorts_downloader/core`: интеграция с yt-dlp, поиск runtime и URL validation
- `stubs`: локальные typing stubs для внешних пакетов без полной типизации
Это позволяет держать публичный API компактным, а инфраструктурный код изолировать в `core`.
## Разработка
Инструменты настраиваются в `pyproject.toml`.
- runtime-зависимости находятся в `[project.dependencies]`
- инструменты разработки находятся в `[dependency-groups.dev]`
- mypy работает в strict-режиме - mypy работает в strict-режиме
- runtime-зависимости лежат в секции project.dependencies - Ruff отвечает за lint и format
- инструменты разработки лежат в секции dependency-groups.dev
Основные команды: Основные команды:
```bash ```bash
make format make format
make ci-check make lint
make typecheck
make test make test
make ci-check
make build make build
make package-version
``` ```
## CI/CD ## Пакетирование и публикация
В репозитории добавлены Gitea Actions workflow: Метаданные пакета и версия берутся из `[project]` в `pyproject.toml`.
- .gitea/workflows/ci.yml: lint, mypy, deptry, pytest и автоматическая публикация в Gitea Packages после успешного CI на push в main Текущий publish flow:
Публикация настроена в Gitea PyPI registry по документации Gitea Packages. - Gitea Actions запускает quality checks и tests на pull request и push в `main`
Версия пакета берётся из секции [project] -> version в pyproject.toml. - после успешного CI на `main` workflow проверяет, существует ли текущая версия в Gitea Packages
Gitea не поддерживает повторную публикацию той же версии, поэтому CI сначала проверяет registry и пропускает upload, если эта версия уже существует. - если версии ещё нет, пакет собирается и публикуется
- если версия уже существует, публикация пропускается без падения workflow
- старые версии пакета сохраняются в registry
Нужные secrets для CI workflow: Необходимые secrets для CI:
- PYPI_REPOSITORY_URL: полный endpoint вида https://gitea.example.com/api/packages/<owner>/pypi - `PYPI_REPOSITORY_URL`: endpoint вида `https://gitea.example.com/api/packages/<owner>/pypi`
- PACKAGE_USERNAME: пользователь Gitea - `PACKAGE_USERNAME`: пользователь Gitea
- PACKAGE_TOKEN: personal access token с правом package write - `PACKAGE_TOKEN`: personal access token с правом package write
Публикация идёт автоматически на push в main после успешных lint/typecheck/test jobs. Локальная публикация:
Если версия пакета уже существует в registry, upload будет пропущен до вызова twine upload.
Если версия в pyproject.toml увеличена, в Gitea Packages появится новая версия пакета, а старые версии останутся доступными.
Локально тот же сценарий можно выполнить так:
```bash ```bash
export PYPI_REPOSITORY_URL="https://gitea.example.com/api/packages/<owner>/pypi" export PYPI_REPOSITORY_URL='https://gitea.example.com/api/packages/<owner>/pypi'
export PACKAGE_USERNAME="<username>" export PACKAGE_USERNAME='<username>'
export PACKAGE_TOKEN="<token>" export PACKAGE_TOKEN='<token>'
make package-version make package-version
make publish-gitea make publish-gitea
``` ```
Для обратной совместимости локальная команда publish-gitea также принимает переменные GITEA_PYPI_REPOSITORY_URL, GITEA_PACKAGE_USERNAME и GITEA_PACKAGE_TOKEN. Для обратной совместимости `publish-gitea` также принимает:
- `GITEA_PYPI_REPOSITORY_URL`
- `GITEA_PACKAGE_USERNAME`
- `GITEA_PACKAGE_TOKEN`
## CI/CD
Основной workflow:
- `.gitea/workflows/ci.yml`
Он выполняет:
- Ruff linting
- strict mypy type checking
- deptry dependency checks
- pytest test suite
- условную публикацию в Gitea Packages после успешного push CI на `main`
## Лицензия
См. `LICENSE`.