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 pub accessible: Option<bool>,
410 pub exists: Option<bool>,
411}
412
413#[derive(Debug, Deserialize, Clone)]
414#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
415#[serde(rename_all = "camelCase")]
416pub struct Media {
417 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
418 pub id: Option<String>,
419 pub duration: Option<u64>,
420 pub bitrate: Option<u32>,
421 pub width: Option<u32>,
422 pub height: Option<u32>,
423 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
424 pub aspect_ratio: Option<f32>,
425 pub audio_channels: Option<u8>,
426 pub protocol: Option<Protocol>,
427 pub audio_codec: Option<AudioCodec>,
428 pub video_codec: Option<VideoCodec>,
429 pub video_resolution: Option<String>,
430 pub container: Option<ContainerFormat>,
431 pub extension: Option<String>,
432 pub video_frame_rate: Option<String>,
433 pub audio_profile: Option<String>,
434 pub video_profile: Option<String>,
435 pub selected: Option<bool>,
436 #[serde(rename = "Part")]
437 pub parts: Vec<Part>,
438 #[serde(rename = "has64bitOffsets")]
439 pub has_64bit_offsets: Option<bool>,
440 #[serde(default, deserialize_with = "optional_boolish")]
441 pub optimized_for_streaming: Option<bool>,
442 pub display_offset: Option<u64>,
443 pub premium: Option<bool>,
444 #[serde(default, deserialize_with = "optional_boolish")]
445 pub has_voice_activity: Option<bool>,
446}
447
448#[derive(Debug, Deserialize, Clone)]
449#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
450pub struct Field {
451 pub locked: bool,
452 pub name: String,
453}
454
455#[derive(Debug, Deserialize, Clone)]
456#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
457pub struct Location {
458 pub path: String,
459}
460
461#[derive(Debug, Deserialize, Clone)]
462#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
463pub struct Tag {
464 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
465 pub id: Option<String>,
466 pub tag: String,
467 pub filter: Option<String>,
468 pub directory: Option<bool>,
469 #[serde(rename = "ratingKey")]
470 pub rating_key: Option<String>,
471 pub context: Option<String>,
472 pub slug: Option<String>,
473 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
474 pub count: Option<u32>,
475}
476
477#[derive(Debug, Deserialize, Clone)]
478#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
479pub struct Collection {
480 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
481 pub id: Option<String>,
482 pub art: Option<String>,
483 pub key: Option<String>,
484 pub thumb: Option<String>,
485 pub tag: String,
486 pub filter: Option<String>,
487 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
488 pub count: Option<u32>,
489 pub guid: Option<Guid>,
490 pub summary: Option<String>,
491}
492
493#[derive(Debug, Deserialize, Clone)]
494#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
495pub struct Review {
496 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
497 pub id: Option<String>,
498 pub tag: String,
499 pub filter: Option<String>,
500 pub text: String,
501 pub image: String,
502 pub link: Option<String>,
503 pub source: String,
504}
505
506#[derive(Debug, Deserialize, Clone)]
507#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
508#[serde(rename_all = "camelCase")]
509pub struct Rating {
510 #[serde(default, deserialize_with = "deserialize_number_from_string")]
511 pub count: u32,
512 pub image: String,
513 #[serde(rename = "type")]
514 pub rating_type: String,
515 #[serde(default, deserialize_with = "deserialize_number_from_string")]
516 pub value: f32,
517}
518
519#[derive(Debug, Deserialize, Clone)]
520#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
521#[serde(rename_all = "camelCase")]
522pub struct Role {
523 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
524 pub id: Option<String>,
525 pub tag: String,
526 pub key: Option<String>,
527 pub slug: Option<String>,
528 pub tag_key: Option<String>,
529 pub filter: Option<String>,
530 pub role: Option<String>,
531 pub thumb: Option<String>,
532 #[serde(rename = "type")]
533 pub role_type: Option<String>,
534 pub directory: Option<bool>,
535 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
536 pub count: Option<u32>,
537}
538
539#[derive(Debug, Deserialize, Clone)]
540#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
541#[serde(rename_all = "camelCase")]
542pub struct ParentMetadata {
543 pub parent_key: Option<String>,
544 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
545 pub parent_rating_key: Option<String>,
546 pub parent_guid: Option<Guid>,
547
548 pub parent_title: Option<String>,
549 pub parent_studio: Option<String>,
550 pub parent_year: Option<u32>,
551 pub parent_content_rating: Option<String>,
552 pub parent_index: Option<u32>,
553
554 pub parent_thumb: Option<String>,
555 pub parent_art: Option<String>,
556 pub parent_theme: Option<String>,
557}
558
559#[derive(Debug, Deserialize, Clone)]
560#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
561#[serde(rename_all = "camelCase")]
562pub struct GrandParentMetadata {
563 pub grandparent_key: Option<String>,
564 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
565 pub grandparent_rating_key: Option<String>,
566 pub grandparent_guid: Option<Guid>,
567
568 pub grandparent_title: Option<String>,
569 pub grandparent_studio: Option<String>,
570 pub grandparent_year: Option<u32>,
571 pub grandparent_content_rating: Option<String>,
572 pub grandparent_index: Option<u32>,
573
574 pub grandparent_thumb: Option<String>,
575 pub grandparent_art: Option<String>,
576 pub grandparent_theme: Option<String>,
577}
578
579#[derive(Debug, Deserialize, Clone)]
580#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
581#[serde(rename_all = "camelCase")]
582pub struct Extras {
583 pub size: u32,
584 pub key: Option<String>,
585 #[serde(default, rename = "Metadata")]
586 pub metadata: Vec<Box<Metadata>>,
587}
588
589#[derive(Debug, Deserialize, Clone)]
590#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
591#[serde(rename_all = "camelCase")]
592pub struct OnDeck {
593 #[serde(rename = "Metadata")]
594 pub metadata: Metadata,
595}
596
597#[derive(Debug, Deserialize, Clone)]
598#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
599#[serde(rename_all = "camelCase")]
600pub struct Chapter {
601 pub id: u32,
602 pub filter: Option<String>,
603 pub index: u32,
604 pub start_time_offset: u64,
605 pub end_time_offset: u64,
606 pub tag: Option<String>,
607 pub thumb: Option<String>,
608}
609
610pub(crate) fn deserialize_marker_type<'de, D>(deserializer: D) -> Result<MarkerType, D::Error>
611where
612 D: Deserializer<'de>,
613{
614 #[derive(Debug, Deserialize)]
615 #[serde(rename_all = "camelCase")]
616 struct Helper {
617 r#type: String,
618 r#final: Option<bool>,
619 }
620
621 let m = Helper::deserialize(deserializer)?;
622
623 match m.r#type.as_str() {
624 "intro" => Ok(MarkerType::Intro),
625 "credits" => Ok(MarkerType::Credits(m.r#final.unwrap_or_default())),
626 #[cfg(not(feature = "tests_deny_unknown_fields"))]
627 _ => Ok(MarkerType::Unknown(m.r#type)),
628 #[cfg(feature = "tests_deny_unknown_fields")]
629 _ => Err(serde::de::Error::unknown_variant(
630 m.r#type.as_str(),
631 &["credits", "intro"],
632 )),
633 }
634}
635
636#[derive(Debug, Clone)]
637pub enum MarkerType {
638 Credits(bool),
640 Intro,
641 #[cfg(not(feature = "tests_deny_unknown_fields"))]
642 Unknown(String),
643}
644
645#[derive(Debug, Deserialize, Clone)]
646#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
647#[serde(rename_all = "camelCase")]
648pub struct MarkerAttributes {
649 pub id: u32,
650 pub version: Option<u32>,
651}
652
653#[derive(Debug, Deserialize, Clone)]
654#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
655#[serde(rename_all = "camelCase")]
656pub struct Marker {
657 pub id: u32,
658 pub start_time_offset: u32,
659 pub end_time_offset: u32,
660 #[serde(flatten, deserialize_with = "deserialize_marker_type")]
661 pub marker_type: MarkerType,
662 #[serde(rename = "Attributes")]
663 pub attributes: MarkerAttributes,
664}
665
666#[derive(Debug, Deserialize, Clone)]
667#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
668#[serde(rename_all = "camelCase")]
669pub struct Metadata {
670 pub key: String,
671 pub rating_key: String,
672 pub guid: Option<Guid>,
673 pub primary_guid: Option<Guid>,
674
675 #[serde(flatten, deserialize_with = "deserialize_option_metadata_type")]
676 pub metadata_type: Option<MetadataType>,
677 #[serde(default, deserialize_with = "optional_boolish")]
678 pub smart: Option<bool>,
679 #[serde(default, deserialize_with = "optional_boolish")]
680 pub allow_sync: Option<bool>,
681
682 pub title: String,
683 pub title_sort: Option<String>,
684 pub original_title: Option<String>,
685 pub studio: Option<String>,
686 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
687 pub year: Option<u32>,
688 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
689 pub min_year: Option<u32>,
690 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
691 pub max_year: Option<u32>,
692 pub content_rating: Option<String>,
693 pub summary: Option<String>,
694 pub rating: Option<f32>,
695 pub rating_count: Option<u32>,
696 pub rating_image: Option<String>,
697 pub audience_rating: Option<f32>,
698 pub audience_rating_image: Option<String>,
699 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
700 pub user_rating: Option<f32>,
701 #[serde(
702 default,
703 deserialize_with = "deserialize_option_datetime_from_timestamp"
704 )]
705 pub last_rated_at: Option<OffsetDateTime>,
706 pub tagline: Option<String>,
707 pub duration: Option<u64>,
708 pub originally_available_at: Option<Date>,
709
710 pub thumb: Option<String>,
711 pub art: Option<String>,
712 pub theme: Option<String>,
713 pub composite: Option<String>,
714 pub banner: Option<String>,
715 pub icon: Option<String>,
716
717 pub index: Option<u32>,
718 #[serde(rename = "playlistItemID")]
719 pub playlist_item_id: Option<u32>,
720 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
721 pub child_count: Option<u32>,
722 pub season_count: Option<u32>,
723 pub leaf_count: Option<u32>,
724 pub viewed_leaf_count: Option<u32>,
725 pub skip_children: Option<bool>,
726
727 pub view_count: Option<u64>,
728 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
729 pub skip_count: Option<u64>,
730 #[serde(default, with = "time::serde::timestamp::option")]
731 pub last_viewed_at: Option<OffsetDateTime>,
732
733 #[serde(rename = "createdAtTZOffset")]
734 pub created_at_tz_offset: Option<String>,
735 pub created_at_accuracy: Option<String>,
736 #[serde(default, with = "time::serde::timestamp::option")]
737 pub added_at: Option<OffsetDateTime>,
738 #[serde(default, with = "time::serde::timestamp::option")]
739 pub deleted_at: Option<OffsetDateTime>,
740 #[serde(default, with = "time::serde::timestamp::option")]
741 pub updated_at: Option<OffsetDateTime>,
742 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
743 pub loudness_analysis_version: Option<u32>,
744 #[serde(default, deserialize_with = "optional_boolish")]
745 pub has_premium_extras: Option<bool>,
746 #[serde(default, deserialize_with = "optional_boolish")]
747 pub has_premium_primary_extra: Option<bool>,
748 pub view_offset: Option<u64>,
749 pub chapter_source: Option<ChapterSource>,
750 pub primary_extra_key: Option<String>,
751 #[serde(default, deserialize_with = "optional_boolish")]
752 pub has_premium_lyrics: Option<bool>,
753 pub music_analysis_version: Option<String>,
754
755 #[serde(rename = "librarySectionID")]
756 #[serde(default, deserialize_with = "deserialize_option_number_from_string")]
757 pub library_section_id: Option<u32>,
758 pub library_section_title: Option<String>,
759 pub library_section_key: Option<String>,
760
761 #[serde(flatten)]
762 pub parent: Box<ParentMetadata>,
763 #[serde(flatten)]
764 pub grand_parent: Box<GrandParentMetadata>,
765
766 #[serde(default, rename = "Guid")]
767 pub guids: Vec<Guid>,
768 #[serde(default, rename = "Collection")]
769 pub collections: Vec<Collection>,
770 #[serde(default, rename = "Similar")]
771 pub similar: Vec<Tag>,
772 #[serde(default, rename = "Genre")]
773 pub genres: Vec<Tag>,
774 #[serde(default, rename = "Director")]
775 pub directors: Vec<Role>,
776 #[serde(default, rename = "Producer")]
777 pub producers: Vec<Role>,
778 #[serde(default, rename = "Writer")]
779 pub writers: Vec<Role>,
780 #[serde(default, rename = "Country")]
781 pub countries: Vec<Tag>,
782 #[serde(default, rename = "Rating")]
783 pub ratings: Vec<Rating>,
784 #[serde(default, rename = "Role")]
785 pub roles: Vec<Role>,
786 #[serde(default, rename = "Location")]
787 pub locations: Vec<Location>,
788 #[serde(default, rename = "Field")]
789 pub fields: Vec<Field>,
790 #[serde(default, rename = "Mood")]
791 pub moods: Vec<Tag>,
792 #[serde(default, rename = "Format")]
793 pub formats: Vec<Tag>,
794 #[serde(default, rename = "Subformat")]
795 pub sub_formats: Vec<Tag>,
796 #[serde(default, rename = "Style")]
797 pub styles: Vec<Tag>,
798 #[serde(default, rename = "Review")]
799 pub reviews: Vec<Review>,
800 #[serde(default, rename = "Chapter")]
801 pub chapters: Vec<Chapter>,
802 #[serde(default, rename = "Label")]
803 pub labels: Vec<Tag>,
804
805 #[serde(rename = "Preferences")]
806 pub preferences: Option<Box<Preferences>>,
807
808 #[serde(rename = "Extras")]
809 pub extras: Option<Extras>,
810
811 #[serde(rename = "OnDeck")]
812 pub on_deck: Option<Box<OnDeck>>,
813
814 #[serde(default, rename = "Marker")]
815 pub markers: Vec<Marker>,
816
817 #[serde(rename = "Media")]
818 pub media: Option<Vec<Media>>,
819
820 #[serde(rename = "Vast")]
821 pub vast: Option<Vec<Link>>,
822
823 #[serde(rename = "publicPagesURL")]
824 pub public_pages_url: Option<String>,
825 pub slug: Option<String>,
826 pub user_state: Option<bool>,
827 pub imdb_rating_count: Option<u64>,
828 pub source: Option<String>,
829 #[serde(rename = "Image")]
830 pub image: Option<Vec<Image>>,
831 #[serde(rename = "Studio")]
832 pub studios: Option<Vec<Tag>>,
833
834 pub language_override: Option<String>,
835 pub content: Option<String>,
836 pub collection_sort: Option<String>,
837 #[serde(default, deserialize_with = "optional_boolish")]
838 pub skip_parent: Option<bool>,
839}
840
841#[derive(Deserialize, Debug, Clone)]
842pub struct Image {
843 pub url: String,
844 #[serde(rename = "type")]
845 pub image_type: String,
846 pub alt: String,
847}
848
849#[derive(Deserialize, Debug, Clone)]
850pub struct Link {
851 pub url: String,
852}
853
854#[derive(Debug, Deserialize, Clone)]
855#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
856#[serde(rename_all = "camelCase")]
857pub struct MetadataMediaContainer {
858 pub key: Option<String>,
859 #[serde(default, deserialize_with = "deserialize_option_string_from_number")]
860 pub rating_key: Option<String>,
861 pub augmentation_key: Option<String>,
862
863 pub title: Option<String>,
864 pub title1: Option<String>,
865 pub title2: Option<String>,
866 pub summary: Option<String>,
867 pub duration: Option<u64>,
868
869 #[serde(default, deserialize_with = "optional_boolish")]
870 pub allow_sync: Option<bool>,
871 #[serde(rename = "nocache")]
872 pub no_cache: Option<bool>,
873 pub sort_asc: Option<bool>,
874 pub smart: Option<bool>,
875
876 pub thumb: Option<String>,
877 pub art: Option<String>,
878 pub content: Option<String>,
879 pub theme: Option<String>,
880 pub composite: Option<String>,
881 pub banner: Option<String>,
882
883 #[serde(rename = "librarySectionID")]
884 pub library_section_id: Option<u32>,
885 pub library_section_title: Option<String>,
886 #[serde(rename = "librarySectionUUID")]
887 pub library_section_uuid: Option<String>,
888
889 #[serde(flatten)]
890 pub parent: ParentMetadata,
891 #[serde(flatten)]
892 pub grand_parent: GrandParentMetadata,
893 #[serde(flatten)]
894 pub media_container: MediaContainer,
895
896 pub media_tag_prefix: Option<String>,
897 #[serde(default, with = "time::serde::timestamp::option")]
898 pub media_tag_version: Option<OffsetDateTime>,
899 pub mixed_parents: Option<bool>,
900 pub view_group: Option<String>,
901 pub view_mode: Option<u32>,
902 pub leaf_count: Option<u32>,
903 pub playlist_type: Option<PlaylistMetadataType>,
904
905 #[serde(default, rename = "Directory")]
906 pub directories: Vec<Value>,
907 #[serde(default, rename = "Metadata")]
908 pub metadata: Vec<Metadata>,
909}
910
911#[derive(Debug, Deserialize, Clone, Copy)]
912#[serde(rename_all = "lowercase")]
913pub enum PivotType {
914 Hub,
915 List,
916 View,
917 #[cfg(not(feature = "tests_deny_unknown_fields"))]
918 #[serde(other)]
919 Unknown,
920}
921
922derive_fromstr_from_deserialize!(PivotType);
923
924#[derive(Debug, Deserialize, Clone)]
925#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
926#[serde(rename_all = "camelCase")]
927pub struct Pivot {
928 pub context: String,
929 pub id: String,
930 pub key: String,
931 pub symbol: String,
932 pub title: String,
933 pub requires: Option<String>,
934 #[serde(rename = "type")]
935 pub pivot_type: PivotType,
936}
937
938#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
939#[serde(rename_all = "lowercase")]
940pub enum LibraryType {
941 Movie,
942 Show,
943 Artist,
944 Photo,
945 Mixed,
946 Clip,
947 #[cfg(not(feature = "tests_deny_unknown_fields"))]
948 #[serde(other)]
949 Unknown,
950}
951
952derive_fromstr_from_deserialize!(LibraryType);
953derive_display_from_serialize!(LibraryType);
954
955#[derive(Debug, Deserialize, Clone)]
956#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
957#[serde(rename_all = "camelCase")]
958pub struct ServerLibrary {
959 #[serde(rename = "type")]
960 pub library_type: LibraryType,
961 #[serde(rename = "Pivot")]
962 pub pivots: Vec<Pivot>,
963 pub agent: String,
964 pub hub_key: String,
965 pub id: String,
966 pub key: String,
967 pub subtype: Option<String>,
968 pub language: String,
969 pub refreshing: bool,
970 #[serde(with = "time::serde::timestamp")]
971 pub scanned_at: OffsetDateTime,
972 pub scanner: String,
973 pub title: String,
974 #[serde(with = "time::serde::timestamp")]
975 pub updated_at: OffsetDateTime,
976 pub uuid: String,
977}
978
979#[derive(Debug, Deserialize, Clone)]
980#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
981#[serde(rename_all = "camelCase")]
982pub struct ServerPlaylists {
983 #[serde(rename = "type")]
984 _type: MustBe!("playlist"),
985 #[serde(rename = "Pivot")]
986 pub pivots: Vec<Pivot>,
987 pub id: String,
988 pub key: String,
989 pub title: String,
990}
991
992#[derive(Debug, Deserialize, Clone)]
993#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
994#[serde(rename_all = "camelCase")]
995pub struct ServerHome {
996 pub hub_key: String,
997 pub title: String,
998}
999
1000#[derive(Debug, Deserialize, Clone)]
1001#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
1002#[serde(rename_all = "camelCase")]
1003pub struct DvrLibrary {
1004 #[serde(rename = "type")]
1005 pub library_type: LibraryType,
1006 pub key: Option<String>,
1007 pub title: String,
1008 pub icon: String,
1009 #[serde(default, with = "time::serde::timestamp::option")]
1010 pub updated_at: Option<OffsetDateTime>,
1011}
1012
1013#[derive(Debug, Deserialize, Clone)]
1014#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
1015#[serde(rename_all = "camelCase")]
1016pub struct LiveTv {
1017 #[serde(rename = "Pivot")]
1018 pub pivots: Vec<Pivot>,
1019 pub id: String,
1020 pub title: String,
1021 pub hub_key: String,
1022}
1023
1024#[derive(Debug, Deserialize, Clone)]
1025#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
1026#[serde(rename_all = "camelCase")]
1027pub struct OnlineLibrary {
1028 #[serde(rename = "type")]
1029 pub library_type: LibraryType,
1030 pub key: Option<String>,
1031 pub title: String,
1032 pub icon: String,
1033 pub id: String,
1034 pub context: Option<String>,
1035 pub hub_key: Option<String>,
1036 #[serde(rename = "Pivot")]
1037 pub pivots: Vec<Pivot>,
1038 #[serde(default, with = "time::serde::timestamp::option")]
1039 pub updated_at: Option<OffsetDateTime>,
1040}
1041
1042#[derive(Debug, Deserialize, Clone)]
1043#[cfg_attr(not(feature = "tests_deny_unknown_fields"), serde(untagged))]
1044#[cfg_attr(feature = "tests_deny_unknown_fields", serde(try_from = "Value"))]
1045pub enum ContentDirectory {
1046 Playlists(ServerPlaylists),
1047 #[serde(rename_all = "camelCase")]
1048 Media(Box<ServerLibrary>),
1049 #[serde(rename_all = "camelCase")]
1050 Home(ServerHome),
1051 #[serde(rename_all = "camelCase")]
1052 LiveTv(LiveTv),
1053 #[serde(rename_all = "camelCase")]
1054 Online(OnlineLibrary),
1055 #[serde(rename_all = "camelCase")]
1056 Dvr(DvrLibrary),
1057 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1058 Unknown(Value),
1059}
1060
1061#[cfg(feature = "tests_deny_unknown_fields")]
1064impl TryFrom<Value> for ContentDirectory {
1065 type Error = String;
1066
1067 fn try_from(value: Value) -> Result<Self, Self::Error> {
1068 let obj = if let Value::Object(o) = &value {
1069 o
1070 } else {
1071 return Err("Failed to decode Directory. Data was not an object.".to_string());
1072 };
1073
1074 let directory_type = match obj.get("type") {
1075 Some(Value::String(n)) => n,
1076 Some(_) => {
1077 return Err("Failed to decode Directory. Unexpected type property.".to_string())
1078 }
1079 None => {
1080 if obj.contains_key("Pivot") {
1081 let live_tv: LiveTv = serde_json::from_value(value)
1082 .map_err(|e| format!("Failed to decode Live TV directory: {e}"))?;
1083 return Ok(Self::LiveTv(live_tv));
1084 }
1085
1086 let home: ServerHome = serde_json::from_value(value)
1087 .map_err(|e| format!("Failed to decode Home directory: {e}"))?;
1088 return Ok(Self::Home(home));
1089 }
1090 };
1091
1092 if directory_type.as_str() == "playlist" {
1093 let p: ServerPlaylists = serde_json::from_value(value)
1094 .map_err(|e| format!("Failed to decode playlist directory: {e}"))?;
1095 Ok(Self::Playlists(p))
1096 } else {
1097 if obj.contains_key("scannedAt") {
1101 let l: ServerLibrary = serde_json::from_value(value)
1102 .map_err(|e| format!("Failed to decode server library directory: {e}"))?;
1103 Ok(Self::Media(Box::new(l)))
1104 } else if obj.contains_key("id") {
1105 let l: OnlineLibrary = serde_json::from_value(value)
1106 .map_err(|e| format!("Failed to decode online library directory: {e}"))?;
1107 Ok(Self::Online(l))
1108 } else {
1109 let l: DvrLibrary = serde_json::from_value(value)
1110 .map_err(|e| format!("Failed to decode dvr library directory: {e}"))?;
1111 Ok(Self::Dvr(l))
1112 }
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Deserialize_repr, Clone, Copy, Serialize_repr)]
1118#[repr(u16)]
1119pub enum SearchType {
1120 Movie = 1,
1121 Show = 2,
1122 Season = 3,
1123 Episode = 4,
1124 Trailer = 5,
1125 Comic = 6,
1126 Person = 7,
1127 Artist = 8,
1128 Album = 9,
1129 Track = 10,
1130 Picture = 11,
1131 Clip = 12,
1132 Photo = 13,
1133 PhotoAlbum = 14,
1134 Playlist = 15,
1135 PlaylistFolder = 16,
1136 Collection = 18,
1137 OptimizedVersion = 42,
1138 UserPlaylistItem = 1001,
1139 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1140 #[serde(other)]
1141 Unknown,
1142}
1143
1144derive_display_from_serialize!(SearchType);