use std::fmt;
use crate::error::{Error, Result};
#[derive(Clone, Debug, PartialEq)]
pub enum PartialRange {
TimeRange {
start: f64,
end: f64,
},
ChapterRange {
start: usize,
end: usize,
},
SingleChapter {
index: usize,
},
}
impl PartialRange {
pub fn time_range(start: f64, end: f64) -> Result<Self> {
tracing::debug!(start = start, end = end, "⚙️ Creating time range for partial download");
if start < 0.0 || start >= end {
return Err(Error::invalid_partial_range(format!(
"start={start} must be non-negative and less than end={end}"
)));
}
Ok(Self::TimeRange { start, end })
}
pub fn chapter_range(start: usize, end: usize) -> Result<Self> {
tracing::debug!(
start = start,
end = end,
"⚙️ Creating chapter range for partial download"
);
if start > end {
return Err(Error::invalid_partial_range(format!(
"chapter start={start} must be <= end={end}"
)));
}
Ok(Self::ChapterRange { start, end })
}
pub fn single_chapter(index: usize) -> Self {
Self::SingleChapter { index }
}
pub fn to_ytdlp_arg(&self) -> String {
tracing::debug!(range = %self, "⚙️ Converting partial range to yt-dlp argument");
let result = match self {
Self::TimeRange { start, end } => {
format!("*{}-{}", format_time(*start), format_time(*end))
}
Self::ChapterRange { start, end } => {
format!("chapters:{}-{}", start, end)
}
Self::SingleChapter { index } => {
format!("chapters:{}-{}", index, index)
}
};
tracing::debug!(range = %self, ytdlp_arg = %result, "✅ Converted partial range to yt-dlp argument");
result
}
pub fn needs_chapter_metadata(&self) -> bool {
matches!(self, Self::ChapterRange { .. } | Self::SingleChapter { .. })
}
pub fn to_time_range(&self, chapters: &[crate::model::chapter::Chapter]) -> Option<Self> {
tracing::debug!(range = %self, chapter_count = chapters.len(), "⚙️ Converting partial range to time range using chapter metadata");
match self {
Self::TimeRange { .. } => Some(self.clone()),
Self::ChapterRange { start, end } => {
if *end >= chapters.len() {
tracing::warn!(
start = start,
end = end,
chapter_count = chapters.len(),
"⚙️ Chapter range end index out of bounds"
);
return None;
}
let start_time = chapters[*start].start_time;
let end_time = chapters[*end].end_time;
tracing::debug!(
start_chapter = start,
end_chapter = end,
start_time = start_time,
end_time = end_time,
"✅ Converted chapter range to time range"
);
Some(Self::TimeRange {
start: start_time,
end: end_time,
})
}
Self::SingleChapter { index } => {
if *index >= chapters.len() {
tracing::warn!(
index = index,
chapter_count = chapters.len(),
"⚙️ Single chapter index out of bounds"
);
return None;
}
let start_time = chapters[*index].start_time;
let end_time = chapters[*index].end_time;
tracing::debug!(
chapter_index = index,
start_time = start_time,
end_time = end_time,
"✅ Converted single chapter to time range"
);
Some(Self::TimeRange {
start: start_time,
end: end_time,
})
}
}
}
pub fn get_times(&self) -> Option<(f64, f64)> {
match self {
Self::TimeRange { start, end } => Some((*start, *end)),
_ => None,
}
}
}
impl fmt::Display for PartialRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TimeRange { start, end } => {
write!(f, "TimeRange(start={}, end={})", format_time(*start), format_time(*end))
}
Self::ChapterRange { start, end } => {
write!(f, "ChapterRange(start={}, end={})", start, end)
}
Self::SingleChapter { index } => {
write!(f, "SingleChapter(index={})", index)
}
}
}
}
fn format_time(seconds: f64) -> String {
let hours = (seconds / 3600.0) as u64;
let minutes = ((seconds % 3600.0) / 60.0) as u64;
let secs = seconds % 60.0;
format!("{:02}:{:02}:{:06.3}", hours, minutes, secs)
}