Application web self-hosted pour lister, mettre en file d'attente et extraire des vidéos/audios YouTube (yt-dlp + ffmpeg), avec suivi en temps réel.
Find a file
Nicolas 374cfbb192 feat: recherche YouTube globale, avec defilement infini
Nouveau formulaire "Recherche YouTube" (mots-cles + limite optionnelle),
branche sur la zone de resultats existante (miniatures, bouton "Ajouter"
par ligne ; pas de "Tout ajouter", comme demande).

- Avec une limite : tous les resultats d'un coup (une seule page).
- Sans limite : defilement infini. Un element sentinelle en fin de liste,
  observe par un IntersectionObserver (racine = la liste, marge 300 px),
  charge la page suivante ; il est rearme apres chaque page, affiche
  chargement / erreur avec "Reessayer" / "Fin des resultats", et une page
  en echec ne fait pas perdre les precedentes.

Serveur (GET/POST/DELETE /api/search) : une "session" = un seul yt-dlp
(--flat-playlist --lazy-playlist -j -- ytsearch1000:<requete>) servi page
par page depuis un tampon. Pourquoi pas un appel yt-dlp par page - mesure
avec le vrai yt-dlp : l'ordre des resultats de YouTube n'est pas
deterministe meme pour deux appels identiques (5/20 aux memes positions,
69 % de recouvrement sur 100), donc paginer en relancant la recherche
donnait 15 a 30 % de doublons des la 3e-4e page et des trous ; YouTube
s'arrete vers ~590 resultats ; ~7 % de doublons internes (dedoublonnes a
l'ingestion) ; sans --lazy-playlist yt-dlp n'imprime rien avant la fin
(~23 s), avec il emet au fil des pages (20 en ~2,3 s).

Le parcours n'avance qu'a la demande : quand le tampon a 20 resultats
d'avance, la lecture de stdout est suspendue (nouvelle option onSpawn de
runYtDlp) ; le tube se remplit et yt-dlp bloque (mesure : plus aucune
ligne pendant 10 s de pause). On n'aspire donc pas ~590 resultats, et ~30
requetes vers YouTube, quand l'utilisateur ne defile pas.

Cycle de vie : 4 sessions simultanees max (la moins recemment utilisee est
fermee), expiration a 10 min d'inactivite / 30 min de vie, fermeture des
que la derniere page est servie, sur DELETE (liste remplacee cote client),
au pagehide du client, et a l'arret du serveur. Requete validee (1 a 200
caracteres, sans caractere de controle) et transmise en un seul argument
ytsearchN:<requete>, jamais interpretable comme une option.

Mise en page : ce troisieme formulaire laissait a la liste ~104 px (une
ligne) a 1366x650. Une liste non vide garde min-height 13rem et la colonne
du formulaire defile au lieu de rogner (la page ne defile toujours pas).

Verifie : contre le vrai yt-dlp (page tamponnee en 4 ms, page 200 apres
12 s d'inactivite en ~4 s : la suspension tient ; 29 pages -> 576 videos,
576 uniques ; processus fermes a la fin / sur DELETE / au plafond) et dans
le navigateur (580 resultats sans doublon jusqu'a "Fin des resultats",
requetes de page uniques malgre deux signaux simultanes, echec + reprise,
session expiree, remplacement de la recherche, mode limite, ajout d'un
resultat a la file, pagehide, playlist et chaine en non-regression). Le
volet IntersectionObserver a ete pilote par un observateur factice : le
panneau de navigation n'affichait plus de frames (rAF/IO natifs muets).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 15:43:46 +02:00
.forgejo/workflows feat(client): frontend (formulaire d'ajout + file d'attente) 2026-09-16 09:28:49 +02:00
client feat: recherche YouTube globale, avec defilement infini 2026-09-19 15:43:46 +02:00
server feat: recherche YouTube globale, avec defilement infini 2026-09-19 15:43:46 +02:00
spec feat: recherche YouTube globale, avec defilement infini 2026-09-19 15:43:46 +02:00
.gitignore chore: init .gitignore 2026-09-16 07:33:38 +02:00
biome.json feat: squelette serveur/client + Docker + CI de build/release 2026-09-16 07:55:43 +02:00
package-lock.json feat: squelette serveur/client + Docker + CI de build/release 2026-09-16 07:55:43 +02:00
package.json feat: squelette serveur/client + Docker + CI de build/release 2026-09-16 07:55:43 +02:00
README.md feat: recherche YouTube globale, avec defilement infini 2026-09-19 15:43:46 +02:00

