add domain concepts: FillStrategy variants, Chapter, InterstitialRule, MidRollRule, gap_filler
- FillStrategy: +Alternating (round-robin series), +Weighted (fresh-first), +Marathon (always ep1, loops) - Chapter value object on MediaItem (JSON column in library_items) - InterstitialRule + MidRollRule on ProgrammingBlock (data model only) - gap_filler: Option<MediaFilter> on Channel (persisted as JSON column) - migration: 20260712000002_add_chapters_and_gap_filler.sql
This commit is contained in:
@@ -28,6 +28,9 @@ pub enum FillStrategy {
|
||||
BestFit,
|
||||
Sequential,
|
||||
Random,
|
||||
Alternating,
|
||||
Weighted,
|
||||
Marathon,
|
||||
}
|
||||
|
||||
const DEFAULT_COOLDOWN_DAYS: u32 = 30;
|
||||
@@ -99,6 +102,75 @@ pub enum MediaRole {
|
||||
Interstitial,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InterstitialRule {
|
||||
pool_filter: MediaFilter,
|
||||
strategy: FillStrategy,
|
||||
min_gap_secs: u32,
|
||||
}
|
||||
|
||||
impl InterstitialRule {
|
||||
pub fn new(pool_filter: MediaFilter, strategy: FillStrategy, min_gap_secs: u32) -> Self {
|
||||
Self {
|
||||
pool_filter,
|
||||
strategy,
|
||||
min_gap_secs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pool_filter(&self) -> &MediaFilter {
|
||||
&self.pool_filter
|
||||
}
|
||||
|
||||
pub fn strategy(&self) -> &FillStrategy {
|
||||
&self.strategy
|
||||
}
|
||||
|
||||
pub fn min_gap_secs(&self) -> u32 {
|
||||
self.min_gap_secs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MidRollRule {
|
||||
prefer_chapters: bool,
|
||||
fallback_interval_mins: u32,
|
||||
break_duration_secs: u32,
|
||||
pool_filter: MediaFilter,
|
||||
}
|
||||
|
||||
impl MidRollRule {
|
||||
pub fn new(
|
||||
prefer_chapters: bool,
|
||||
fallback_interval_mins: u32,
|
||||
break_duration_secs: u32,
|
||||
pool_filter: MediaFilter,
|
||||
) -> Self {
|
||||
Self {
|
||||
prefer_chapters,
|
||||
fallback_interval_mins,
|
||||
break_duration_secs,
|
||||
pool_filter,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefer_chapters(&self) -> bool {
|
||||
self.prefer_chapters
|
||||
}
|
||||
|
||||
pub fn fallback_interval_mins(&self) -> u32 {
|
||||
self.fallback_interval_mins
|
||||
}
|
||||
|
||||
pub fn break_duration_secs(&self) -> u32 {
|
||||
self.break_duration_secs
|
||||
}
|
||||
|
||||
pub fn pool_filter(&self) -> &MediaFilter {
|
||||
&self.pool_filter
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/scheduling.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -6,3 +6,40 @@ pub enum SourceUri {
|
||||
NetworkUrl { url: String },
|
||||
FilePath { path: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Chapter {
|
||||
title: Option<String>,
|
||||
start_secs: f64,
|
||||
end_secs: f64,
|
||||
}
|
||||
|
||||
impl Chapter {
|
||||
pub fn new(title: Option<String>, start_secs: f64, end_secs: f64) -> Self {
|
||||
Self {
|
||||
title,
|
||||
start_secs,
|
||||
end_secs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_persistence(title: Option<String>, start_secs: f64, end_secs: f64) -> Self {
|
||||
Self {
|
||||
title,
|
||||
start_secs,
|
||||
end_secs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> Option<&str> {
|
||||
self.title.as_deref()
|
||||
}
|
||||
|
||||
pub fn start_secs(&self) -> f64 {
|
||||
self.start_secs
|
||||
}
|
||||
|
||||
pub fn end_secs(&self) -> f64 {
|
||||
self.end_secs
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user