1mod guid;
2mod metadata_type;
3
4use crate::media_container::{
5 helpers::deserialize_option_string_from_number,
6 helpers::{deserialize_option_datetime_from_timestamp, optional_boolish},
7 preferences::Preferences,
8 MediaContainer,
9};
10pub use guid::Guid;
11pub use metadata_type::*;
12use monostate::MustBe;
13use serde::{Deserialize, Deserializer, Serialize};
14use serde_aux::prelude::{
15 deserialize_number_from_string, deserialize_option_number_from_string,
16 deserialize_string_from_number,
17};
18use serde_json::Value;
19use serde_plain::{derive_display_from_serialize, derive_fromstr_from_deserialize};
20use serde_repr::{Deserialize_repr, Serialize_repr};
21use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator};
22use time::{Date, OffsetDateTime};
23
24#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
25#[serde(rename_all = "lowercase")]
26pub enum Decision {
27 Copy,
28 Transcode,
29 Ignore,
30 DirectPlay,
31 Burn,
32 #[cfg(not(feature = "tests_deny_unknown_fields"))]
33 #[serde(other)]
34 Unknown,
35}
36
37derive_display_from_serialize!(Decision);
38
39#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
40#[serde(rename_all = "lowercase")]
41pub enum Protocol {
42 Http,
44 Hls,
46 Dash,
48 Mp4,
50 #[cfg(not(feature = "tests_deny_unknown_fields"))]
51 #[serde(other)]
52 Unknown,
53}
54
55derive_display_from_serialize!(Protocol);
56
57#[derive(Debug, Clone, Copy, Deserialize)]
58#[serde(rename_all = "lowercase")]
59pub enum ChapterSource {
60 Media,
61 Mixed,
62 Agent,
63 #[cfg(not(feature = "tests_deny_unknown_fields"))]
64 #[serde(other)]
65 Unknown,
66}
67
68derive_fromstr_from_deserialize!(ChapterSource);
69
70#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
71#[serde(rename_all = "kebab-case")]
72pub enum AudioCodec {
73 Aac,
74 Ac3,
75 Dca,
76 DcaMa,
77 Eac3,
78 Mp2,
79 Mp3,
80 Opus,
81 Pcm,
82 Vorbis,
83 Flac,
84 #[serde(rename = "truehd")]
85 TrueHd,
86 #[cfg(not(feature = "tests_deny_unknown_fields"))]
87 #[serde(other)]
88 Unknown,
89}
90
91derive_fromstr_from_deserialize!(AudioCodec);
92derive_display_from_serialize!(AudioCodec);
93
94#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
95#[serde(rename_all = "kebab-case")]
96pub enum VideoCodec {
97 H264,
98 Hevc,
99 Mpeg1Video,
100 Mpeg2Video,
101 Mpeg4,
102 Msmpeg4v3,
103 Vc1,
104 Vp8,
105 Vp9,
106 #[cfg(not(feature = "tests_deny_unknown_fields"))]
107 #[serde(other)]
108 Unknown,
109}
110
111derive_fromstr_from_deserialize!(VideoCodec);
112derive_display_from_serialize!(VideoCodec);
113
114#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
115#[serde(rename_all = "kebab-case")]
116pub enum LyricCodec {
117 Lrc,
118 Txt,
119 #[cfg(not(feature = "tests_deny_unknown_fields"))]
120 #[serde(other)]
121 Unknown,
122}
123
124derive_fromstr_from_deserialize!(LyricCodec);
125derive_display_from_serialize!(LyricCodec);
126
127#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
128#[serde(rename_all = "snake_case")]
129pub enum SubtitleCodec {
130 Ass,
131 Pgs,
132 Subrip,
133 Srt,
134 DvdSubtitle,
135 MovText,
136 Vtt,
137 DvbSubtitle,
138 #[cfg(not(feature = "tests_deny_unknown_fields"))]
139 #[serde(other)]
140 Unknown,
141}
142
143derive_fromstr_from_deserialize!(SubtitleCodec);
144derive_display_from_serialize!(SubtitleCodec);
145
146#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
147#[serde(rename_all = "lowercase")]
148pub enum ContainerFormat {
149 Aac,
150 Avi,
151 Jpeg,
152 M4v,
153 Mkv,
154 Mp3,
155 Mp4,
156 Mpeg,
157 MpegTs,
158 Ogg,
159 Wav,
160 Ac3,
161 Eac3,
162 #[cfg(not(feature = "tests_deny_unknown_fields"))]
163 #[serde(other)]
164 Unknown,
165}
166
167derive_fromstr_from_deserialize!(ContainerFormat);
168derive_display_from_serialize!(ContainerFormat);
169
170#[serde_as]
171#[derive(Debug, Deserialize, Clone)]
172#[serde(rename_all = "camelCase")]
173#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
174pub struct VideoStream {
175 #[serde(default, deserialize_with = "deserialize_string_from_number")]
176 pub id: String,
177 pub stream_type: MustBe!(1),
178 pub index: Option<u32>,
179 pub codec: VideoCodec,
180 pub default: Option<bool>,
181 pub selected: Option<bool>,
182 pub title: Option<String>,
183 pub display_title: String,
184 pub extended_display_title: Option<String>,
185
186 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, u32>>")]
187 pub required_bandwidths: Option<Vec<u32>>,
188 pub decision: Option<Decision>,
189 pub location: Option<String>,
190
191 pub height: u32,
192 pub width: u32,
193 pub bit_depth: Option<u8>,
194 pub bitrate: Option<u32>,
195 pub chroma_location: Option<String>,
196 pub chroma_subsampling: Option<String>,
197 pub coded_height: Option<u32>,
198 pub coded_width: Option<u32>,
199 pub color_primaries: Option<String>,
200 pub color_range: Option<String>,
201 pub color_space: Option<String>,
202 pub color_trc: Option<String>,
203 pub frame_rate: Option<f32>,
204 pub has_scaling_matrix: Option<bool>,
205 pub level: Option<u32>,
206 pub profile: Option<String>,
207 pub ref_frames: Option<u32>,
208 pub scan_type: Option<String>,
209 #[serde(rename = "codecID")]
210 pub codec_id: Option<String>,
211 pub stream_identifier: Option<String>,
212 pub language: Option<String>,
213 pub language_code: Option<String>,
214 pub language_tag: Option<String>,
215 pub anamorphic: Option<bool>,
216 pub pixel_aspect_ratio: Option<String>,
217}
218
219#[serde_as]
220#[derive(Debug, Deserialize, Clone)]
221#[serde(rename_all = "camelCase")]
222#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
223pub struct AudioStream {
224 #[serde(default, deserialize_with = "deserialize_string_from_number")]
225 pub id: String,
226 pub stream_type: MustBe!(2),
227 pub index: Option<u32>,
228 pub codec: AudioCodec,
229 pub default: Option<bool>,
230 pub selected: Option<bool>,
231 pub title: Option<String>,
232 pub display_title: String,
233 pub extended_display_title: Option<String>,
234
235 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, u32>>")]
236 pub required_bandwidths: Option<Vec<u32>>,
237 pub decision: Option<Decision>,
238 pub location: Option<String>,
239
240 pub channels: u32,
241 pub audio_channel_layout: Option<String>,
242 pub profile: Option<String>,
243 pub sampling_rate: Option<u32>,
244 pub bitrate: Option<u32>,
245 pub bitrate_mode: Option<String>,
246 pub language: Option<String>,
247 pub language_code: Option<String>,
248 pub language_tag: Option<String>,
249 pub peak: Option<String>,
250 pub gain: Option<String>,
251 pub album_peak: Option<String>,
252 pub album_gain: Option<String>,
253 pub album_range: Option<String>,
254 pub lra: Option<String>,
255 pub loudness: Option<String>,
256 pub stream_identifier: Option<String>,
257}
258
259#[serde_as]
260#[derive(Debug, Deserialize, Clone)]
261#[serde(rename_all = "camelCase")]
262#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
263pub struct SubtitleStream {
264 #[serde(default, deserialize_with = "deserialize_string_from_number")]
265 pub id: String,
266 pub stream_type: MustBe!(3),
267 pub index: Option<u32>,
268 pub codec: SubtitleCodec,
269 pub default: Option<bool>,
270 pub selected: Option<bool>,
271 pub title: Option<String>,
272 pub display_title: String,
273 pub extended_display_title: Option<String>,
274 pub forced: Option<bool>,
275
276 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, u32>>")]
277 pub required_bandwidths: Option<Vec<u32>>,
278 pub decision: Option<Decision>,
279 pub location: Option<String>,
280
281 pub key: Option<String>,
282 pub format: Option<String>,
283 pub file: Option<String>,
284 pub bitrate: Option<u32>,
285 pub hearing_impaired: Option<bool>,
286 pub language: Option<String>,
287 pub language_code: Option<String>,
288 pub language_tag: Option<String>,
289 pub ignore: Option<String>,
290 pub burn: Option<String>,
291}
292
293#[derive(Debug, Deserialize, Clone)]
294#[serde(rename_all = "camelCase")]
295#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
296pub struct LyricStream {
297 #[serde(default, deserialize_with = "deserialize_string_from_number")]
298 pub id: String,
299 pub stream_type: MustBe!(4),
300 pub index: Option<u32>,
301 pub codec: LyricCodec,
302 pub default: Option<bool>,
303 pub selected: Option<bool>,
304 pub title: Option<String>,
305 pub display_title: String,
306 pub extended_display_title: Option<String>,
307
308 pub key: Option<String>,
309 pub format: Option<String>,
310 pub timed: Option<String>,
311 pub min_lines: Option<String>,
312 pub provider: Option<String>,
313}
314
315#[derive(Debug, Deserialize, Clone)]
316#[cfg_attr(not(feature = "tests_deny_unknown_fields"), serde(untagged))]
317#[cfg_attr(feature = "tests_deny_unknown_fields", serde(try_from = "Value"))]
318pub enum Stream {
319 Video(VideoStream),
320 Audio(AudioStream),
321 Subtitle(SubtitleStream),
322 Lyric(LyricStream),
323 Unknown(Value),
324}
325
326#[cfg(feature = "tests_deny_unknown_fields")]
328impl TryFrom<Value> for Stream {
329 type Error = String;
330
331 fn try_from(value: Value) -> Result<Self, Self::Error> {
332 let stream_type = match &value {
333 Value::Object(o) => {
334 if let Some(Value::Number(n)) = o.get("streamType") {
335 if let Some(v) = n.as_u64() {
336 v
337 } else {
338 return Err(format!(
339 "Failed to decode Stream. Unexpected streamType `{n}`"
340 ));
341 }
342 } else {
343 return Err("Failed to decode Stream. Missing streamType property.".to_string());
344 }
345 }
346 _ => return Err("Failed to decode Stream. Data was not an object.".to_string()),
347 };
348
349 if stream_type == 1 {
350 let s: VideoStream = serde_json::from_value(value)
351 .map_err(|e| format!("Failed to decode video stream: {e}"))?;
352 Ok(Self::Video(s))
353 } else if stream_type == 2 {
354 let s: AudioStream = serde_json::from_value(value)
355 .map_err(|e| format!("Failed to decode audio stream: {e}"))?;
356 Ok(Self::Audio(s))
357 } else if stream_type == 3 {
358 let s: SubtitleStream = serde_json::from_value(value)
359 .map_err(|e| format!("Failed to decode subtitle stream: {e}"))?;
360 Ok(Self::Subtitle(s))
361 } else if stream_type == 4 {
362 let s: LyricStream = serde_json::from_value(value)
363 .map_err(|e| format!("Failed to decode lyric stream: {e}"))?;
364 Ok(Self::Lyric(s))
365 } else if !cfg!(feature = "tests_deny_unknown_fields") {
366 Ok(Self::Unknown(value))
367 } else {
368 Err(format!(
369 "Failed to decode Stream. Unexpected streamType `{stream_type}`"
370 ))
371 }
372 }
373}
374
375#[serde_as]
376#[derive(Debug, Deserialize, Clone)]
377#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
378#[serde(rename_all = "camelCase")]
379pub struct Part {
380 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
381 pub id: Option<String>,
382 pub key: Option<String>,
383 pub duration: Option<u64>,
384 pub file: Option<String>,
385 pub size: Option<u64>,
386 pub container: Option<ContainerFormat>,
387 pub indexes: Option<String>,
388 pub audio_profile: Option<String>,
389 pub video_profile: Option<String>,
390 pub protocol: Option<Protocol>,
391 pub selected: Option<bool>,
392 pub decision: Option<Decision>,
393 pub width: Option<u32>,
394 pub height: Option<u32>,
395 pub packet_length: Option<u64>,
396 pub has_thumbnail: Option<String>,
397 #[serde(rename = "has64bitOffsets")]
398 pub has_64bit_offsets: Option<bool>,
399 #[serde(default, deserialize_with = "optional_boolish")]
400 pub optimized_for_streaming: Option<bool>,
401 pub has_chapter_text_stream: Option<bool>,
402 pub has_chapter_video_stream: Option<bool>,
403 pub deep_analysis_version: Option<String>,
404 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, u32>>")]
405 pub required_bandwidths: Option<Vec<u32>>,
406 pub bitrate: Option<u32>,
407 #[serde(rename = "Stream")]
408 pub streams: Option<Vec<Stream>>,
409}
410
411#[derive(Debug, Deserialize, Clone)]
412#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
413#[serde(rename_all = "camelCase")]
414pub struct Media {
415 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
416 pub id: Option<String>,
417 pub duration: Option<u64>,
418 pub bitrate: Option<u32>,
419 pub width: Option<u32>,
420 pub height: Option<u32>,
421 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
422 pub aspect_ratio: Option<f32>,
423 pub audio_channels: Option<u8>,
424 pub protocol: Option<Protocol>,
425 pub audio_codec: Option<AudioCodec>,
426 pub video_codec: Option<VideoCodec>,
427 pub video_resolution: Option<String>,
428 pub container: Option<ContainerFormat>,
429 pub extension: Option<String>,
430 pub video_frame_rate: Option<String>,
431 pub audio_profile: Option<String>,
432 pub video_profile: Option<String>,
433 pub selected: Option<bool>,
434 #[serde(rename = "Part")]
435 pub parts: Vec<Part>,
436 #[serde(rename = "has64bitOffsets")]
437 pub has_64bit_offsets: Option<bool>,
438 #[serde(default, deserialize_with = "optional_boolish")]
439 pub optimized_for_streaming: Option<bool>,
440 pub display_offset: Option<u64>,
441 pub premium: Option<bool>,
442}
443
444#[derive(Debug, Deserialize, Clone)]
445#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
446pub struct Field {
447 pub locked: bool,
448 pub name: String,
449}
450
451#[derive(Debug, Deserialize, Clone)]
452#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
453pub struct Location {
454 pub path: String,
455}
456
457#[derive(Debug, Deserialize, Clone)]
458#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
459pub struct Tag {
460 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
461 pub id: Option<String>,
462 pub tag: String,
463 pub filter: Option<String>,
464 pub directory: Option<bool>,
465 #[serde(rename = "ratingKey")]
466 pub rating_key: Option<String>,
467 pub context: Option<String>,
468 pub slug: Option<String>,
469 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
470 pub count: Option<u32>,
471}
472
473#[derive(Debug, Deserialize, Clone)]
474#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
475pub struct Collection {
476 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
477 pub id: Option<String>,
478 pub art: Option<String>,
479 pub key: Option<String>,
480 pub thumb: Option<String>,
481 pub tag: String,
482 pub filter: Option<String>,
483 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
484 pub count: Option<u32>,
485 pub guid: Option<Guid>,
486 pub summary: Option<String>,
487}
488
489#[derive(Debug, Deserialize, Clone)]
490#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
491pub struct Review {
492 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
493 pub id: Option<String>,
494 pub tag: String,
495 pub filter: Option<String>,
496 pub text: String,
497 pub image: String,
498 pub link: Option<String>,
499 pub source: String,
500}
501
502#[derive(Debug, Deserialize, Clone)]
503#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
504#[serde(rename_all = "camelCase")]
505pub struct Rating {
506 #[serde(default, deserialize_with = "deserialize_number_from_string")]
507 pub count: u32,
508 pub image: String,
509 #[serde(rename = "type")]
510 pub rating_type: String,
511 #[serde(default, deserialize_with = "deserialize_number_from_string")]
512 pub value: f32,
513}
514
515#[derive(Debug, Deserialize, Clone)]
516#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
517#[serde(rename_all = "camelCase")]
518pub struct Role {
519 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
520 pub id: Option<String>,
521 pub tag: String,
522 pub key: Option<String>,
523 pub slug: Option<String>,
524 pub tag_key: Option<String>,
525 pub filter: Option<String>,
526 pub role: Option<String>,
527 pub thumb: Option<String>,
528 #[serde(rename = "type")]
529 pub role_type: Option<String>,
530 pub directory: Option<bool>,
531 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
532 pub count: Option<u32>,
533}
534
535#[derive(Debug, Deserialize, Clone)]
536#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
537#[serde(rename_all = "camelCase")]
538pub struct ParentMetadata {
539 pub parent_key: Option<String>,
540 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
541 pub parent_rating_key: Option<String>,
542 pub parent_guid: Option<Guid>,
543
544 pub parent_title: Option<String>,
545 pub parent_studio: Option<String>,
546 pub parent_year: Option<u32>,
547 pub parent_content_rating: Option<String>,
548 pub parent_index: Option<u32>,
549
550 pub parent_thumb: Option<String>,
551 pub parent_art: Option<String>,
552 pub parent_theme: Option<String>,
553}
554
555#[derive(Debug, Deserialize, Clone)]
556#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
557#[serde(rename_all = "camelCase")]
558pub struct GrandParentMetadata {
559 pub grandparent_key: Option<String>,
560 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
561 pub grandparent_rating_key: Option<String>,
562 pub grandparent_guid: Option<Guid>,
563
564 pub grandparent_title: Option<String>,
565 pub grandparent_studio: Option<String>,
566 pub grandparent_year: Option<u32>,
567 pub grandparent_content_rating: Option<String>,
568 pub grandparent_index: Option<u32>,
569
570 pub grandparent_thumb: Option<String>,
571 pub grandparent_art: Option<String>,
572 pub grandparent_theme: Option<String>,
573}
574
575#[derive(Debug, Deserialize, Clone)]
576#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
577#[serde(rename_all = "camelCase")]
578pub struct Extras {
579 pub size: u32,
580 pub key: Option<String>,
581 #[serde(default, rename = "Metadata")]
582 pub metadata: Vec<Box<Metadata>>,
583}
584
585#[derive(Debug, Deserialize, Clone)]
586#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
587#[serde(rename_all = "camelCase")]
588pub struct OnDeck {
589 #[serde(rename = "Metadata")]
590 pub metadata: Metadata,
591}
592
593#[derive(Debug, Deserialize, Clone)]
594#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
595#[serde(rename_all = "camelCase")]
596pub struct Chapter {
597 pub id: u32,
598 pub filter: Option<String>,
599 pub index: u32,
600 pub start_time_offset: u64,
601 pub end_time_offset: u64,
602 pub tag: Option<String>,
603 pub thumb: Option<String>,
604}
605
606pub(crate) fn deserialize_marker_type<'de, D>(deserializer: D) -> Result<MarkerType, D::Error>
607where
608 D: Deserializer<'de>,
609{
610 #[derive(Debug, Deserialize)]
611 #[serde(rename_all = "camelCase")]
612 struct Helper {
613 r#type: String,
614 r#final: Option<bool>,
615 }
616
617 let m = Helper::deserialize(deserializer)?;
618
619 match m.r#type.as_str() {
620 "intro" => Ok(MarkerType::Intro),
621 "credits" => Ok(MarkerType::Credits(m.r#final.unwrap_or_default())),
622 #[cfg(not(feature = "tests_deny_unknown_fields"))]
623 _ => Ok(MarkerType::Unknown(m.r#type)),
624 #[cfg(feature = "tests_deny_unknown_fields")]
625 _ => {
626 return Err(serde::de::Error::unknown_variant(
627 m.r#type.as_str(),
628 &["credits", "intro"],
629 ))
630 }
631 }
632}
633
634#[derive(Debug, Clone)]
635pub enum MarkerType {
636 Credits(bool),
638 Intro,
639 #[cfg(not(feature = "tests_deny_unknown_fields"))]
640 Unknown(String),
641}
642
643#[derive(Debug, Deserialize, Clone)]
644#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
645#[serde(rename_all = "camelCase")]
646pub struct MarkerAttributes {
647 pub id: u32,
648 pub version: Option<u32>,
649}
650
651#[derive(Debug, Deserialize, Clone)]
652#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
653#[serde(rename_all = "camelCase")]
654pub struct Marker {
655 pub id: u32,
656 pub start_time_offset: u32,
657 pub end_time_offset: u32,
658 #[serde(flatten, deserialize_with = "deserialize_marker_type")]
659 pub marker_type: MarkerType,
660 #[serde(rename = "Attributes")]
661 pub attributes: MarkerAttributes,
662}
663
664#[derive(Debug, Deserialize, Clone)]
665#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
666#[serde(rename_all = "camelCase")]
667pub struct Metadata {
668 pub key: String,
669 pub rating_key: String,
670 pub guid: Option<Guid>,
671 pub primary_guid: Option<Guid>,
672
673 #[serde(flatten, deserialize_with = "deserialize_option_metadata_type")]
674 pub metadata_type: Option<MetadataType>,
675 #[serde(default, deserialize_with = "optional_boolish")]
676 pub smart: Option<bool>,
677 #[serde(default, deserialize_with = "optional_boolish")]
678 pub allow_sync: Option<bool>,
679
680 pub title: String,
681 pub title_sort: Option<String>,
682 pub original_title: Option<String>,
683 pub studio: Option<String>,
684 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
685 pub year: Option<u32>,
686 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
687 pub min_year: Option<u32>,
688 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
689 pub max_year: Option<u32>,
690 pub content_rating: Option<String>,
691 pub summary: Option<String>,
692 pub rating: Option<f32>,
693 pub rating_count: Option<u32>,
694 pub rating_image: Option<String>,
695 pub audience_rating: Option<f32>,
696 pub audience_rating_image: Option<String>,
697 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
698 pub user_rating: Option<f32>,
699 #[serde(
700 default,
701 deserialize_with = "deserialize_option_datetime_from_timestamp"
702 )]
703 pub last_rated_at: Option<OffsetDateTime>,
704 pub tagline: Option<String>,
705 pub duration: Option<u64>,
706 pub originally_available_at: Option<Date>,
707
708 pub thumb: Option<String>,
709 pub art: Option<String>,
710 pub theme: Option<String>,
711 pub composite: Option<String>,
712 pub banner: Option<String>,
713 pub icon: Option<String>,
714
715 pub index: Option<u32>,
716 #[serde(rename = "playlistItemID")]
717 pub playlist_item_id: Option<u32>,
718 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
719 pub child_count: Option<u32>,
720 pub season_count: Option<u32>,
721 pub leaf_count: Option<u32>,
722 pub viewed_leaf_count: Option<u32>,
723 pub skip_children: Option<bool>,
724
725 pub view_count: Option<u64>,
726 pub skip_count: Option<u64>,
727 #[serde(default, with = "time::serde::timestamp::option")]
728 pub last_viewed_at: Option<OffsetDateTime>,
729
730 #[serde(rename = "createdAtTZOffset")]
731 pub created_at_tz_offset: Option<String>,
732 pub created_at_accuracy: Option<String>,
733 #[serde(default, with = "time::serde::timestamp::option")]
734 pub added_at: Option<OffsetDateTime>,
735 #[serde(default, with = "time::serde::timestamp::option")]
736 pub deleted_at: Option<OffsetDateTime>,
737 #[serde(default, with = "time::serde::timestamp::option")]
738 pub updated_at: Option<OffsetDateTime>,
739 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
740 pub loudness_analysis_version: Option<u32>,
741 #[serde(default, deserialize_with = "optional_boolish")]
742 pub has_premium_extras: Option<bool>,
743 #[serde(default, deserialize_with = "optional_boolish")]
744 pub has_premium_primary_extra: Option<bool>,
745 pub view_offset: Option<u64>,
746 pub chapter_source: Option<ChapterSource>,
747 pub primary_extra_key: Option<String>,
748 #[serde(default, deserialize_with = "optional_boolish")]
749 pub has_premium_lyrics: Option<bool>,
750 pub music_analysis_version: Option<String>,
751
752 #[serde(rename = "librarySectionID")]
753 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
754 pub library_section_id: Option<u32>,
755 pub library_section_title: Option<String>,
756 pub library_section_key: Option<String>,
757
758 #[serde(flatten)]
759 pub parent: Box<ParentMetadata>,
760 #[serde(flatten)]
761 pub grand_parent: Box<GrandParentMetadata>,
762
763 #[serde(default, rename = "Guid")]
764 pub guids: Vec<Guid>,
765 #[serde(default, rename = "Collection")]
766 pub collections: Vec<Collection>,
767 #[serde(default, rename = "Similar")]
768 pub similar: Vec<Tag>,
769 #[serde(default, rename = "Genre")]
770 pub genres: Vec<Tag>,
771 #[serde(default, rename = "Director")]
772 pub directors: Vec<Role>,
773 #[serde(default, rename = "Producer")]
774 pub producers: Vec<Role>,
775 #[serde(default, rename = "Writer")]
776 pub writers: Vec<Role>,
777 #[serde(default, rename = "Country")]
778 pub countries: Vec<Tag>,
779 #[serde(default, rename = "Rating")]
780 pub ratings: Vec<Rating>,
781 #[serde(default, rename = "Role")]
782 pub roles: Vec<Role>,
783 #[serde(default, rename = "Location")]
784 pub locations: Vec<Location>,
785 #[serde(default, rename = "Field")]
786 pub fields: Vec<Field>,
787 #[serde(default, rename = "Mood")]
788 pub moods: Vec<Tag>,
789 #[serde(default, rename = "Format")]
790 pub formats: Vec<Tag>,
791 #[serde(default, rename = "Subformat")]
792 pub sub_formats: Vec<Tag>,
793 #[serde(default, rename = "Style")]
794 pub styles: Vec<Tag>,
795 #[serde(default, rename = "Review")]
796 pub reviews: Vec<Review>,
797 #[serde(default, rename = "Chapter")]
798 pub chapters: Vec<Chapter>,
799 #[serde(default, rename = "Label")]
800 pub labels: Vec<Tag>,
801
802 #[serde(rename = "Preferences")]
803 pub preferences: Option<Box<Preferences>>,
804
805 #[serde(rename = "Extras")]
806 pub extras: Option<Extras>,
807
808 #[serde(rename = "OnDeck")]
809 pub on_deck: Option<Box<OnDeck>>,
810
811 #[serde(default, rename = "Marker")]
812 pub markers: Vec<Marker>,
813
814 #[serde(rename = "Media")]
815 pub media: Option<Vec<Media>>,
816
817 #[serde(rename = "Vast")]
818 pub vast: Option<Vec<Link>>,
819
820 #[serde(rename = "publicPagesURL")]
821 pub public_pages_url: Option<String>,
822 pub slug: Option<String>,
823 pub user_state: Option<bool>,
824 pub imdb_rating_count: Option<u64>,
825 pub source: Option<String>,
826 #[serde(rename = "Image")]
827 pub image: Option<Vec<Image>>,
828 #[serde(rename = "Studio")]
829 pub studios: Option<Vec<Tag>>,
830
831 pub language_override: Option<String>,
832 pub content: Option<String>,
833 pub collection_sort: Option<String>,
834 pub skip_parent: Option<bool>,
835}
836
837#[derive(Deserialize, Debug, Clone)]
838pub struct Image {
839 pub url: String,
840 #[serde(rename = "type")]
841 pub image_type: String,
842 pub alt: String,
843}
844
845#[derive(Deserialize, Debug, Clone)]
846pub struct Link {
847 pub url: String,
848}
849
850#[derive(Debug, Deserialize, Clone)]
851#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
852#[serde(rename_all = "camelCase")]
853pub struct MetadataMediaContainer {
854 pub key: Option<String>,
855 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
856 pub rating_key: Option<String>,
857 pub augmentation_key: Option<String>,
858
859 pub title: Option<String>,
860 pub title1: Option<String>,
861 pub title2: Option<String>,
862 pub summary: Option<String>,
863 pub duration: Option<u64>,
864
865 #[serde(default, deserialize_with = "optional_boolish")]
866 pub allow_sync: Option<bool>,
867 #[serde(rename = "nocache")]
868 pub no_cache: Option<bool>,
869 pub sort_asc: Option<bool>,
870 pub smart: Option<bool>,
871
872 pub thumb: Option<String>,
873 pub art: Option<String>,
874 pub theme: Option<String>,
875 pub composite: Option<String>,
876 pub banner: Option<String>,
877
878 #[serde(rename = "librarySectionID")]
879 pub library_section_id: Option<u32>,
880 pub library_section_title: Option<String>,
881 #[serde(rename = "librarySectionUUID")]
882 pub library_section_uuid: Option<String>,
883
884 #[serde(flatten)]
885 pub parent: ParentMetadata,
886 #[serde(flatten)]
887 pub grand_parent: GrandParentMetadata,
888 #[serde(flatten)]
889 pub media_container: MediaContainer,
890
891 pub media_tag_prefix: Option<String>,
892 #[serde(default, with = "time::serde::timestamp::option")]
893 pub media_tag_version: Option<OffsetDateTime>,
894 pub mixed_parents: Option<bool>,
895 pub view_group: Option<String>,
896 pub view_mode: Option<u32>,
897 pub leaf_count: Option<u32>,
898 pub playlist_type: Option<PlaylistMetadataType>,
899
900 #[serde(default, rename = "Directory")]
901 pub directories: Vec<Value>,
902 #[serde(default, rename = "Metadata")]
903 pub metadata: Vec<Metadata>,
904}
905
906#[derive(Debug, Deserialize, Clone, Copy)]
907#[serde(rename_all = "lowercase")]
908pub enum PivotType {
909 Hub,
910 List,
911 View,
912 #[cfg(not(feature = "tests_deny_unknown_fields"))]
913 #[serde(other)]
914 Unknown,
915}
916
917derive_fromstr_from_deserialize!(PivotType);
918
919#[derive(Debug, Deserialize, Clone)]
920#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
921#[serde(rename_all = "camelCase")]
922pub struct Pivot {
923 pub context: String,
924 pub id: String,
925 pub key: String,
926 pub symbol: String,
927 pub title: String,
928 pub requires: Option<String>,
929 #[serde(rename = "type")]
930 pub pivot_type: PivotType,
931}
932
933#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
934#[serde(rename_all = "lowercase")]
935pub enum LibraryType {
936 Movie,
937 Show,
938 Artist,
939 Photo,
940 Mixed,
941 Clip,
942 #[cfg(not(feature = "tests_deny_unknown_fields"))]
943 #[serde(other)]
944 Unknown,
945}
946
947derive_fromstr_from_deserialize!(LibraryType);
948derive_display_from_serialize!(LibraryType);
949
950#[derive(Debug, Deserialize, Clone)]
951#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
952#[serde(rename_all = "camelCase")]
953pub struct ServerLibrary {
954 #[serde(rename = "type")]
955 pub library_type: LibraryType,
956 #[serde(rename = "Pivot")]
957 pub pivots: Vec<Pivot>,
958 pub agent: String,
959 pub hub_key: String,
960 pub id: String,
961 pub key: String,
962 pub subtype: Option<String>,
963 pub language: String,
964 pub refreshing: bool,
965 #[serde(with = "time::serde::timestamp")]
966 pub scanned_at: OffsetDateTime,
967 pub scanner: String,
968 pub title: String,
969 #[serde(with = "time::serde::timestamp")]
970 pub updated_at: OffsetDateTime,
971 pub uuid: String,
972}
973
974#[derive(Debug, Deserialize, Clone)]
975#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
976#[serde(rename_all = "camelCase")]
977pub struct ServerPlaylists {
978 #[serde(rename = "type")]
979 _type: MustBe!("playlist"),
980 #[serde(rename = "Pivot")]
981 pub pivots: Vec<Pivot>,
982 pub id: String,
983 pub key: String,
984 pub title: String,
985}
986
987#[derive(Debug, Deserialize, Clone)]
988#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
989#[serde(rename_all = "camelCase")]
990pub struct ServerHome {
991 pub hub_key: String,
992 pub title: String,
993}
994
995#[derive(Debug, Deserialize, Clone)]
996#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
997#[serde(rename_all = "camelCase")]
998pub struct DvrLibrary {
999 #[serde(rename = "type")]
1000 pub library_type: LibraryType,
1001 pub key: Option<String>,
1002 pub title: String,
1003 pub icon: String,
1004 #[serde(default, with = "time::serde::timestamp::option")]
1005 pub updated_at: Option<OffsetDateTime>,
1006}
1007
1008#[derive(Debug, Deserialize, Clone)]
1009#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
1010#[serde(rename_all = "camelCase")]
1011pub struct LiveTv {
1012 #[serde(rename = "Pivot")]
1013 pub pivots: Vec<Pivot>,
1014 pub id: String,
1015 pub title: String,
1016 pub hub_key: String,
1017}
1018
1019#[derive(Debug, Deserialize, Clone)]
1020#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
1021#[serde(rename_all = "camelCase")]
1022pub struct OnlineLibrary {
1023 #[serde(rename = "type")]
1024 pub library_type: LibraryType,
1025 pub key: Option<String>,
1026 pub title: String,
1027 pub icon: String,
1028 pub id: String,
1029 pub context: Option<String>,
1030 pub hub_key: Option<String>,
1031 #[serde(rename = "Pivot")]
1032 pub pivots: Vec<Pivot>,
1033 #[serde(default, with = "time::serde::timestamp::option")]
1034 pub updated_at: Option<OffsetDateTime>,
1035}
1036
1037#[derive(Debug, Deserialize, Clone)]
1038#[cfg_attr(not(feature = "tests_deny_unknown_fields"), serde(untagged))]
1039#[cfg_attr(feature = "tests_deny_unknown_fields", serde(try_from = "Value"))]
1040pub enum ContentDirectory {
1041 Playlists(ServerPlaylists),
1042 #[serde(rename_all = "camelCase")]
1043 Media(Box<ServerLibrary>),
1044 #[serde(rename_all = "camelCase")]
1045 Home(ServerHome),
1046 #[serde(rename_all = "camelCase")]
1047 LiveTv(LiveTv),
1048 #[serde(rename_all = "camelCase")]
1049 Online(OnlineLibrary),
1050 #[serde(rename_all = "camelCase")]
1051 Dvr(DvrLibrary),
1052 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1053 Unknown(Value),
1054}
1055
1056#[cfg(feature = "tests_deny_unknown_fields")]
1059impl TryFrom<Value> for ContentDirectory {
1060 type Error = String;
1061
1062 fn try_from(value: Value) -> Result<Self, Self::Error> {
1063 let obj = if let Value::Object(o) = &value {
1064 o
1065 } else {
1066 return Err("Failed to decode Directory. Data was not an object.".to_string());
1067 };
1068
1069 let directory_type = match obj.get("type") {
1070 Some(Value::String(n)) => n,
1071 Some(_) => {
1072 return Err("Failed to decode Directory. Unexpected type property.".to_string())
1073 }
1074 None => {
1075 if obj.contains_key("Pivot") {
1076 let live_tv: LiveTv = serde_json::from_value(value)
1077 .map_err(|e| format!("Failed to decode Live TV directory: {e}"))?;
1078 return Ok(Self::LiveTv(live_tv));
1079 }
1080
1081 let home: ServerHome = serde_json::from_value(value)
1082 .map_err(|e| format!("Failed to decode Home directory: {e}"))?;
1083 return Ok(Self::Home(home));
1084 }
1085 };
1086
1087 if directory_type.as_str() == "playlist" {
1088 let p: ServerPlaylists = serde_json::from_value(value)
1089 .map_err(|e| format!("Failed to decode playlist directory: {e}"))?;
1090 Ok(Self::Playlists(p))
1091 } else {
1092 if obj.contains_key("scannedAt") {
1096 let l: ServerLibrary = serde_json::from_value(value)
1097 .map_err(|e| format!("Failed to decode server library directory: {e}"))?;
1098 Ok(Self::Media(Box::new(l)))
1099 } else if obj.contains_key("id") {
1100 let l: OnlineLibrary = serde_json::from_value(value)
1101 .map_err(|e| format!("Failed to decode online library directory: {e}"))?;
1102 Ok(Self::Online(l))
1103 } else {
1104 let l: DvrLibrary = serde_json::from_value(value)
1105 .map_err(|e| format!("Failed to decode dvr library directory: {e}"))?;
1106 Ok(Self::Dvr(l))
1107 }
1108 }
1109 }
1110}
1111
1112#[derive(Debug, Deserialize_repr, Clone, Copy, Serialize_repr)]
1113#[repr(u16)]
1114pub enum SearchType {
1115 Movie = 1,
1116 Show = 2,
1117 Season = 3,
1118 Episode = 4,
1119 Trailer = 5,
1120 Comic = 6,
1121 Person = 7,
1122 Artist = 8,
1123 Album = 9,
1124 Track = 10,
1125 Picture = 11,
1126 Clip = 12,
1127 Photo = 13,
1128 PhotoAlbum = 14,
1129 Playlist = 15,
1130 PlaylistFolder = 16,
1131 Collection = 18,
1132 OptimizedVersion = 42,
1133 UserPlaylistItem = 1001,
1134 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1135 #[serde(other)]
1136 Unknown,
1137}
1138
1139derive_display_from_serialize!(SearchType);