Files

157 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PVideoDl
Локальная скачивалка файлов и видео с веб-интерфейсом. Запускается одной командой,
открывается в браузере, принимает список ссылок, качает в фоне и показывает прогресс
в реальном времени.
- **Прямые файлы** (`.zip`, `.pdf`, `.mp4`, …) качаются через `httpx` по chunk'ам.
- **Видео с сайтов** (YouTube и сотни других) — через `yt-dlp`.
- Прогресс течёт в UI по **SSE** в реальном времени.
- Три вкладки: **Активные** (что качается сейчас и завершилось за последний час),
**История** (все завершённые загрузки) и **Загрузчики** (какие стратегии скачивания
сейчас доступны).
- Если ссылка не прямой файл и ни один загрузчик её не поддерживает — загрузка
завершается явной ошибкой (а не молчаливой попыткой угадать).
## Стек
- Бэкенд: Python 3.12, FastAPI, Uvicorn, httpx, yt-dlp (+ curl_cffi для импперсонации), aiosqlite, sse-starlette
- Фронтенд: SvelteKit (SPA), TypeScript, Tailwind CSS v4, lucide-svelte
- Менеджер зависимостей: `uv`
## Архитектура
Реализации спрятаны за интерфейсами в `app/core/` — роуты и сервисы зовут абстракции,
а не конкретику. Сегодня `asyncio.Queue` + SQLite, завтра Redis + Postgres — меняется
только начинка обёртки.
```
app/
main.py точка входа, lifespan, отдача статики
config.py настройки (env PVDL_*)
models.py Pydantic-модели
api/ роуты: downloads.py, events.py (SSE)
core/ абстракции: queue.py, storage.py, events.py (EventBus)
services/ downloader.py (реестр стратегий), worker.py (пул воркеров)
extractors/ расширения для сайтов с непрямыми ссылками
frontend/ SvelteKit SPA → собирается в frontend/build
```
### Расширения: сайты с непрямыми ссылками
Выбор стратегии — цепочка обработчиков с приоритетами (`pick_downloader`):
кастомные экстракторы (priority > 0) перехватывают URL раньше встроенных
`HttpxDownloader` (прямые файлы) и `YtDlpDownloader` (универсальный фолбэк).
Добавить сайт = положить один файл в `app/services/extractors/`. Чаще всего хватает
**резолвера** — достать прямую ссылку (и при нужде `Referer`/`Cookie`), а
скачивание, прогресс и `(n)`-имена наследуются:
```python
# app/services/extractors/my_site.py
import re
from app.services.downloader import Resolved, SiteExtractor, register
@register
class MySite(SiteExtractor):
priority = 100
@classmethod
def matches(cls, url: str) -> bool:
return "my-site.com/watch/" in url
async def resolve(self, url: str) -> Resolved:
# ... найти настоящую ссылку (запрос/скрейпинг) ...
return Resolved(download_url=real_url, headers={"Referer": url})
```
Модули пакета авто-загружаются на старте (`load_extractors()`). Готовый образец —
[google_drive.py](app/services/extractors/google_drive.py). Если сайту нужен
нестандартный процесс (HLS, сегменты) — наследуйся прямо от `Downloader` и
переопредели `download()` целиком.
## Запуск — готовые скрипты
В корне лежат скрипты-обёртки (Linux/macOS — `.sh`, Windows — `.bat`):
```bash
# Прод: зависимости -> сборка фронта (если её нет) -> сервер
./run.sh # Windows: run.bat
# Дев: бэкенд (:8000) + Vite dev-сервер (:5173) с hot-reload
./dev.sh # Windows: dev.bat
```
`run` открывает <http://127.0.0.1:8000>, `dev` — <http://localhost:5173>.
Файлы складываются в `downloads/`.
## Запуск (прод, «для себя») — вручную
Фронт собирается в статику, FastAPI отдаёт её с того же origin:
```bash
# 1. зависимости
uv sync
cd frontend && npm install && npm run build && cd ..
# 2. запуск
uv run pvideodl
```
## Запуск (дев) — два процесса
```bash
# терминал 1 — бэкенд на :8000
uv run python -m app.main
# терминал 2 — Vite dev-сервер на :5173 (проксирует /api на :8000)
cd frontend && npm run dev
```
Открыть <http://localhost:5173> — с hot-reload фронта.
## API
| Метод | Путь | Назначение |
| -------- | --------------------- | ----------------------------------- |
| `POST` | `/api/downloads` | Добавить ссылки (`{"urls": [...]}`) |
| `GET` | `/api/downloads` | Список всех загрузок |
| `DELETE` | `/api/downloads/{id}` | Удалить задачу |
| `GET` | `/api/downloaders` | Список загруженных стратегий |
| `GET` | `/api/events` | SSE-поток обновлений прогресса |
| `GET` | `/api/health` | Проверка живости |
## Настройки (переменные окружения)
| Переменная | По умолчанию | Описание |
| ------------------- | ------------------ | ---------------------------- |
| `PVDL_DOWNLOAD_DIR` | `./downloads` | Куда складывать файлы |
| `PVDL_DB_PATH` | `./app.db` | Файл SQLite |
| `PVDL_STATIC_DIR` | `./frontend/build` | Собранная статика фронтенда |
| `PVDL_WORKERS` | `3` | Сколько воркеров параллельно |
| `PVDL_HOST` | `127.0.0.1` | Хост сервера |
| `PVDL_PORT` | `8000` | Порт сервера |
| `PVDL_COOKIES_FROM_BROWSER` | — | Cookies для yt-dlp из браузера: `chrome`, `firefox`, `edge`… (можно `chrome:Профиль`) |
| `PVDL_COOKIES_FILE` | — | Cookies для yt-dlp из файла (формат Netscape `cookies.txt`) |
| `PVDL_IMPERSONATE` | `chrome` | Импперсонация браузера для yt-dlp (`chrome`, `edge`, `safari`…); пусто — выключить |
> Cookies нужны для сайтов, которые блокируют анонимные запросы (возрастной гейт,
> логин, гео). Достаточно одного из вариантов.
>
> Сайты с анти-ботом по TLS-отпечатку (например PornHub, иначе `HTTP 410 Gone` или
> обрыв соединения при скачивании видео) требуют импперсонации браузера. Она включена
> по умолчанию (`PVDL_IMPERSONATE=chrome`) через `curl_cffi` и применяется ко всем
> запросам yt-dlp. Выключить — `PVDL_IMPERSONATE=` (пусто).
## Тесты
```bash
uv run pytest
```
## Задел на будущее
Эндпоинты `pause` / `resume` / `retry` и статус `PAUSED` лягут естественно —
модель и слои уже к ним готовы. Тяжёлую инфраструктуру (Redis, Celery, WebSocket,
Postgres) вводим только когда конкретная фича упрётся в текущую реализацию.