194 lines
7.2 KiB
Markdown
194 lines
7.2 KiB
Markdown
# Ombrora-YTDLP — Design Spec
|
|
|
|
Date: 2026-08-10
|
|
|
|
## Vue d'ensemble
|
|
|
|
Interface web publique permettant a n'importe quel utilisateur de soumettre une URL de video a telecharger via yt-dlp. Les telechargements sont traites en arriere-plan par un worker Node.js gere par Passenger sur o2switch. Les fichiers sont stockes localement dans un dossier prive et accessibles via un lien temporaire a duree de vie configurable.
|
|
|
|
---
|
|
|
|
## Architecture generale
|
|
|
|
Approche retenue : **Monorepo Next.js + Worker script Node.js separe**.
|
|
|
|
- `src/` : app Next.js (UI React + API routes)
|
|
- `worker/` : script Node.js long-running qui poll la DB et execute yt-dlp
|
|
- `config/` : configuration centralisee (TTL, concurrence, chemins)
|
|
- `prisma/` : schema et migrations
|
|
- `docker-compose.yml` : MariaDB pour le developpement local
|
|
- Un seul `package.json` a la racine (contrainte o2switch/Passenger)
|
|
|
|
---
|
|
|
|
## Structure du projet
|
|
|
|
```
|
|
Ombrora-YTDLP/
|
|
├── src/
|
|
│ ├── app/
|
|
│ │ ├── page.tsx # Formulaire de soumission
|
|
│ │ ├── status/[uuid]/page.tsx # Page de statut
|
|
│ │ └── api/
|
|
│ │ ├── downloads/route.ts # POST soumission, GET statut
|
|
│ │ ├── downloads/[uuid]/route.ts
|
|
│ │ └── download/[token]/route.ts # Stream fichier via token
|
|
│ ├── components/
|
|
│ └── lib/
|
|
│ ├── prisma.ts
|
|
│ ├── rate-limit.ts
|
|
│ └── token.ts
|
|
├── worker/
|
|
│ ├── index.ts # Worker long-running principal
|
|
│ ├── cron-check.ts # Verifie que le worker tourne (cron 5 min)
|
|
│ └── cron-cleanup.ts # Supprime les fichiers expires (cron 1x/jour)
|
|
├── config/
|
|
│ └── app.config.ts
|
|
├── prisma/
|
|
│ └── schema.prisma
|
|
├── docker-compose.yml
|
|
└── package.json
|
|
```
|
|
|
|
---
|
|
|
|
## Schema base de donnees (Prisma / MariaDB)
|
|
|
|
```prisma
|
|
model Download {
|
|
id String @id @default(cuid())
|
|
uuid String @unique @default(uuid()) // expose sur les interfaces
|
|
url String
|
|
status Status @default(PENDING)
|
|
format String // ex: "mp4", "mp3"
|
|
quality String // ex: "best", "1080p"
|
|
subtitles Boolean @default(false)
|
|
extraArgs String? // options yt-dlp (JSON array de strings, ex: ["--sponsorblock-remove","all"])
|
|
|
|
filePath String? // chemin absolu sur disque
|
|
fileName String? // nom original
|
|
fileSize BigInt? // taille en octets
|
|
errorMsg String?
|
|
|
|
ipAddress String
|
|
submittedAt DateTime @default(now())
|
|
startedAt DateTime?
|
|
completedAt DateTime?
|
|
deletedAt DateTime?
|
|
|
|
tokens DownloadToken[]
|
|
}
|
|
|
|
model DownloadToken {
|
|
id String @id @default(cuid())
|
|
download Download @relation(fields: [downloadId], references: [id])
|
|
downloadId String
|
|
token String @unique @default(uuid())
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
enum Status {
|
|
PENDING // en attente de traitement
|
|
PROCESSING // worker en cours
|
|
DONE // fichier disponible
|
|
FAILED // echec yt-dlp
|
|
FILE_DELETED // metadonnees conservees, fichier supprime du disque
|
|
}
|
|
```
|
|
|
|
**Regles immuables :**
|
|
- Aucune entree n'est jamais supprimee de la DB (historique complet).
|
|
- `id` (cuid) usage interne uniquement ; `uuid` expose sur toutes les interfaces.
|
|
- Le fichier est stocke sous `{STORAGE_PATH}/{uuid}.{extension}`.
|
|
|
|
---
|
|
|
|
## Worker (worker/index.ts)
|
|
|
|
Processus Node.js long-running sous Passenger. Cycle a chaque iteration :
|
|
|
|
1. Requete DB : `SELECT` les `PENDING` ordonnes par `submittedAt`, limite a `WORKER_CONCURRENCY`.
|
|
2. Passe les entrees selectionnees en `PROCESSING` (atomique : UPDATE WHERE status=PENDING).
|
|
3. Spawne un `worker_thread` par telechargement.
|
|
4. Chaque thread construit la commande yt-dlp :
|
|
- `--no-playlist` toujours present
|
|
- `-f {format}` pour le format
|
|
- `--write-sub --sub-lang fr,en` si `subtitles=true`
|
|
- `-o {STORAGE_PATH}/{uuid}.%(ext)s` pour le chemin de sortie
|
|
- extraArgs injectes depuis le champ JSON
|
|
5. Fin du thread :
|
|
- **Succes** : `status=DONE`, remplit `filePath`, `fileName`, `fileSize`, `completedAt`, cree un `DownloadToken` avec `expiresAt = now + DOWNLOAD_LINK_TTL_HOURS`.
|
|
- **Echec** : `status=FAILED`, remplit `errorMsg` (stderr yt-dlp).
|
|
6. Attend `WORKER_POLL_INTERVAL_MS` puis recommence.
|
|
|
|
---
|
|
|
|
## Crons o2switch (cPanel)
|
|
|
|
| Script | Frequence | Role |
|
|
|--------|-----------|------|
|
|
| `worker/cron-check.ts` | Toutes les 5 min | Relance le worker s'il n'est pas actif |
|
|
| `worker/cron-cleanup.ts` | 1x par jour | Supprime les fichiers dont `completedAt + TTL < now`, passe `status=FILE_DELETED`, remplit `deletedAt` |
|
|
|
|
---
|
|
|
|
## API Routes (Next.js)
|
|
|
|
| Methode | Route | Description |
|
|
|---------|-------|-------------|
|
|
| `POST` | `/api/downloads` | Valide IP (rate limit), cree l'entree DB, retourne `{ uuid }` |
|
|
| `GET` | `/api/downloads/[uuid]` | Retourne statut et metadonnees du telechargement |
|
|
| `GET` | `/api/download/[token]` | Verifie token (existence, expiration), stream le fichier, marque `usedAt` |
|
|
|
|
**Rate limiting** (`lib/rate-limit.ts`) :
|
|
- Map en memoire : IP -> `{ count, resetAt }`
|
|
- Limite et fenetre configurables dans `app.config.ts`
|
|
- Pas de Redis requis
|
|
- Hypothese : Passenger tourne avec 1 seul processus Next.js sur o2switch (standard). Si plusieurs processus sont configures, la Map n'est pas partagee et le rate limit serait par-processus — a surveiller.
|
|
|
|
---
|
|
|
|
## Configuration (config/app.config.ts)
|
|
|
|
```typescript
|
|
export const config = {
|
|
DOWNLOAD_LINK_TTL_HOURS: 24, // duree de validite du lien temporaire
|
|
WORKER_CONCURRENCY: 3, // telechargements en parallele
|
|
WORKER_POLL_INTERVAL_MS: 10_000, // intervalle de poll de la DB
|
|
STORAGE_PATH: '/home/.../storage', // chemin absolu du dossier de stockage
|
|
RATE_LIMIT_MAX: 5, // soumissions max par IP
|
|
RATE_LIMIT_WINDOW_MS: 3_600_000, // fenetre de rate limit (1h)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Frontend (React / Next.js)
|
|
|
|
### `/` — Formulaire de soumission
|
|
- Champ URL
|
|
- Select format (mp4, mp3, webm, ...)
|
|
- Select qualite (best, 1080p, 720p, 480p, ...)
|
|
- Checkbox sous-titres
|
|
- Champ options avancees (optionnel, libre)
|
|
- Soumission -> POST `/api/downloads` -> redirect vers `/status/{uuid}`
|
|
|
|
### `/status/[uuid]` — Page de statut
|
|
- Polling `GET /api/downloads/[uuid]` toutes les 5 secondes
|
|
- Affichage selon statut :
|
|
- `PENDING` / `PROCESSING` : indicateur de progression
|
|
- `DONE` : bouton de telechargement (lien `/api/download/{token}`)
|
|
- `FAILED` : message d'erreur yt-dlp
|
|
- `FILE_DELETED` : message "fichier expire, le telechargement n'est plus disponible"
|
|
|
|
---
|
|
|
|
## Contraintes de deploiement o2switch
|
|
|
|
- Un seul `package.json` / `node_modules` a la racine (pas de `frontend/node_modules`).
|
|
- Toute dependance runtime du frontend doit etre dans les `dependencies` de la racine ET dans `frontend/package.json` (si sous-dossier).
|
|
- yt-dlp doit etre installe comme binaire precompile (pas de build depuis les sources).
|
|
- Les dependances avec bindings natifs doivent publier des binaires precompiles pour la plateforme o2switch.
|