Files
ВяткинАртём 0a3cf97200
CI / Quality Checks (push) Successful in 13s
CI / Test Suite (push) Successful in 10s
CI / Publish to Gitea Packages (push) Successful in 11s
Обновить документацию: уточнить формат скачиваемых видео, добавить примеры использования и требования к session file
2026-05-27 17:59:21 +03:00

9.2 KiB
Raw Permalink Blame History

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.

Установка

Для использования библиотеки:

uv pip install .

Для разработки:

uv sync --group dev

Быстрый старт

Python API

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

Если нужен файл на диске:

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

uv run yt-shorts-downloader \
  'https://www.youtube.com/shorts/VIDEO_ID' \
  cookies.txt \
  --output-dir downloads

CLI печатает в stdout итоговый путь к скачанному файлу.

Пример интеграции с FastAPI

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

Основные команды:

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/<owner>/pypi
  • PACKAGE_USERNAME: пользователь Gitea
  • PACKAGE_TOKEN: personal access token с правом package write

Локальная публикация:

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

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.