fix(fetch): map media:thumbnail so YouTube items get thumbnails
media:content-only mapping dropped media:thumbnail elements, but YouTube (and podcast) feeds carry their only image there — media:content is the video/audio itself. Map thumbnails as image/thumbnail media so the SPA's existing image pick renders them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuVbgSbyfRBkd4XkEDHV4B
This commit is contained in:
@@ -93,11 +93,9 @@ fn entry_to_feed_entry(entry: feed_rs::model::Entry) -> FeedEntry {
|
||||
let tags = entry.categories.into_iter().map(|c| c.term).collect();
|
||||
let published_at = entry.published.or(entry.updated);
|
||||
|
||||
let media = entry
|
||||
.media
|
||||
.into_iter()
|
||||
.flat_map(|m| m.content)
|
||||
.filter_map(|c| {
|
||||
let mut media = Vec::new();
|
||||
for m in entry.media {
|
||||
media.extend(m.content.into_iter().filter_map(|c| {
|
||||
c.url.map(|u| Media {
|
||||
kind: c
|
||||
.content_type
|
||||
@@ -107,8 +105,16 @@ fn entry_to_feed_entry(entry: feed_rs::model::Entry) -> FeedEntry {
|
||||
width: c.width,
|
||||
height: c.height,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}));
|
||||
// media:thumbnail is the only image YouTube (and many podcast) feeds carry —
|
||||
// their media:content is the video/audio itself.
|
||||
media.extend(m.thumbnails.into_iter().map(|t| Media {
|
||||
kind: "image/thumbnail".to_string(),
|
||||
url: t.image.uri,
|
||||
width: t.image.width,
|
||||
height: t.image.height,
|
||||
}));
|
||||
}
|
||||
|
||||
FeedEntry {
|
||||
external_id,
|
||||
@@ -363,6 +369,40 @@ mod tests {
|
||||
assert!(find_feed_link(html).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn youtube_media_thumbnail_becomes_image_media() {
|
||||
// Shape of a YouTube channel feed entry: the media:group's media:content is the
|
||||
// video itself; the only image is the media:thumbnail.
|
||||
let atom = r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:media="http://search.yahoo.com/mrss/">
|
||||
<title>channel</title>
|
||||
<entry>
|
||||
<id>yt:video:abc123</id>
|
||||
<title>a video</title>
|
||||
<link rel="alternate" href="https://www.youtube.com/watch?v=abc123"/>
|
||||
<media:group>
|
||||
<media:title>a video</media:title>
|
||||
<media:content url="https://www.youtube.com/v/abc123?version=3"
|
||||
type="application/x-shockwave-flash" width="640" height="390"/>
|
||||
<media:thumbnail url="https://i2.ytimg.com/vi/abc123/hqdefault.jpg"
|
||||
width="480" height="360"/>
|
||||
</media:group>
|
||||
</entry>
|
||||
</feed>"#;
|
||||
let feed = feed_rs::parser::parse(atom.as_bytes()).unwrap();
|
||||
let entry = feed.entries.into_iter().next().unwrap();
|
||||
let fe = entry_to_feed_entry(entry);
|
||||
let thumb = fe
|
||||
.media
|
||||
.iter()
|
||||
.find(|m| m.kind.starts_with("image"))
|
||||
.expect("thumbnail mapped as image media");
|
||||
assert_eq!(thumb.url, "https://i2.ytimg.com/vi/abc123/hqdefault.jpg");
|
||||
assert_eq!(thumb.width, Some(480));
|
||||
assert_eq!(thumb.height, Some(360));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_opml_with_nested_folders() {
|
||||
let opml = r#"<?xml version="1.0"?>
|
||||
|
||||
Reference in New Issue
Block a user