This document explains the setup for the three real podcast RSS feeds used in the POC.
The code now handles all three GUID formats correctly:
Test 1 & 2: Plain UUID
<guid isPermaLink="false">ec8b4655-cb61-4d69-87ee-dbdba77c0a62</guid>Test 3: CDATA-wrapped UUID
<guid isPermaLink="false"><![CDATA[ b47746b8-daa3-11f0-b08a-233a79f7667c ]]></guid>The normalizeGuid() function in adSegmentIdentity.ts automatically:
- Strips CDATA wrappers using regex:
/^<!\[CDATA\[([\s\S]*?)\]\]>$/ - Trims whitespace
- Returns normalized GUID or falls back to hash if empty/missing
Three fixture JSON files matching the episode GUIDs:
| Episode | GUID | Fixture File |
|---|---|---|
| What Bitcoin Did (Test 1) | ec8b4655-cb61-4d69-87ee-dbdba77c0a62 |
ec8b4655-cb61-4d69-87ee-dbdba77c0a62.json |
| TFTC (Test 2) | 2d4217bb-799b-4192-a559-348e82e2ea0e |
2d4217bb-799b-4192-a559-348e82e2ea0e.json |
| TIP (Test 3) | b47746b8-daa3-11f0-b08a-233a79f7667c |
b47746b8-daa3-11f0-b08a-233a79f7667c.json |
You've already placed the MP3 files in public/test-podcasts/:
public/test-podcasts/
├── AUDIO---DEFAULT---dcfdc6d6-489e-4ac7-b9a4-5629cc632395.mp3 (Test 1)
├── AUDIO---DEFAULT---34eb3a45-d135-453e-a790-58a744da35ba.mp3 (Test 2)
├── PPLLC1896786790.mp3 (Test 3)
├── MAPPING.md
└── README.md
These are accessible at:
http://localhost:3000/test-podcasts/AUDIO---DEFAULT---dcfdc6d6-489e-4ac7-b9a4-5629cc632395.mp3http://localhost:3000/test-podcasts/AUDIO---DEFAULT---34eb3a45-d135-453e-a790-58a744da35ba.mp3http://localhost:3000/test-podcasts/PPLLC1896786790.mp3
Added comprehensive tests for GUID normalization in adSegmentIdentity.test.ts:
test('strips CDATA wrapper from GUID', () => {
const input = {
guid: '<![CDATA[ b47746b8-daa3-11f0-b08a-233a79f7667c ]]>',
// ...
}
const key = generateEpisodeKey(input)
expect(key).toBe('b47746b8-daa3-11f0-b08a-233a79f7667c')
})
test('handles real-world test episode GUIDs', () => {
// Tests all 3 actual episodes
})- Feed: https://feeds.fountain.fm/UZSKQcrOnhqYS1JopxGg
- Episode: "Fiat Money, Inflation & the Collapse of Civilization | Saifedean Ammous"
- Duration: 4020 seconds (67 minutes)
- GUID:
ec8b4655-cb61-4d69-87ee-dbdba77c0a62(plain) - Chapters: YES -
<podcast:chapters>element present - AlternateEnclosures: NO
- MP3:
AUDIO---DEFAULT---dcfdc6d6-489e-4ac7-b9a4-5629cc632395.mp3
- Feed: https://feeds.fountain.fm/ZwwaDULvAj0yZvJ5kdB9/
- Episode: "#697: The Legal Fight Against UK Online Censorship with Preston Byrne"
- Duration: 5239 seconds (87 minutes)
- GUID:
2d4217bb-799b-4192-a559-348e82e2ea0e(plain) - Chapters: YES -
<podcast:chapters>element present - AlternateEnclosures: YES - video + ad-free versions (ignored per POC requirements)
- MP3:
AUDIO---DEFAULT---34eb3a45-d135-453e-a790-58a744da35ba.mp3
Note: This episode has multiple <podcast:chapters> elements:
- Primary audio chapters (DEFAULT)
- Ad-free audio chapters (in alternateEnclosure)
- Video chapters (in alternateEnclosures)
Per POC requirements, we only use the primary chapters (not inside alternateEnclosure).
- Feed: https://feeds.megaphone.fm/PPLLC8974708240
- Episode: "TECH009: Data Centers in Space, AI Education, Haptic Touch Robotics..."
- Duration: 4135 seconds (68 minutes)
- GUID:
b47746b8-daa3-11f0-b08a-233a79f7667c(CDATA wrapped) - Chapters: NO - no
<podcast:chapters>element - AlternateEnclosures: NO
- MP3:
PPLLC1896786790.mp3
Open each MP3 file and note the start/end times of any ad segments you find.
Use an audio player that shows timestamps (VLC, Audacity, or any podcast app).
For each ad you identify, update the startTime and endTime in the corresponding JSON file.
Example for Test 1:
If you find these ads in the What Bitcoin Did episode:
- Pre-roll: 0:00 - 0:30
- Mid-roll #1: 12:45 - 13:30
- Mid-roll #2: 35:20 - 36:05
- Post-roll: 66:15 - 66:45
Edit src/lib/fixtures/adSegments/ec8b4655-cb61-4d69-87ee-dbdba77c0a62.json:
{
"episodeKey": "ec8b4655-cb61-4d69-87ee-dbdba77c0a62",
"adSegments": [
{
"id": "ad-test1-001",
"startTime": 0, // 0:00 in seconds
"endTime": 30, // 0:30 in seconds
"description": "Pre-roll ad - What Bitcoin Did episode",
"taggerPubkey": "npub1tester1abc123"
},
{
"id": "ad-test1-002",
"startTime": 765, // 12:45 in seconds (12*60 + 45)
"endTime": 810, // 13:30 in seconds (13*60 + 30)
"description": "Mid-roll sponsor read #1",
"taggerPubkey": "npub1tester2def456"
},
{
"id": "ad-test1-003",
"startTime": 2120, // 35:20 in seconds (35*60 + 20)
"endTime": 2165, // 36:05 in seconds (36*60 + 5)
"description": "Mid-roll sponsor read #2",
"taggerPubkey": "npub1tester3ghi789"
},
{
"id": "ad-test1-004",
"startTime": 3975, // 66:15 in seconds (66*60 + 15)
"endTime": 4005, // 66:45 in seconds (66*60 + 45)
"description": "Post-roll ad",
"taggerPubkey": "npub1tester1abc123"
}
]
}Time Conversion Formula:
seconds = (minutes × 60) + seconds
Examples:
- 12:45 = (12 × 60) + 45 = 765 seconds
- 35:20 = (35 × 60) + 20 = 2120 seconds
Edit all three fixture files:
ec8b4655-cb61-4d69-87ee-dbdba77c0a62.json(Test 1)2d4217bb-799b-4192-a559-348e82e2ea0e.json(Test 2)b47746b8-daa3-11f0-b08a-233a79f7667c.json(Test 3)
Create a test script or check console logs:
import { generateEpisodeKey } from '~/services/adSegments/adSegmentIdentity'
// Test 1
const key1 = generateEpisodeKey({
guid: 'ec8b4655-cb61-4d69-87ee-dbdba77c0a62',
feedUrl: 'https://feeds.fountain.fm/UZSKQcrOnhqYS1JopxGg',
enclosureUrl: 'https://feeds.fountain.fm/.../AUDIO---DEFAULT---dcfdc6d6-489e-4ac7-b9a4-5629cc632395.mp3'
})
console.log(key1) // Should output: ec8b4655-cb61-4d69-87ee-dbdba77c0a62
// Test 3 (CDATA)
const key3 = generateEpisodeKey({
guid: '<![CDATA[ b47746b8-daa3-11f0-b08a-233a79f7667c ]]>',
feedUrl: 'https://feeds.megaphone.fm/PPLLC8974708240',
enclosureUrl: 'https://www.podtrac.com/pts/redirect.mp3/pdst.fm/e/traffic.megaphone.fm/PPLLC1896786790.mp3'
})
console.log(key3) // Should output: b47746b8-daa3-11f0-b08a-233a79f7667cnpm testShould pass all tests including the new CDATA normalization tests.
- Start dev server:
npm run dev - Load one of the test episodes in Podverse
- Open browser console
- Look for log message:
[AdSegments] Loaded X ad segments (merged to Y) for episode: <guid> - Verify the GUID matches the expected value
Check:
- Fixture filename matches episode GUID exactly
- JSON file is valid (use a JSON validator)
- File path uses forward slashes
/not backslashes\ - Episode GUID is being normalized correctly (check console logs)
Debug:
// In browser console:
import { generateEpisodeKey } from '~/services/adSegments/adSegmentIdentity'
const nowPlayingItem = OmniAural.state.player.currentNowPlayingItem.value()
const key = generateEpisodeKey({
guid: nowPlayingItem.episodeGuid,
feedUrl: nowPlayingItem.podcastFeedUrl,
enclosureUrl: nowPlayingItem.episodeMediaUrl
})
console.log('Generated episode key:', key)Check:
- The regex pattern in
normalizeGuid()is correct - GUID string is exactly as it appears in RSS feed
- No extra characters or encoding issues
Test:
const testGuid = '<![CDATA[ b47746b8-daa3-11f0-b08a-233a79f7667c ]]>'
const normalized = normalizeGuid(testGuid)
console.log(normalized) // Should be: b47746b8-daa3-11f0-b08a-233a79f7667cCheck:
- Episode is loading from the correct feed URL
- GUID in episode data matches fixture filename
- No typos in GUID (easy to mix up similar characters)
- MAPPING.md - Quick reference table
- README.md - Test MP3 instructions
- PODCAST_AD_BLOCKING_POC.md - Main POC documentation
- NOSTR_INTEGRATION.md - Future integration plan
- GUID normalization implemented (handles CDATA)
- Three fixture files created with correct episode GUIDs
- Unit tests added for CDATA stripping
- MP3 files placed in public/test-podcasts/
- Mapping documentation created
- TODO: Edit ad timecodes in fixture files (after listening to MP3s)
- TODO: Test loading episodes in browser
- TODO: Verify auto-skip behavior
Once you've edited the ad timecodes, the POC will be ready to test! 🎉