feat: playlists YouTube (parcourir, ajouter une par une ou d'un coup) #10

Merged
kyuno merged 1 commit from feature/playlist-support into main 2026-09-19 15:49:37 +02:00
Owner

Résumé

Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ URL affiche toutes ses vidéos dans la même zone de résultats que la recherche de chaîne. Chaque ligne a son bouton « Ajouter », et un bouton « Tout ajouter à la file » ajoute le reste d'un coup.

Le champ « URL directe » devient « URL (vidéo ou playlist) » : un lien de vidéo est ajouté directement à la file comme avant, un lien de playlist charge ses vidéos. Le bouton passe de « Ajouter à la file » à « Ajouter » puisqu'il fait désormais l'un ou l'autre.

Serveur

  • GET /api/playlist?url= (PlaylistDiscoveryService) : yt-dlp --flat-playlist -J. -J (un seul objet JSON) plutôt que -j : le titre de la playlist n'existe qu'à la racine, et une playlist vide reste un résultat valide avec son titre. 200 { title, videos }, 400 si le lien n'est pas une playlist, 502 si yt-dlp échoue (introuvable/privée, binaire absent, sortie illisible).
  • playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id> (https, domaines YouTube, sans port ni identifiants) et reconstruit l'URL canonique depuis le seul identifiant validé — rien d'autre de l'entrée n'atteint yt-dlp. Même garde anti-SSRF que videoUrl.ts.
  • videoSummary.ts : la conversion d'une entrée --flat-playlist en résumé de vidéo est extraite de ChannelDiscoveryService pour être partagée (chaîne et playlist parsent identiquement, y compris le repli sur thumbnails du correctif précédent). ChannelVideoSummary → VideoSummary côté serveur, ChannelVideoDto → VideoDto côté client (le type ne désigne plus uniquement des vidéos de chaîne).

Client

  • isPlaylistUrl() (fonction pure, testée) aiguille la saisie ; le serveur reste l'autorité de validation.
  • Zone de résultats partagée entre recherche de chaîne et playlist (createResultsView), avec statut (« Playlist « X » : 13 vidéo(s). »), erreurs d'ajout et bouton « Tout ajouter ».
  • « Tout ajouter » enchaîne des POST /api/queue séquentiels : l'ordre de la playlist est conservé dans la file, les lignes déjà ajoutées sont ignorées (aucun doublon), un échec n'interrompt pas la suite — en fin de lot le nombre d'échecs et le premier message sont affichés, seules les lignes en échec redeviennent ajoutables, et relancer le bouton ne réajoute que celles-là. Le mode est lu une seule fois pour tout le lot.
  • Miniatures de résultats en loading="lazy" (une playlist peut compter des centaines de vidéos).

Choix à connaître

  • Un lien watch?v=…&list=… (vidéo vue depuis une playlist) reste traité comme une seule vidéo, côté client comme côté serveur (et le téléchargement passait déjà --no-playlist). C'est la règle la plus prévisible, mais si tu préfères qu'un tel lien ouvre la playlist, c'est un changement isolé dans les deux playlistUrl.ts.
  • « Tout ajouter » n'apparaît que pour les playlists, pas pour les résultats de recherche de chaîne (une chaîne peut compter des milliers de vidéos ; un clic accidentel les mettrait toutes en file). Trivial à activer si voulu (allowAddAll: true dans wireChannelForm).
  • Pas de boîte de confirmation : le nombre de vidéos est affiché dans le statut, juste à côté du bouton.

Vérifications

  • npm run lint (Biome) + typecheck + build serveur/client : clean ; tests : 127 serveur (dont 60 nouveaux : validation d'URL par cas limites/anti-SSRF, service, route, résumé d'entrée) + 22 client.
  • Contre le vrai yt-dlp (2026.07.04, pas seulement des mocks) : forme réelle de -J relevée et fixtures de test calquées dessus (après le bug des miniatures, ne plus se fier à la forme théorique). Playlist de 1607 vidéos listée en ~11 s (2 Mo de JSON) → délai fixé à 120 s. Playlist inexistante → 502 avec message clair ; lien noyé de paramètres (music.youtube.com…&si=…&index=…) → canonicalisé.
  • Dans le navigateur réel (clics réels, serveur branché sur le vrai yt-dlp) : playlist de 13 vidéos affichée (miniatures, durées) → ajout unitaire → « Tout ajouter » (file de 13, 13 titres distincts, aucun doublon avec la ligne déjà ajoutée, concurrence bornée respectée) ; échec partiel simulé (2 POST rejetés) → message « 2 vidéo(s) n'ont pas pu être ajoutée(s) », seules ces 2 lignes réactivées, relance → 13/13 sans doublon ; playlist inexistante → message sous le formulaire, champ conservé pour retenter ; watch?v=…&list=… → ajouté comme vidéo seule ; recherche de chaîne (non-régression : 60 résultats avec miniatures, sans bouton « Tout ajouter », pas de débordement de page) ; thèmes clair et sombre.

🤖 Generated with Claude Code

## Résumé Coller un lien de playlist (`youtube.com/playlist?list=...`) dans le champ URL affiche **toutes ses vidéos** dans la même zone de résultats que la recherche de chaîne. Chaque ligne a son bouton « Ajouter », et un bouton **« Tout ajouter à la file »** ajoute le reste d'un coup. Le champ « URL directe » devient « URL (vidéo ou playlist) » : un lien de vidéo est ajouté directement à la file comme avant, un lien de playlist charge ses vidéos. Le bouton passe de « Ajouter à la file » à « Ajouter » puisqu'il fait désormais l'un ou l'autre. ## Serveur - `GET /api/playlist?url=` (`PlaylistDiscoveryService`) : `yt-dlp --flat-playlist -J`. `-J` (un seul objet JSON) plutôt que `-j` : le titre de la playlist n'existe qu'à la racine, et une playlist vide reste un résultat valide avec son titre. `200 { title, videos }`, `400` si le lien n'est pas une playlist, `502` si yt-dlp échoue (introuvable/privée, binaire absent, sortie illisible). - `playlistUrl.ts` : n'accepte que les liens `youtube.com/playlist?list=<id>` (https, domaines YouTube, sans port ni identifiants) et **reconstruit l'URL canonique** depuis le seul identifiant validé — rien d'autre de l'entrée n'atteint yt-dlp. Même garde anti-SSRF que `videoUrl.ts`. - `videoSummary.ts` : la conversion d'une entrée `--flat-playlist` en résumé de vidéo est extraite de `ChannelDiscoveryService` pour être partagée (chaîne et playlist parsent identiquement, y compris le repli sur `thumbnails` du correctif précédent). `ChannelVideoSummary` → `VideoSummary` côté serveur, `ChannelVideoDto` → `VideoDto` côté client (le type ne désigne plus uniquement des vidéos de chaîne). ## Client - `isPlaylistUrl()` (fonction pure, testée) aiguille la saisie ; le serveur reste l'autorité de validation. - Zone de résultats partagée entre recherche de chaîne et playlist (`createResultsView`), avec statut (« Playlist « X » : 13 vidéo(s). »), erreurs d'ajout et bouton « Tout ajouter ». - « Tout ajouter » enchaîne des `POST /api/queue` **séquentiels** : l'ordre de la playlist est conservé dans la file, les lignes déjà ajoutées sont ignorées (aucun doublon), un échec n'interrompt pas la suite — en fin de lot le nombre d'échecs et le premier message sont affichés, seules les lignes en échec redeviennent ajoutables, et relancer le bouton ne réajoute que celles-là. Le mode est lu **une seule fois** pour tout le lot. - Miniatures de résultats en `loading="lazy"` (une playlist peut compter des centaines de vidéos). ## Choix à connaître - Un lien **`watch?v=…&list=…`** (vidéo vue depuis une playlist) reste traité comme **une seule vidéo**, côté client comme côté serveur (et le téléchargement passait déjà `--no-playlist`). C'est la règle la plus prévisible, mais si tu préfères qu'un tel lien ouvre la playlist, c'est un changement isolé dans les deux `playlistUrl.ts`. - « Tout ajouter » n'apparaît que pour les **playlists**, pas pour les résultats de recherche de chaîne (une chaîne peut compter des milliers de vidéos ; un clic accidentel les mettrait toutes en file). Trivial à activer si voulu (`allowAddAll: true` dans `wireChannelForm`). - Pas de boîte de confirmation : le nombre de vidéos est affiché dans le statut, juste à côté du bouton. ## Vérifications - `npm run lint` (Biome) + typecheck + build serveur/client : clean ; tests : **127 serveur** (dont 60 nouveaux : validation d'URL par cas limites/anti-SSRF, service, route, résumé d'entrée) + **22 client**. - **Contre le vrai yt-dlp** (2026.07.04, pas seulement des mocks) : forme réelle de `-J` relevée et fixtures de test calquées dessus (après le bug des miniatures, ne plus se fier à la forme théorique). Playlist de **1607 vidéos** listée en ~11 s (2 Mo de JSON) → délai fixé à 120 s. Playlist inexistante → 502 avec message clair ; lien noyé de paramètres (`music.youtube.com…&si=…&index=…`) → canonicalisé. - **Dans le navigateur réel** (clics réels, serveur branché sur le vrai yt-dlp) : playlist de 13 vidéos affichée (miniatures, durées) → ajout unitaire → « Tout ajouter » (file de 13, 13 titres distincts, **aucun doublon** avec la ligne déjà ajoutée, concurrence bornée respectée) ; **échec partiel simulé** (2 POST rejetés) → message « 2 vidéo(s) n'ont pas pu être ajoutée(s) », seules ces 2 lignes réactivées, relance → 13/13 sans doublon ; playlist inexistante → message sous le formulaire, champ conservé pour retenter ; `watch?v=…&list=…` → ajouté comme vidéo seule ; recherche de chaîne (non-régression : 60 résultats avec miniatures, sans bouton « Tout ajouter », pas de débordement de page) ; thèmes clair et sombre. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-19 14:56:12 +02:00
feat: playlists YouTube (parcourir, ajouter une par une ou d'un coup)
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
f0dec86b19
Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ
URL affiche toutes ses videos dans la meme zone de resultats que la
recherche de chaine ; chaque ligne a son bouton "Ajouter", et un bouton
"Tout ajouter a la file" ajoute le reste d'un coup.

Serveur :
- GET /api/playlist (PlaylistDiscoveryService) : yt-dlp --flat-playlist
  -J. -J plutot que -j : le titre de la playlist n'existe qu'a la racine,
  et une playlist vide reste un resultat valide.
- playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id>
  et reconstruit l'URL canonique depuis le seul identifiant valide (rien
  d'autre de l'entree n'atteint yt-dlp). Un lien watch?v=...&list=...
  reste une video (--no-playlist etait deja passe au telechargement).
- videoSummary.ts : conversion d'une entree --flat-playlist en resume de
  video, extraite de ChannelDiscoveryService pour etre partagee (chaine
  et playlist parsent desormais identiquement). ChannelVideoSummary
  devient VideoSummary (et ChannelVideoDto -> VideoDto cote client).

Client :
- Le champ URL accepte video ou playlist (isPlaylistUrl aiguille) ; zone
  de resultats partagee (createResultsView).
- "Tout ajouter" : POST /api/queue sequentiels (ordre conserve, pas de
  doublon avec les lignes deja ajoutees). Un echec n'interrompt pas le
  lot : nombre d'echecs + premier message affiches, seules les lignes en
  echec redeviennent ajoutables. Le mode est lu une seule fois pour tout
  le lot.
- Miniatures de resultats en loading="lazy" (une playlist peut compter
  des centaines de videos).

Verifie contre le vrai yt-dlp (2026.07.04) : forme reelle de -J relevee
(fixtures de test calquees dessus), playlist de 1607 videos listee en
~11 s (delai fixe a 120 s). Teste dans le navigateur reel : playlist de
13 videos, ajout unitaire, "tout ajouter" (13 elements, aucun doublon),
echec partiel simule + reprise, playlist inexistante, lien video avec
list=, recherche de chaine (non-regression).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit ad477fa035 into main 2026-09-19 15:49:37 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/youtubeDownloader#10
No description provided.