# yt-shorts-downloader Библиотека и CLI для скачивания YouTube Shorts в формате MP4 через Netscape cookie session file. Основной контракт проекта: - вход: ссылка 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. ## Установка Для использования библиотеки: ```bash uv pip install . ``` Для разработки: ```bash uv sync --group dev ``` ## Быстрый старт ### Python API ```python from yt_shorts_downloader import download video = download( 'https://www.youtube.com/shorts/VIDEO_ID', 'cookies.txt', ) assert video.media_type == 'video/mp4' assert video.filename.endswith('.mp4') binary_mp4 = video.content ``` Если нужен файл на диске: ```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', ) ``` ### CLI ```bash uv run yt-shorts-downloader \ '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/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-режиме - Ruff отвечает за lint и format Основные команды: ```bash make format make lint make typecheck make test make ci-check make build make package-version ``` ## Пакетирование и публикация Метаданные пакета и версия берутся из `[project]` в `pyproject.toml`. Текущий publish flow: - Gitea Actions запускает quality checks и tests на pull request и push в `main` - после успешного CI на `main` workflow проверяет, существует ли текущая версия в Gitea Packages - если версии ещё нет, пакет собирается и публикуется - если версия уже существует, публикация пропускается без падения workflow - старые версии пакета сохраняются в registry Необходимые secrets для CI: - `PYPI_REPOSITORY_URL`: endpoint вида `https://gitea.example.com/api/packages//pypi` - `PACKAGE_USERNAME`: пользователь Gitea - `PACKAGE_TOKEN`: personal access token с правом package write Локальная публикация: ```bash export PYPI_REPOSITORY_URL='https://gitea.example.com/api/packages//pypi' export PACKAGE_USERNAME='' export PACKAGE_TOKEN='' make package-version make publish-gitea ``` Для обратной совместимости `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`.