youtubeDownloader

Application web self-hosted pour parcourir, mettre en file d'attente et extraire des vidéos/audios YouTube, en s'appuyant sur yt-dlp et ffmpeg.

Ce projet reprend et remplace une série de scripts .cmd (téléchargement vidéo, récupération de miniatures, recherche de streams par chaîne) par une interface web unique.

Fonctionnalités prévues

  • Parcourir une chaîne : recherche par chaîne (URL ou @handle), liste de ses vidéos/streams avec filtre par mots-clés dans le titre.
  • Ajout par URL : ajout direct d'une ou plusieurs URLs YouTube.
  • Recherche YouTube : recherche globale par mots-clés, avec une limite de résultats optionnelle ; sans limite, la liste se prolonge en défilant jusqu'à la fin des résultats de YouTube.
  • Playlists : coller un lien de playlist (youtube.com/playlist?list=...) affiche toutes ses vidéos, à ajouter une par une ou d'un coup (« Tout ajouter à la file »).
  • File d'attente : chaque élément ajouté est configurable individuellement :
    • Vidéo : qualité, sous-titres (fr/en), miniature intégrée, métadonnées.
    • Audio seul : extraction dans un format courant (mp3/m4a/opus).
    • Miniature seule : récupération du poster uniquement.
  • Exécution en parallèle : plusieurs téléchargements simultanés (concurrence configurable), avec suivi de progression en temps réel.
  • Livraison au navigateur : les fichiers terminés sont téléchargés directement par l'utilisateur (pas de dépôt sur une bibliothèque partagée type Emby).
  • Accès : aucune authentification, usage LAN uniquement.

Stack technique (prévue)

  • Backend : TypeScript / Fastify, organisé en services modulaires (découverte de chaîne, gestion de la file d'attente, exécution des téléchargements, notifications temps réel).
  • Frontend : TypeScript vanilla, vues séparées en .ts / .html / .scss.
  • Moteur : yt-dlp + ffmpeg (installés dans l'image Docker, non versionnés).
  • Déploiement : Docker / docker-compose.
  • Linting : Biome.js.

Développement

Squelette actuel : serveur Fastify (server/) + client minimal (client/), sans encore la logique de découverte/file d'attente/téléchargement.

# Racine : lint (Biome) sur tout le repo
npm install && npm run lint

# Client : build (esbuild + sass) -> client/dist
cd client && npm install && npm run build

# Serveur : typecheck, tests, dev (sert client/dist)
cd server && npm install
npm run typecheck
npm test
npm run dev

Le serveur écoute par défaut sur http://localhost:3000 (/health pour la vérification de santé) et sert les fichiers statiques de client/dist (variable CLIENT_DIST_PATH).

Docker

docker build -f server/Dockerfile -t youtubedownloader .
docker run -p 3000:3000 youtubedownloader

CI/CD

.forgejo/workflows/publish-docker.yml teste (lint + typecheck + tests + build) puis publie l'image sur git.dev.kyuno.fr/kyuno/youtubedownloader (push sur main -> latest + sha court, tag vX.Y.Z -> version). Nécessite un secret REGISTRY_TOKEN sur le dépôt (voir l'en-tête du workflow).

Statut

Squelette d'infrastructure en place (serveur/client minimaux, Docker, CI). Fonctionnalités listées ci-dessus pas encore implémentées. Voir les issues et PR pour le suivi du développement.