Pin RSS <title> as the only source of an episode title
Confirmed byte-for-byte against a live feed, and tested against a fixture whose itunes:title differs: the RSS title wins, and season/episode stay metadata rather than being folded into the displayed name. An item with a season but no episode number keeps a null episode instead of inventing one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
18
PROGRESS.md
18
PROGRESS.md
@@ -56,6 +56,24 @@ and until now nothing set them.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-09-10 — Titles: RSS `<title>` is the only source
|
||||||
|
|
||||||
|
Confirmed against the live feed rather than assumed: 131 feed items, 131 stored, **0 mismatches** —
|
||||||
|
entry titles are byte-for-byte what the RSS `<title>` publishes, separators included. `feed.rs` never
|
||||||
|
consults `itunes:title`, and season/episode live in their own columns, rendered as chips rather than
|
||||||
|
folded into the title.
|
||||||
|
|
||||||
|
Pinned with a test: a fixture whose `itunes:title` differs from its `<title>` must still yield the
|
||||||
|
RSS title, and an item with `itunes:season` but no `itunes:episode` keeps a null episode instead of
|
||||||
|
inventing one.
|
||||||
|
|
||||||
|
That null-episode case is real in the wild — this feed's `Music from a Darkened Room | Session Zero`
|
||||||
|
carries S8 with no episode number, while Parts 1-5 get E1-E5, and it drops the "Part N" convention
|
||||||
|
its siblings use. The arc is 11 items: Session Zero plus five parts, each part being a main episode
|
||||||
|
and its shorter `Junk in the Trunk` companion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-09-10 — Phase 3: full-featured UI
|
## 2026-09-10 — Phase 3: full-featured UI
|
||||||
|
|
||||||
Rewrite of `web/index.html` (~714 lines) plus the backend it needed.
|
Rewrite of `web/index.html` (~714 lines) plus the backend it needed.
|
||||||
|
|||||||
40
src/feed.rs
40
src/feed.rs
@@ -351,6 +351,46 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_rss_title_always_wins_and_episode_numbers_stay_metadata() {
|
||||||
|
// Some feeds set a different itunes:title. The displayed title is always the RSS
|
||||||
|
// <title>, verbatim -- separators and all -- and season/episode are stored
|
||||||
|
// alongside it rather than folded into it.
|
||||||
|
let xml = br#"<?xml version="1.0"?>
|
||||||
|
<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
|
||||||
|
<channel><title>Show</title><link>https://x</link><description>d</description>
|
||||||
|
<item>
|
||||||
|
<title>Music from a Darkened Room | Session Zero</title>
|
||||||
|
<itunes:title>Session Zero</itunes:title>
|
||||||
|
<guid>sz</guid>
|
||||||
|
<itunes:season>8</itunes:season>
|
||||||
|
<itunes:duration>6720</itunes:duration>
|
||||||
|
<enclosure url="https://x/sz.mp3" length="1" type="audio/mpeg"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>Music from a Darkened Room Part 1 | Murphy's Drawer</title>
|
||||||
|
<guid>p1</guid>
|
||||||
|
<itunes:season>8</itunes:season><itunes:episode>1</itunes:episode>
|
||||||
|
<enclosure url="https://x/p1.mp3" length="1" type="audio/mpeg"/>
|
||||||
|
</item>
|
||||||
|
</channel></rss>"#;
|
||||||
|
let feed = parse(xml).unwrap();
|
||||||
|
|
||||||
|
let sz = &feed.entries[0];
|
||||||
|
assert_eq!(
|
||||||
|
sz.title.as_deref(),
|
||||||
|
Some("Music from a Darkened Room | Session Zero"),
|
||||||
|
"itunes:title must not override the RSS title"
|
||||||
|
);
|
||||||
|
assert_eq!(sz.season, Some(8));
|
||||||
|
assert_eq!(sz.episode, None, "a missing episode number stays missing");
|
||||||
|
assert_eq!(sz.duration, Some(6720));
|
||||||
|
|
||||||
|
let p1 = &feed.entries[1];
|
||||||
|
assert_eq!(p1.title.as_deref(), Some("Music from a Darkened Room Part 1 | Murphy's Drawer"));
|
||||||
|
assert_eq!((p1.season, p1.episode), (Some(8), Some(1)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn durations_parse_from_seconds_or_a_clock() {
|
fn durations_parse_from_seconds_or_a_clock() {
|
||||||
assert_eq!(parse_duration("5649"), Some(5649));
|
assert_eq!(parse_duration("5649"), Some(5649));
|
||||||
|
|||||||
Reference in New Issue
Block a user