feat: display video publication dates
This commit is contained in:
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user