Vu en prod : « BEIN SPORTS 1 FR ULTRA FHD » (550403) est rangée en
« |AR| BEIN SPORTS 4K », hors abonnement ; le panneau la refuse en
HTTP 407. classify ne lisait la catégorie qu'en dernier recours : le
« FR » du nom la classait française, et à qualité égale elle passait
devant la vraie FR - BEIN SPORTS 1 FHD (554021). Toutes les autres
beIN 1 répondent 200.
- classify : le préfixe pays de la catégorie passe avant les mots du
nom (ordre : préfixe du nom, préfixe de catégorie, mots du nom, mots
de catégorie).
- La table des chaînes de l'EPG garde la catégorie de chaque flux
(get_live_categories, une requête par chargement de table) ;
channel_table remplace channel_names.
- channel_labels ignore un flux choisi reconnu comme beIN étrangère :
les règles existantes visant 550403 basculent seules sur la FR, et
les enregistrements programmés sont recréés à la réconciliation.
- /api/auto-rules (création, liste des chaînes) lit la table en cache
au lieu de retélécharger 7 Mo à chaque ouverture des réglages ;
fetch_live_table disparaît.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Un match NBA a échoué après 30 refus du fournisseur en 13 min, alors
qu'il restait 3 h 20 de diffusion. Le plafond MAX_FFMPEG_ATTEMPTS
condamnait tout l'enregistrement sur un refus temporaire : on relance
désormais tant que la fenêtre est ouverte, au rythme de
ffmpeg_retry_delay (30 s au plus).
- direct_proxy journalise le code HTTP du panneau : FFmpeg n'affiche
que « 4XX », le code exact (ici 407) n'était visible nulle part.
- direct_proxy libère les connexions de lecture orphelines du compte
avant d'ouvrir le flux, comme turbo.ts et HLS. Un flux encore regardé
n'est jamais coupé.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cargo fmt --check refusait le bloc de closure : replié sur une ligne,
l'appel dépassait 100 colonnes, et rustfmt casse alors devant .retain
au lieu de garder les accolades. Sortie de rustfmt appliquée telle
quelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les enregistrements, season passes et la règle Auto NBA s'affichaient
encore sous les deux IPTV. Lecture et écriture ne résolvaient pas la
playlist de la même façon : la création passait par get_playlist, qui
se replie sur la playlist la plus récente, alors que les listes
passaient par selected_playlist_id, qui exigeait un choix explicite du
client et ne filtrait rien du tout sans lui. En-tête X-Playlist-Id
absent ou non recevable, tout ressortait mélangé — et pour Auto NBA le
repli rendait toutes les règles du compte.
- selected_playlist_id devient scoped_playlist_id et résout via
get_playlist, exactement comme à l'écriture ; None signifie
désormais « compte sans playlist », plus « ne filtre pas ».
- Listes des enregistrements et des season passes : égalité stricte sur
playlist_id au lieu de is_none_or, qui gardait les lignes sans
playlist sous chaque IPTV. Les rattacher est le travail de
attach_legacy_rows_to_playlists, rejoué à chaque ouverture de la base,
donc aucune ligne ne devient invisible.
- Les trois chemins de création (enregistrement, season pass, règle)
utilisent le même helper au lieu de recopier la résolution.
Non compilé localement : ni cargo ni Docker sur cette machine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le serveur ignorait l'IPTV choisie : direct, VOD, guide, enregistrements,
season passes et Auto NBA passaient tous par la playlist la plus récente
(ou celle du premier utilisateur pour la capture FFmpeg), et les
enregistrements s'affichaient sous toutes les playlists.
- get_playlist respecte ?playlist=<id> (prioritaire) puis l'en-tête
X-Playlist-Id, avec contrôle du propriétaire (admin : toutes).
- Sessions FFmpeg HLS/VOD séparées par IPTV (même stream_id possible) ;
le m3u8 propage ?playlist= aux segments, redirections et bascule
.ts → HLS du lecteur web aussi.
- tv_recordings, season_passes, auto_rules : colonne playlist_id
(migrations v4-v6) ; lignes existantes rattachées à la playlist la
plus récente de leur propriétaire ; URL de capture avec ?playlist= ;
listes filtrées par IPTV ; une règle NBA par IPTV.
- Proxy /api/xtream : cache d'allowlist par playlist, paramètre retiré
avant l'appel au panneau ; CORS autorise X-Playlist-Id.
- Client : AuthedHttp envoie X-Playlist-Id ; XtreamService ajoute
?playlist= aux flux, à l'EPG et aux appels xtream-api (cache distinct).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L'onglet restait vide alors que le guide beIN annonçait des matchs : une
diffusion n'était prise en compte que si sa chaîne beIN était reliée à
un flux de la règle (liste vide si la détection avait échoué à la
création, ou nom différent chez le revendeur).
- Toute diffusion en direct du guide beIN est rapprochée et affichée ;
seul l'enregistrement exige un flux IPTV (sinon « absente de la
playlist », bouton inactif, aucun enregistrement créé).
- Les beIN FR de la playlist sont redétectées à chaque passage et
complètent les flux choisis dans la règle.
- Plus besoin de playlist pour afficher le programme.
- Diagnostic par règle (source, directs au guide, chaînes reliées ou
absentes, matchs rapprochés) renvoyé par l'API et affiché quand la
liste est vide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Programme officiel beIN Sports France (api/opta, ~8 jours) comme
source : chaîne exacte et direct/rediffusion explicites, recoupés avec
le coup d'envoi ESPN (un faux « direct » du guide est écarté). Repli sur
l'EPG IPTV si le site est injoignable. Seuls les matchs annoncés en
direct sur beIN sont affichés.
- Plus aucun enregistrement automatique : seul un « Enregistrer »
explicite programme un match ; les programmations sans choix sont
annulées.
- Un seul flux IPTV : un match qui chevauche un match choisi ou un autre
enregistrement est refusé (409) et affiché « Créneau pris » ; la liste
est relue après chaque choix et les boutons sont figés pendant la
requête.
- Interrupteurs lisibles (curseur et piste distincts), boutons
Enregistrer / Choisi à la place des interrupteurs par match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Une règle désactivée ne programme plus rien : la réconciliation relit
l'état en base et, en pause, supprime les enregistrements encore
planifiés (un enregistrement en cours n'est pas coupé).
- Index unique (user_id, kind) : deux activations concurrentes ne créent
plus deux règles NBA ; le conflit répond 409.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le calendrier NBA (API ESPN, 21 jours) annonce les matchs à l'avance ;
l'EPG des beIN Sports françaises confirme la chaîne ~48 h avant. Seule la
diffusion qui démarre avec le coup d'envoi est retenue : les rediffusions
sont ignorées, sans dépendre des titres. Le CDN nba.com refuse les
requêtes serveur (403), d'où ESPN.
- Serveur : tables auto_rules / nba_games (migration v3 : source_rule_id),
réconciliation toutes les 30 min, marges, filtre d'équipes, conflits de
créneaux, rétention, API /api/auto-rules (oui/non par match).
- Détection des beIN FR (hors MENA, TR, ES…), meilleure qualité par chaîne.
- XMLTV : sous-titre, catégories, drapeaux live/new/premiere/
previously-shown, horizon EPG_HORIZON_HOURS.
- Plex/Jellyfin : vignette et affiche avec logos des équipes, .nfo,
pochette intégrée au MKV, nom de fichier lisible.
- UI : onglet « Auto NBA » et pastille sur les enregistrements.
- Docker : fonts-dejavu-core (texte des vignettes) et tzdata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Le serveur Dart (bin/) est remplacé par un serveur Rust (server/) :
tokio + axum, reqwest/rustls, SQLite embarqué via rusqlite. Même API,
mêmes routes et mêmes réponses : le front Flutter ne change pas.
La base de production est reprise telle quelle : schéma, migrations et
formats de dates (DateTime.toIso8601String de Dart) sont reproduits à
l'identique, les sessions et hachages existants restent valides.
Comportement conservé : cascade de routage, anti-SSRF, logs masqués,
rate limits, transcodage HLS/turbo.ts avec repli GPU, quotas fournisseur,
enregistrements (reprise, relances, fusion), season passes, EPG, logos de
repli, arrêt gracieux sur SIGTERM. Tests Dart portés (132 tests Rust) ;
la CI exécute fmt, test et clippy -D warnings, l'image Docker compile le
serveur dans un étage rust:1.98.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>