Skip to main content

crispy_iptv_types/
epg.rs

1//! Protocol-agnostic EPG (Electronic Programme Guide) types.
2//!
3//! Mirrors the XMLTV DTD as defined by `@iptv/xmltv` while remaining
4//! usable by Xtream short-EPG and Stalker EPG responses.
5
6use serde::{Deserialize, Serialize};
7use smallvec::SmallVec;
8
9/// A single EPG programme entry.
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct EpgProgramme {
12    /// Channel ID this programme belongs to (XMLTV channel@id).
13    pub channel: String,
14
15    /// Programme start time (UTC timestamp).
16    pub start: Option<i64>,
17
18    /// Programme stop time (UTC timestamp).
19    pub stop: Option<i64>,
20
21    /// Programme title(s), potentially multilingual.
22    #[serde(default)]
23    pub title: SmallVec<[EpgStringWithLang; 1]>,
24
25    /// Subtitle(s).
26    #[serde(default)]
27    pub sub_title: SmallVec<[EpgStringWithLang; 1]>,
28
29    /// Description(s).
30    #[serde(default)]
31    pub desc: SmallVec<[EpgStringWithLang; 1]>,
32
33    /// Category / genre tags.
34    #[serde(default)]
35    pub category: SmallVec<[EpgStringWithLang; 2]>,
36
37    /// Credits (directors, actors, writers, etc.).
38    pub credits: Option<EpgCredits>,
39
40    /// Original air date.
41    pub date: Option<String>,
42
43    /// Programme length in minutes.
44    pub length: Option<u32>,
45
46    /// Episode numbering (xmltv_ns, onscreen, etc.).
47    #[serde(default)]
48    pub episode_num: SmallVec<[EpgEpisodeNumber; 1]>,
49
50    /// Programme images (poster, backdrop, still).
51    #[serde(default)]
52    pub image: SmallVec<[EpgImage; 1]>,
53
54    /// Programme icon.
55    pub icon: Option<EpgIcon>,
56
57    /// Content ratings.
58    #[serde(default)]
59    pub rating: SmallVec<[EpgRating; 1]>,
60
61    /// Star ratings (critic scores).
62    #[serde(default)]
63    pub star_rating: SmallVec<[EpgRating; 1]>,
64
65    /// Broadcast flags.
66    #[serde(default)]
67    pub is_new: bool,
68
69    #[serde(default)]
70    pub is_premiere: bool,
71
72    #[serde(default)]
73    pub is_rerun: bool,
74
75    #[serde(default)]
76    pub is_last_chance: bool,
77
78    /// Keyword(s) for the programme.
79    #[serde(default)]
80    pub keyword: SmallVec<[EpgStringWithLang; 2]>,
81
82    /// Original language of the programme.
83    pub orig_language: Option<EpgStringWithLang>,
84
85    /// Video properties (aspect ratio, colour, quality).
86    pub video: Option<EpgVideo>,
87
88    /// Audio properties (stereo mode, presence).
89    #[serde(default)]
90    pub audio: SmallVec<[EpgAudio; 1]>,
91
92    /// Reviews / critiques.
93    #[serde(default)]
94    pub review: SmallVec<[EpgReview; 1]>,
95}
96
97/// A string value with an optional language attribute.
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct EpgStringWithLang {
100    pub value: String,
101    pub lang: Option<String>,
102}
103
104impl EpgStringWithLang {
105    pub fn new(value: impl Into<String>) -> Self {
106        Self {
107            value: value.into(),
108            lang: None,
109        }
110    }
111
112    pub fn with_lang(value: impl Into<String>, lang: impl Into<String>) -> Self {
113        Self {
114            value: value.into(),
115            lang: Some(lang.into()),
116        }
117    }
118}
119
120/// Credits for a programme (cast & crew).
121#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122pub struct EpgCredits {
123    #[serde(default)]
124    pub director: SmallVec<[String; 2]>,
125
126    #[serde(default)]
127    pub actor: SmallVec<[EpgPerson; 4]>,
128
129    #[serde(default)]
130    pub writer: SmallVec<[String; 2]>,
131
132    #[serde(default)]
133    pub producer: SmallVec<[String; 1]>,
134
135    #[serde(default)]
136    pub composer: SmallVec<[String; 1]>,
137
138    #[serde(default)]
139    pub presenter: SmallVec<[String; 2]>,
140
141    #[serde(default)]
142    pub commentator: SmallVec<[String; 1]>,
143
144    #[serde(default)]
145    pub guest: SmallVec<[EpgPerson; 2]>,
146}
147
148/// A person with optional role and guest flag.
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct EpgPerson {
151    pub name: String,
152    pub role: Option<String>,
153    #[serde(default)]
154    pub guest: bool,
155    pub image: Option<String>,
156    pub url: Option<String>,
157}
158
159/// Episode numbering entry.
160#[derive(Debug, Clone, Default, Serialize, Deserialize)]
161pub struct EpgEpisodeNumber {
162    pub value: String,
163    /// Numbering system: "xmltv_ns", "onscreen", etc.
164    pub system: Option<String>,
165}
166
167/// Programme image with type metadata.
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
169pub struct EpgImage {
170    pub url: String,
171    /// Image type: "poster", "backdrop", "still".
172    pub image_type: Option<String>,
173    pub size: Option<String>,
174    pub orient: Option<String>,
175}
176
177/// Programme icon (typically a small thumbnail).
178#[derive(Debug, Clone, Default, Serialize, Deserialize)]
179pub struct EpgIcon {
180    pub src: String,
181    pub width: Option<u32>,
182    pub height: Option<u32>,
183}
184
185/// Content or star rating.
186#[derive(Debug, Clone, Default, Serialize, Deserialize)]
187pub struct EpgRating {
188    pub value: String,
189    pub system: Option<String>,
190}
191
192/// Video properties for a programme (XMLTV `<video>`).
193#[derive(Debug, Clone, Default, Serialize, Deserialize)]
194pub struct EpgVideo {
195    pub present: Option<bool>,
196    pub colour: Option<bool>,
197    pub aspect: Option<String>,
198    pub quality: Option<String>,
199}
200
201/// Audio properties for a programme (XMLTV `<audio>`).
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203pub struct EpgAudio {
204    pub present: Option<bool>,
205    pub stereo: Option<String>,
206}
207
208/// A review for a programme (XMLTV `<review>`).
209#[derive(Debug, Clone, Default, Serialize, Deserialize)]
210pub struct EpgReview {
211    pub value: String,
212    pub review_type: Option<String>,
213    pub source: Option<String>,
214    pub reviewer: Option<String>,
215    pub lang: Option<String>,
216}
217
218/// An XMLTV channel definition.
219#[derive(Debug, Clone, Default, Serialize, Deserialize)]
220pub struct EpgChannel {
221    pub id: String,
222    #[serde(default)]
223    pub display_name: SmallVec<[EpgStringWithLang; 1]>,
224    /// Single icon (backward compat).
225    pub icon: Option<EpgIcon>,
226    /// Single URL (backward compat).
227    pub url: Option<String>,
228    /// Multiple icons.
229    #[serde(default)]
230    pub icons: SmallVec<[EpgIcon; 1]>,
231    /// Multiple URLs.
232    #[serde(default)]
233    pub urls: SmallVec<[String; 1]>,
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn programme_default_has_empty_title() {
242        let p = EpgProgramme::default();
243        assert!(p.title.is_empty());
244        assert!(p.channel.is_empty());
245        assert!(!p.is_new);
246    }
247
248    #[test]
249    fn programme_default_has_empty_new_fields() {
250        let p = EpgProgramme::default();
251        assert!(p.keyword.is_empty());
252        assert!(p.orig_language.is_none());
253        assert!(p.video.is_none());
254        assert!(p.audio.is_empty());
255        assert!(p.review.is_empty());
256    }
257
258    #[test]
259    fn epg_video_default_has_all_none() {
260        let v = EpgVideo::default();
261        assert!(v.present.is_none());
262        assert!(v.colour.is_none());
263        assert!(v.aspect.is_none());
264        assert!(v.quality.is_none());
265    }
266
267    #[test]
268    fn string_with_lang_constructors() {
269        let plain = EpgStringWithLang::new("Hello");
270        assert_eq!(plain.value, "Hello");
271        assert!(plain.lang.is_none());
272
273        let lang = EpgStringWithLang::with_lang("Bonjour", "fr");
274        assert_eq!(lang.value, "Bonjour");
275        assert_eq!(lang.lang.as_deref(), Some("fr"));
276    }
277}