feat: display video publication dates

This commit is contained in:
2026-10-10 17:35:02 +02:00
parent a4db9808dc
commit aa2f61a8d5
4 changed files with 64 additions and 3 deletions
Generated
+1
View File
@@ -1311,6 +1311,7 @@ dependencies = [
"ratatui-image",
"serde",
"serde_json",
"time",
"toml_edit",
]
+1
View File
@@ -12,6 +12,7 @@ ratatui-image = { version = "11.1.0", default-features = false, features = ["cro
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
toml_edit = "0.25.17"
time = "0.3.55"
[target."cfg(unix)".dependencies]
libc = "0.2.190"
+12 -3
View File
File diff suppressed because one or more lines are too long
+50
View File
@@ -24,6 +24,9 @@ pub struct Video {
pub channel_id: Option<String>,
pub uploader_id: Option<String>,
pub timestamp: Option<i64>,
/// Cached YYYY-MM-DD label; a leading ~ marks an estimated UTC date.
#[serde(default, rename = "upload_date")]
pub publication_date: Option<String>,
// Search descriptions are truncated; only full video metadata populates this cache.
#[serde(skip)]
pub description: Option<String>,
@@ -89,6 +92,24 @@ fn validate_video(video: &mut Video) -> Result<(), String> {
if let Some(channel) = &mut video.channel {
channel.retain(|c| !c.is_control());
}
let exact_date = video.publication_date.as_deref().and_then(|date| {
if date.len() != 8 || !date.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
time::Date::from_calendar_date(
date[..4].parse().ok()?,
date[4..6].parse::<u8>().ok()?.try_into().ok()?,
date[6..].parse().ok()?,
)
.ok()
});
video.publication_date = exact_date.map(|date| date.to_string()).or_else(|| {
video.timestamp.and_then(|timestamp| {
time::OffsetDateTime::from_unix_timestamp(timestamp)
.ok()
.map(|date| format!("~{}", date.date()))
})
});
Ok(())
}
@@ -188,6 +209,8 @@ impl Job {
&start.to_string(),
"--playlist-end",
&end.to_string(),
"--extractor-args",
"youtubetab:approximate_date",
"--dump-single-json",
&format!("ytsearchall:{query}"),
]))
@@ -524,6 +547,33 @@ impl Drop for Playback {
mod tests {
use super::*;
#[test]
fn publication_dates_prefer_exact_metadata_and_handle_utc_boundaries_and_invalid_dates() {
for parse in [search_results, feed_results] {
for (upload_date, timestamp, expected) in [
(Some("20240229"), Some(0), Some("2024-02-29")),
(Some("20000229"), None, Some("2000-02-29")),
(Some("19000229"), None, None),
(Some("20230229"), Some(0), Some("~1970-01-01")),
(Some("20241301"), None, None),
(Some("日本語"), None, None),
(None, Some(-1), Some("~1969-12-31")),
(None, Some(1_709_251_199), Some("~2024-02-29")),
(None, Some(1_709_251_200), Some("~2024-03-01")),
(None, Some(i64::MAX), None),
(None, None, None),
] {
let output = serde_json::json!({"entries":[{
"id": "jNQXAC9IVRw", "title": "Video",
"upload_date": upload_date, "timestamp": timestamp,
}]})
.to_string();
let page = parse(&output).unwrap();
assert_eq!(page.videos[0].publication_date.as_deref(), expected);
}
}
}
#[test]
fn thumbnails_bound_binary_input_and_reject_unsafe_ids() {
let bytes = [0xff, 0xd8, 0x00, 0xfe];