Обновить документацию: уточнить формат скачиваемых видео, добавить примеры использования и требования к 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
+213 -45
View File
@@ -1,11 +1,33 @@
# yt-shorts-downloader
Библиотека и CLI для скачивания YouTube Shorts через Netscape cookie session.
Библиотека и CLI для скачивания YouTube Shorts в формате MP4 через Netscape cookie session file.
Целевой контракт публичного API:
Основной контракт проекта:
- вход: ссылка YouTube и путь к session file
- выход: бинарный MP4 в памяти для интеграции в API
- вход: ссылка YouTube и путь к cookie-файлу
- выход для 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
```
## Использование библиотеки
## Быстрый старт
### Python API
```python
from yt_shorts_downloader import download
video = download(
"https://www.youtube.com/shorts/VIDEO_ID",
"cookies.txt",
'https://www.youtube.com/shorts/VIDEO_ID',
'cookies.txt',
)
assert video.media_type == "video/mp4"
assert video.media_type == 'video/mp4'
assert video.filename.endswith('.mp4')
binary_mp4 = video.content
filename = video.filename
```
Если нужен файл на диске, используйте вспомогательную функцию:
Если нужен файл на диске:
```python
from yt_shorts_downloader import download_to_path
file_path = download_to_path(
"https://www.youtube.com/shorts/VIDEO_ID",
"cookies.txt",
output_dir="downloads",
'https://www.youtube.com/shorts/VIDEO_ID',
'cookies.txt',
output_dir='downloads',
)
```
## CLI
### CLI
```bash
uv run yt-shorts-downloader \
"https://www.youtube.com/shorts/VIDEO_ID" \
cookies.txt \
--output-dir downloads
'https://www.youtube.com/shorts/VIDEO_ID' \
cookies.txt \
--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-режиме
- runtime-зависимости лежат в секции project.dependencies
- инструменты разработки лежат в секции dependency-groups.dev
- Ruff отвечает за lint и format
Основные команды:
```bash
make format
make ci-check
make lint
make typecheck
make test
make ci-check
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.
Версия пакета берётся из секции [project] -> version в pyproject.toml.
Gitea не поддерживает повторную публикацию той же версии, поэтому CI сначала проверяет registry и пропускает upload, если эта версия уже существует.
- Gitea Actions запускает quality checks и tests на pull request и push в `main`
- после успешного CI на `main` workflow проверяет, существует ли текущая версия в Gitea Packages
- если версии ещё нет, пакет собирается и публикуется
- если версия уже существует, публикация пропускается без падения workflow
- старые версии пакета сохраняются в registry
Нужные secrets для CI workflow:
Необходимые secrets для CI:
- PYPI_REPOSITORY_URL: полный endpoint вида https://gitea.example.com/api/packages/<owner>/pypi
- PACKAGE_USERNAME: пользователь Gitea
- PACKAGE_TOKEN: personal access token с правом package write
- `PYPI_REPOSITORY_URL`: endpoint вида `https://gitea.example.com/api/packages/<owner>/pypi`
- `PACKAGE_USERNAME`: пользователь Gitea
- `PACKAGE_TOKEN`: personal access token с правом package write
Публикация идёт автоматически на push в main после успешных lint/typecheck/test jobs.
Если версия пакета уже существует в registry, upload будет пропущен до вызова twine upload.
Если версия в pyproject.toml увеличена, в Gitea Packages появится новая версия пакета, а старые версии останутся доступными.
Локально тот же сценарий можно выполнить так:
Локальная публикация:
```bash
export PYPI_REPOSITORY_URL="https://gitea.example.com/api/packages/<owner>/pypi"
export PACKAGE_USERNAME="<username>"
export PACKAGE_TOKEN="<token>"
export PYPI_REPOSITORY_URL='https://gitea.example.com/api/packages/<owner>/pypi'
export PACKAGE_USERNAME='<username>'
export PACKAGE_TOKEN='<token>'
make package-version
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`.