Skip to main content

rama_http/protocols/rss/feed_ext/
podcast.rs

1//! Podcasting 2.0 namespace extension (<https://podcastindex.org/namespace/1.0>).
2
3use std::time::Duration;
4
5use jiff::Timestamp;
6use rama_net::uri::Uri;
7
8/// A `podcast:transcript` element.
9#[derive(Debug, Clone, PartialEq)]
10pub struct PodcastTranscript {
11    pub url: String,
12    pub type_: String,
13    pub language: Option<String>,
14    pub rel: Option<String>,
15}
16
17/// A `podcast:alternateEnclosure` element.
18#[derive(Debug, Clone, PartialEq)]
19pub struct PodcastAlternateEnclosure {
20    pub type_: String,
21    pub length: Option<u64>,
22    pub bitrate: Option<f64>,
23    pub height: Option<u64>,
24    pub lang: Option<String>,
25    /// Podcasting 2.0 limits this to 32 characters; the lenient parser and
26    /// writer preserve caller input rather than enforcing it.
27    pub title: Option<String>,
28    /// Podcasting 2.0 limits this to 32 characters; the lenient parser and
29    /// writer preserve caller input rather than enforcing it.
30    pub rel: Option<String>,
31    pub codecs: Option<String>,
32    /// Whether this alternate is the default rendition. The writer emits the
33    /// attribute only when true; callers are responsible for keeping at most
34    /// one default alternate per item.
35    pub default: bool,
36    pub sources: Vec<PodcastSource>,
37    pub integrity: Option<PodcastIntegrity>,
38}
39
40/// A `podcast:source` child of `podcast:alternateEnclosure`.
41#[derive(Debug, Clone, PartialEq)]
42pub struct PodcastSource {
43    pub uri: Uri,
44    pub content_type: Option<String>,
45}
46
47/// A `podcast:integrity` child of `podcast:alternateEnclosure`.
48#[derive(Debug, Clone, PartialEq)]
49pub struct PodcastIntegrity {
50    pub type_: String,
51    pub value: String,
52}
53
54/// A `podcast:chapters` reference.
55#[derive(Debug, Clone, PartialEq)]
56pub struct PodcastChapters {
57    pub url: String,
58    pub type_: String,
59}
60
61/// A `podcast:soundbite` element. Podcasting 2.0 spec uses decimal seconds
62/// for both start and duration (sub-second precision is meaningful).
63#[derive(Debug, Clone, PartialEq)]
64pub struct PodcastSoundbite {
65    pub start_time: Duration,
66    pub duration: Duration,
67    pub title: Option<String>,
68}
69
70/// A `podcast:person` element (used at both item and feed level).
71#[derive(Debug, Clone, PartialEq)]
72pub struct PodcastPerson {
73    pub name: String,
74    pub role: Option<String>,
75    pub group: Option<String>,
76    pub img: Option<String>,
77    pub href: Option<String>,
78}
79
80/// A `podcast:location` element.
81#[derive(Debug, Clone, PartialEq)]
82pub struct PodcastLocation {
83    pub name: String,
84    pub geo: Option<String>,
85    pub osm: Option<String>,
86}
87
88/// A `podcast:season` element.
89#[derive(Debug, Clone, PartialEq)]
90pub struct PodcastSeason {
91    pub number: u64,
92    pub name: Option<String>,
93}
94
95/// A `podcast:episode` element.
96#[derive(Debug, Clone, PartialEq)]
97pub struct PodcastEpisode {
98    pub number: f64,
99    pub display: Option<String>,
100}
101
102/// A `podcast:funding` element (feed-level).
103#[derive(Debug, Clone, PartialEq)]
104pub struct PodcastFunding {
105    pub url: String,
106    pub title: Option<String>,
107}
108
109/// A `podcast:trailer` element (feed-level).
110#[derive(Debug, Clone, PartialEq)]
111pub struct PodcastTrailer {
112    pub title: String,
113    pub url: String,
114    pub pub_date: Option<Timestamp>,
115    pub length: Option<u64>,
116    pub type_: Option<String>,
117    pub season: Option<u64>,
118}
119
120/// A `podcast:remoteItem` element (feed-level).
121#[derive(Debug, Clone, PartialEq)]
122pub struct PodcastRemoteItem {
123    pub feed_guid: String,
124    pub item_guid: Option<String>,
125    pub feed_url: Option<String>,
126    pub title: Option<String>,
127    pub medium: Option<String>,
128}
129
130/// Podcasting 2.0 extension fields for a single episode item.
131#[derive(Debug, Clone, Default, PartialEq)]
132pub struct Podcast {
133    pub transcripts: Vec<PodcastTranscript>,
134    pub alternate_enclosures: Vec<PodcastAlternateEnclosure>,
135    pub chapters: Option<PodcastChapters>,
136    pub soundbites: Vec<PodcastSoundbite>,
137    pub persons: Vec<PodcastPerson>,
138    pub location: Option<PodcastLocation>,
139    pub season: Option<PodcastSeason>,
140    pub episode: Option<PodcastEpisode>,
141    /// `<podcast:remoteItem>` inside `<item>` — points the host episode at
142    /// another feed's item (used for cross-feed value-split or inter-publisher
143    /// references in Podcasting 2.0).
144    pub remote_items: Vec<PodcastRemoteItem>,
145}
146
147/// Podcasting 2.0 extension fields at the feed (channel) level.
148#[derive(Debug, Clone, Default, PartialEq)]
149pub struct PodcastFeed {
150    pub guid: Option<String>,
151    /// `<podcast:locked>` truthy content (yes/no → true/false).
152    pub locked: Option<bool>,
153    /// `<podcast:locked owner="...">` attribute — the email of the host
154    /// authorised to approve a feed-import request. Optional per spec;
155    /// preserved on round-trip when present.
156    pub locked_owner: Option<String>,
157    pub fundings: Vec<PodcastFunding>,
158    pub persons: Vec<PodcastPerson>,
159    pub location: Option<PodcastLocation>,
160    pub trailers: Vec<PodcastTrailer>,
161    pub license: Option<String>,
162    pub medium: Option<String>,
163    pub remote_items: Vec<PodcastRemoteItem>,
164}