Enregistrements : auto-enregistrement des matchs NBA en direct sur beIN Sports FR

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>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-18 11:31:23 +02:00
1 parent 530bf82b04
commit f291b66087
20 files changed
+4486 -32

No files matched your search

+67
View File
@@ -89,6 +89,8 @@ pub struct Recording {
pub status: String,
pub file_path: Option<String>,
pub error_reason: Option<String>,
/// Règle automatique à l'origine de l'enregistrement (None = manuel/season pass).
pub source_rule_id: Option<String>,
pub created_at: DartDateTime,
pub updated_at: DartDateTime,
}
@@ -106,6 +108,7 @@ impl Recording {
map.insert("status".into(), json!(self.status));
map.insert("file_path".into(), json!(self.file_path));
map.insert("error_reason".into(), json!(self.error_reason));
map.insert("source_rule_id".into(), json!(self.source_rule_id));
map.insert("created_at".into(), json!(self.created_at.to_iso()));
map.insert("updated_at".into(), json!(self.updated_at.to_iso()));
map
@@ -119,3 +122,67 @@ impl Recording {
self.end_time.to_utc()
}
}
/// Règle d'enregistrement automatique (aujourd'hui : NBA sur beIN Sports FR).
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct AutoRule {
pub id: String,
pub user_id: String,
/// Type de règle ; seul 'nba' existe pour l'instant.
pub kind: String,
pub enabled: bool,
/// 'auto' : programmé sans validation (veto possible) ; 'ask' : attend un oui.
pub default_decision: String,
/// stream_ids des chaînes surveillées (beIN Sports FR retenus).
pub channel_ids: Vec<String>,
pub pad_before_min: i64,
pub pad_after_min: i64,
/// Noms d'équipes à suivre ; vide = toutes.
pub team_filter: Vec<String>,
/// Rétention en jours des fichiers créés par la règle ; None = illimitée.
pub keep_days: Option<i64>,
pub created_at: String,
pub updated_at: String,
}
/// Un match NBA connu d'une règle, de l'annonce au calendrier jusqu'au fichier.
#[derive(Clone, Debug, serde::Serialize, PartialEq)]
pub struct NbaGame {
pub game_id: String,
pub rule_id: String,
pub home: String,
pub away: String,
pub home_id: String,
pub away_id: String,
pub home_tri: String,
pub away_tri: String,
/// Coup d'envoi officiel, ISO UTC.
pub tipoff_utc: String,
/// provisional | confirmed | scheduled | recorded | missed | not_on_bein
pub status: String,
/// auto | accepted | refused
pub decision: String,
pub channel_id: Option<String>,
pub channel_name: Option<String>,
pub epg_title: Option<String>,
pub epg_start: Option<String>,
pub epg_end: Option<String>,
pub recording_id: Option<String>,
pub confidence: Option<i64>,
/// Pas de créneau d'enregistrement libre (MAX_CONCURRENT_RECORDINGS).
pub conflict: bool,
pub updated_at: String,
}
/// Match tel que publié par le calendrier officiel (avant toute décision).
#[derive(Clone, Debug, PartialEq)]
pub struct ScheduledGame {
pub game_id: String,
pub home: String,
pub away: String,
pub home_id: String,
pub away_id: String,
pub home_tri: String,
pub away_tri: String,
pub tipoff_utc: chrono::DateTime<chrono::Utc>,
}