1use std::{future::Future, marker::PhantomData, ops::RangeBounds};
2
3use enum_dispatch::enum_dispatch;
4use futures::AsyncWrite;
5use http::StatusCode;
6use isahc::AsyncReadResponseExt;
7
8use crate::{
9 isahc_compat::StatusCodeExt,
10 media_container::{
11 server::library::{
12 CollectionMetadataSubtype, LibraryType, Media as MediaMetadata, Metadata,
13 MetadataMediaContainer, MetadataType, Part as PartMetadata, PlaylistMetadataType,
14 Protocol, SearchType, ServerLibrary,
15 },
16 MediaContainerWrapper,
17 },
18 transcode::{
19 download_queue::{DownloadQueue, QueueItem},
20 session::{create_transcode_session, TranscodeSession},
21 Context, MusicTranscodeOptions, TranscodeOptions, VideoTranscodeOptions,
22 },
23 Error, HttpClient, Result,
24};
25
26pub trait FromMetadata {
27 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self;
30}
31
32macro_rules! derive_from_metadata {
35 ($typ:ident) => {
36 impl FromMetadata for $typ {
37 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
38 Self { client, metadata }
39 }
40 }
41 };
42}
43
44#[enum_dispatch]
46pub trait MetadataItem {
47 fn metadata(&self) -> &Metadata;
49 fn client(&self) -> &HttpClient;
51
52 fn rating_key(&self) -> &str {
57 self.metadata().rating_key.as_str()
58 }
59
60 fn title(&self) -> &str {
62 &self.metadata().title
63 }
64}
65
66macro_rules! derive_metadata_item {
69 ($typ:ident) => {
70 impl MetadataItem for $typ {
71 fn metadata(&self) -> &Metadata {
72 &self.metadata
73 }
74
75 fn client(&self) -> &HttpClient {
76 &self.client
77 }
78 }
79 };
80 ($typ:ident<$gen:ident>) => {
81 impl<$gen> MetadataItem for $typ<$gen> {
82 fn metadata(&self) -> &Metadata {
83 &self.metadata
84 }
85
86 fn client(&self) -> &HttpClient {
87 &self.client
88 }
89 }
90 };
91}
92
93#[tracing::instrument(level = "trace", skip(client))]
95pub(crate) async fn metadata_items<T>(client: &HttpClient, path: &str) -> Result<Vec<T>>
96where
97 T: FromMetadata,
98{
99 let wrapper: MediaContainerWrapper<MetadataMediaContainer> = client.get(path).json().await?;
100
101 let media = wrapper
102 .media_container
103 .metadata
104 .into_iter()
105 .map(|metadata| {
106 T::from_metadata(
107 client.clone(),
108 Metadata {
109 library_section_id: metadata
110 .library_section_id
111 .or(wrapper.media_container.library_section_id),
112 library_section_title: metadata
113 .library_section_title
114 .or(wrapper.media_container.library_section_title.clone()),
115 ..metadata
116 },
117 )
118 })
119 .collect();
120 Ok(media)
121}
122
123#[tracing::instrument(level = "trace", skip_all, fields(item.rating_key = item.rating_key()))]
125async fn parent<T, P>(item: &T, client: &HttpClient) -> Result<Option<P>>
126where
127 T: MetadataItem,
128 P: FromMetadata,
129{
130 if let Some(ref parent_key) = item.metadata().parent.parent_key {
131 Ok(metadata_items(client, parent_key).await?.into_iter().next())
132 } else {
133 Ok(None)
134 }
135}
136
137#[tracing::instrument(level = "trace", skip(client, directory), fields(directory.key = directory.key))]
139async fn pivot_items<M>(
140 client: &HttpClient,
141 directory: &ServerLibrary,
142 context: &str,
143) -> Result<Vec<M>>
144where
145 M: FromMetadata,
146{
147 if let Some(pivot) = directory.pivots.iter().find(|p| p.context == context) {
148 metadata_items(client, &pivot.key).await
149 } else {
150 Ok(Vec::new())
151 }
152}
153
154#[derive(Debug, Clone)]
156pub struct Media<'a, M: MediaItem> {
157 _options: PhantomData<M>,
158 client: &'a HttpClient,
159 media_index: usize,
160 media: &'a MediaMetadata,
161 parent_metadata: &'a Metadata,
162}
163
164impl<'a, M: MediaItem> Media<'a, M> {
165 pub fn parts(&'_ self) -> Vec<Part<'_, M>> {
168 self.media
169 .parts
170 .iter()
171 .enumerate()
172 .map(|(index, part)| Part {
173 _options: self._options,
174 client: self.client,
175 media_index: self.media_index,
176 part_index: index,
177 parent_metadata: self.parent_metadata,
178 part,
179 })
180 .collect()
181 }
182
183 pub fn duration(&self) -> Option<u64> {
184 self.media.duration
185 }
186
187 pub fn metadata(&self) -> &MediaMetadata {
189 self.media
190 }
191}
192
193impl<'a, M: Transcodable + MediaItem + Sync> Transcodable for Media<'a, M> {
194 type Options = M::Options;
195
196 #[tracing::instrument(level = "debug", skip_all)]
197 async fn create_download_session(&self, options: M::Options) -> Result<TranscodeSession> {
198 create_transcode_session(
199 self.client,
200 self.parent_metadata,
201 Context::Static,
202 Protocol::Http,
203 Some(self.media_index),
204 None,
205 options,
206 )
207 .await
208 }
209
210 #[tracing::instrument(level = "debug", skip_all)]
211 async fn create_streaming_session(
212 &self,
213 protocol: Protocol,
214 options: M::Options,
215 ) -> Result<TranscodeSession> {
216 create_transcode_session(
217 self.client,
218 self.parent_metadata,
219 Context::Streaming,
220 protocol,
221 Some(self.media_index),
222 None,
223 options,
224 )
225 .await
226 }
227
228 #[tracing::instrument(level = "debug", skip_all)]
229 async fn queue_download(
230 &self,
231 options: Self::Options,
232 download_queue: Option<&DownloadQueue>,
233 ) -> Result<QueueItem> {
234 let queue = if let Some(q) = download_queue {
235 q.clone()
236 } else {
237 DownloadQueue::get_or_create(self.client.clone()).await?
238 };
239
240 queue
241 .add_item(self.parent_metadata, Some(self.media_index), None, options)
242 .await
243 }
244}
245
246#[derive(Debug, Clone)]
248pub struct Part<'a, M: MediaItem> {
249 _options: PhantomData<M>,
250 pub(crate) client: &'a HttpClient,
251 pub media_index: usize,
252 pub part_index: usize,
253 part: &'a PartMetadata,
254 parent_metadata: &'a Metadata,
255}
256
257impl<'a, M: MediaItem> Part<'a, M> {
258 #[allow(clippy::len_without_is_empty)]
260 pub fn len(&self) -> Option<u64> {
261 self.part.size
262 }
263
264 pub fn duration(&self) -> Option<u64> {
265 self.part.duration
266 }
267
268 #[tracing::instrument(level = "debug", skip_all)]
274 pub async fn download<W, R>(&self, writer: W, range: R) -> Result
275 where
276 W: AsyncWrite + Unpin,
277 R: RangeBounds<u64>,
278 {
279 let path = format!("{}?download=1", self.part.key.as_ref().unwrap());
280
281 let start = match range.start_bound() {
282 std::ops::Bound::Included(v) => *v,
283 std::ops::Bound::Excluded(v) => v + 1,
284 std::ops::Bound::Unbounded => 0,
285 };
286
287 let end = match range.end_bound() {
288 std::ops::Bound::Included(v) => Some(*v),
289 std::ops::Bound::Excluded(v) => Some(v - 1),
290 std::ops::Bound::Unbounded => None,
291 };
292
293 let mut builder = self.client.get(path).timeout(None);
294 if start != 0 || (end.is_some() && end != self.part.size) {
295 let end = end.map(|v| v.to_string()).unwrap_or_default();
297 builder = builder.header("Range", format!("bytes={start}-{end}"))
298 }
299
300 let mut response = builder.send().await?;
301 match response.status().as_http_status() {
302 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
303 response.copy_to(writer).await?;
304 Ok(())
305 }
306 _ => Err(crate::Error::from_response(response).await),
307 }
308 }
309
310 pub fn metadata(&self) -> &PartMetadata {
312 self.part
313 }
314}
315
316impl<'a, M: Transcodable + MediaItem + Sync> Transcodable for Part<'a, M> {
317 type Options = M::Options;
318
319 #[tracing::instrument(level = "debug", skip_all)]
320 async fn create_download_session(&self, options: M::Options) -> Result<TranscodeSession> {
321 create_transcode_session(
322 self.client,
323 self.parent_metadata,
324 Context::Static,
325 Protocol::Http,
326 Some(self.media_index),
327 Some(self.part_index),
328 options,
329 )
330 .await
331 }
332
333 #[tracing::instrument(level = "debug", skip_all)]
334 async fn create_streaming_session(
335 &self,
336 protocol: Protocol,
337 options: M::Options,
338 ) -> Result<TranscodeSession> {
339 create_transcode_session(
340 self.client,
341 self.parent_metadata,
342 Context::Streaming,
343 protocol,
344 Some(self.media_index),
345 Some(self.part_index),
346 options,
347 )
348 .await
349 }
350
351 #[tracing::instrument(level = "debug", skip_all)]
352 async fn queue_download(
353 &self,
354 options: Self::Options,
355 download_queue: Option<&DownloadQueue>,
356 ) -> Result<QueueItem> {
357 let queue = if let Some(q) = download_queue {
358 q.clone()
359 } else {
360 DownloadQueue::get_or_create(self.client.clone()).await?
361 };
362
363 queue
364 .add_item(
365 self.parent_metadata,
366 Some(self.media_index),
367 Some(self.part_index),
368 options,
369 )
370 .await
371 }
372}
373
374pub trait MediaItem: MetadataItem + Sized {
378 fn media(&'_ self) -> Vec<Media<'_, Self>> {
380 let metadata = self.metadata();
381 if let Some(ref media) = metadata.media {
382 media
383 .iter()
384 .enumerate()
385 .map(|(index, media)| Media {
386 _options: PhantomData,
387 client: self.client(),
388 media_index: index,
389 parent_metadata: metadata,
390 media,
391 })
392 .collect()
393 } else {
394 Vec::new()
395 }
396 }
397}
398
399pub trait Transcodable {
400 type Options: TranscodeOptions + Send;
401
402 fn create_download_session(
410 &self,
411 options: Self::Options,
412 ) -> impl Future<Output = Result<TranscodeSession>> + Send;
413
414 fn create_streaming_session(
419 &self,
420 protocol: Protocol,
421 options: Self::Options,
422 ) -> impl Future<Output = Result<TranscodeSession>> + Send;
423
424 fn queue_download(
431 &self,
432 options: Self::Options,
433 download_queue: Option<&DownloadQueue>,
434 ) -> impl Future<Output = Result<QueueItem>> + Send;
435}
436
437#[enum_dispatch(MetadataItem)]
439#[derive(Debug, Clone)]
440pub enum Video {
441 Movie,
442 Episode,
443}
444
445impl FromMetadata for Video {
446 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
447 if let Some(MetadataType::Episode) = metadata.metadata_type {
448 Episode::from_metadata(client, metadata).into()
449 } else {
450 Movie::from_metadata(client, metadata).into()
451 }
452 }
453}
454
455impl MediaItem for Video {}
456impl Transcodable for Video {
457 type Options = VideoTranscodeOptions;
458
459 #[tracing::instrument(level = "debug", skip_all)]
460 async fn create_download_session(&self, options: Self::Options) -> Result<TranscodeSession> {
461 create_transcode_session(
462 self.client(),
463 self.metadata(),
464 Context::Static,
465 Protocol::Http,
466 None,
467 None,
468 options,
469 )
470 .await
471 }
472
473 #[tracing::instrument(level = "debug", skip_all)]
474 async fn create_streaming_session(
475 &self,
476 protocol: Protocol,
477 options: Self::Options,
478 ) -> Result<TranscodeSession> {
479 create_transcode_session(
480 self.client(),
481 self.metadata(),
482 Context::Streaming,
483 protocol,
484 None,
485 None,
486 options,
487 )
488 .await
489 }
490
491 #[tracing::instrument(level = "debug", skip_all)]
492 async fn queue_download(
493 &self,
494 options: Self::Options,
495 download_queue: Option<&DownloadQueue>,
496 ) -> Result<QueueItem> {
497 let queue = if let Some(q) = download_queue {
498 q.clone()
499 } else {
500 DownloadQueue::get_or_create(self.client().clone()).await?
501 };
502
503 queue.add_item(self.metadata(), None, None, options).await
504 }
505}
506
507#[derive(Debug, Clone)]
508pub struct Playlist<M> {
509 _items: PhantomData<M>,
510 client: HttpClient,
511 metadata: Metadata,
512}
513
514impl<M> FromMetadata for Playlist<M> {
515 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
516 Self {
517 _items: PhantomData,
518 client,
519 metadata,
520 }
521 }
522}
523
524derive_metadata_item!(Playlist<M>);
525
526impl<M> Playlist<M>
527where
528 M: FromMetadata,
529{
530 #[tracing::instrument(level = "debug", skip_all)]
531 pub async fn children(&self) -> Result<Vec<M>> {
532 metadata_items(&self.client, &self.metadata.key).await
533 }
534}
535
536#[derive(Debug, Clone)]
537pub struct Collection<M> {
538 _items: PhantomData<M>,
539 client: HttpClient,
540 metadata: Metadata,
541}
542
543impl<M> FromMetadata for Collection<M> {
544 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
545 Self {
546 _items: PhantomData,
547 client,
548 metadata,
549 }
550 }
551}
552
553derive_metadata_item!(Collection<M>);
554
555impl<M> Collection<M>
556where
557 M: FromMetadata,
558{
559 #[tracing::instrument(level = "debug", skip_all)]
560 pub async fn children(&self) -> Result<Vec<M>> {
561 metadata_items(&self.client, &self.metadata.key).await
562 }
563}
564
565#[derive(Debug, Clone)]
566pub struct Movie {
567 client: HttpClient,
568 metadata: Metadata,
569}
570
571derive_from_metadata!(Movie);
572derive_metadata_item!(Movie);
573
574impl MediaItem for Movie {}
575impl Transcodable for Movie {
576 type Options = VideoTranscodeOptions;
577
578 #[tracing::instrument(level = "debug", skip_all)]
579 async fn create_download_session(&self, options: Self::Options) -> Result<TranscodeSession> {
580 create_transcode_session(
581 self.client(),
582 self.metadata(),
583 Context::Static,
584 Protocol::Http,
585 None,
586 None,
587 options,
588 )
589 .await
590 }
591
592 #[tracing::instrument(level = "debug", skip_all)]
593 async fn create_streaming_session(
594 &self,
595 protocol: Protocol,
596 options: Self::Options,
597 ) -> Result<TranscodeSession> {
598 create_transcode_session(
599 self.client(),
600 self.metadata(),
601 Context::Streaming,
602 protocol,
603 None,
604 None,
605 options,
606 )
607 .await
608 }
609
610 #[tracing::instrument(level = "debug", skip_all)]
611 async fn queue_download(
612 &self,
613 options: Self::Options,
614 download_queue: Option<&DownloadQueue>,
615 ) -> Result<QueueItem> {
616 let queue = if let Some(q) = download_queue {
617 q.clone()
618 } else {
619 DownloadQueue::get_or_create(self.client.clone()).await?
620 };
621
622 queue.add_item(self.metadata(), None, None, options).await
623 }
624}
625
626#[derive(Debug, Clone)]
627pub struct Show {
628 client: HttpClient,
629 metadata: Metadata,
630}
631
632derive_from_metadata!(Show);
633derive_metadata_item!(Show);
634
635impl Show {
636 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
638 pub async fn seasons(&self) -> Result<Vec<Season>> {
639 metadata_items(&self.client, &self.metadata.key).await
640 }
641
642 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.rating_key = self.metadata.rating_key))]
644 pub async fn episodes(&self) -> Result<Vec<Episode>> {
645 let path = format!("/library/metadata/{}/allLeaves", self.metadata.rating_key);
646 metadata_items(&self.client, &path).await
647 }
648}
649
650#[derive(Debug, Clone)]
651pub struct Season {
652 client: HttpClient,
653 metadata: Metadata,
654}
655
656derive_from_metadata!(Season);
657derive_metadata_item!(Season);
658
659impl Season {
660 pub fn season_number(&self) -> Option<u32> {
661 self.metadata.index
662 }
663
664 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
666 pub async fn episodes(&self) -> Result<Vec<Episode>> {
667 metadata_items(&self.client, &self.metadata.key).await
668 }
669
670 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
672 pub async fn show(&self) -> Result<Option<Show>> {
673 parent(self, &self.client).await
674 }
675}
676
677#[derive(Debug, Clone)]
678pub struct Episode {
679 client: HttpClient,
680 metadata: Metadata,
681}
682
683derive_from_metadata!(Episode);
684derive_metadata_item!(Episode);
685
686impl MediaItem for Episode {}
687impl Transcodable for Episode {
688 type Options = VideoTranscodeOptions;
689
690 #[tracing::instrument(level = "debug", skip_all)]
691 async fn create_download_session(&self, options: Self::Options) -> Result<TranscodeSession> {
692 create_transcode_session(
693 self.client(),
694 self.metadata(),
695 Context::Static,
696 Protocol::Http,
697 None,
698 None,
699 options,
700 )
701 .await
702 }
703
704 #[tracing::instrument(level = "debug", skip_all)]
705 async fn create_streaming_session(
706 &self,
707 protocol: Protocol,
708 options: Self::Options,
709 ) -> Result<TranscodeSession> {
710 create_transcode_session(
711 self.client(),
712 self.metadata(),
713 Context::Streaming,
714 protocol,
715 None,
716 None,
717 options,
718 )
719 .await
720 }
721
722 #[tracing::instrument(level = "debug", skip_all)]
723 async fn queue_download(
724 &self,
725 options: Self::Options,
726 download_queue: Option<&DownloadQueue>,
727 ) -> Result<QueueItem> {
728 let queue = if let Some(q) = download_queue {
729 q.clone()
730 } else {
731 DownloadQueue::get_or_create(self.client.clone()).await?
732 };
733
734 queue.add_item(self.metadata(), None, None, options).await
735 }
736}
737
738impl Episode {
739 pub fn season_number(&self) -> Option<u32> {
741 self.metadata.parent.parent_index
742 }
743
744 pub fn episode_number(&self) -> Option<u32> {
746 self.metadata.index
747 }
748
749 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
751 pub async fn season(&self) -> Result<Option<Season>> {
752 parent(self, &self.client).await
753 }
754}
755
756#[derive(Debug, Clone)]
757pub struct Artist {
758 client: HttpClient,
759 metadata: Metadata,
760}
761
762derive_from_metadata!(Artist);
763derive_metadata_item!(Artist);
764
765impl Artist {
766 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
768 pub async fn full_studio_albums(&self) -> Result<Vec<MusicAlbum>> {
769 metadata_items(&self.client, &self.metadata.key).await
770 }
771
772 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
774 pub async fn albums(&self) -> Result<Vec<MusicAlbum>> {
775 let section_id = match &self.metadata.library_section_id {
776 Some(id) => id,
777 None => return Err(Error::UnexpectedError),
778 };
779
780 let albums_search_path = format!(
781 "/library/sections/{}/all?type={}&artist.id={}",
782 section_id,
783 SearchType::Album,
784 self.metadata.rating_key
785 );
786 metadata_items(&self.client, &albums_search_path).await
787 }
788}
789
790#[derive(Debug, Clone)]
791pub struct MusicAlbum {
792 client: HttpClient,
793 metadata: Metadata,
794}
795
796derive_from_metadata!(MusicAlbum);
797derive_metadata_item!(MusicAlbum);
798
799impl MusicAlbum {
800 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
802 pub async fn tracks(&self) -> Result<Vec<Track>> {
803 metadata_items(&self.client, &self.metadata.key).await
804 }
805
806 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
808 pub async fn artist(&self) -> Result<Option<Artist>> {
809 parent(self, &self.client).await
810 }
811}
812
813#[derive(Debug, Clone)]
814pub struct Track {
815 client: HttpClient,
816 metadata: Metadata,
817}
818
819derive_from_metadata!(Track);
820derive_metadata_item!(Track);
821
822impl MediaItem for Track {}
823impl Transcodable for Track {
824 type Options = MusicTranscodeOptions;
825
826 #[tracing::instrument(level = "debug", skip_all)]
827 async fn create_download_session(&self, options: Self::Options) -> Result<TranscodeSession> {
828 create_transcode_session(
829 self.client(),
830 self.metadata(),
831 Context::Static,
832 Protocol::Http,
833 None,
834 None,
835 options,
836 )
837 .await
838 }
839
840 #[tracing::instrument(level = "debug", skip_all)]
841 async fn create_streaming_session(
842 &self,
843 protocol: Protocol,
844 options: Self::Options,
845 ) -> Result<TranscodeSession> {
846 create_transcode_session(
847 self.client(),
848 self.metadata(),
849 Context::Streaming,
850 protocol,
851 None,
852 None,
853 options,
854 )
855 .await
856 }
857
858 #[tracing::instrument(level = "debug", skip_all)]
859 async fn queue_download(
860 &self,
861 options: Self::Options,
862 download_queue: Option<&DownloadQueue>,
863 ) -> Result<QueueItem> {
864 let queue = if let Some(q) = download_queue {
865 q.clone()
866 } else {
867 DownloadQueue::get_or_create(self.client.clone()).await?
868 };
869
870 queue.add_item(self.metadata(), None, None, options).await
871 }
872}
873
874impl Track {
875 pub fn track_number(&self) -> Option<u32> {
877 self.metadata.index
878 }
879
880 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
882 pub async fn album(&self) -> Result<Option<MusicAlbum>> {
883 parent(self, &self.client).await
884 }
885}
886
887#[derive(Debug, Clone)]
888pub struct Photo {
889 client: HttpClient,
890 metadata: Metadata,
891}
892
893derive_from_metadata!(Photo);
894derive_metadata_item!(Photo);
895
896impl MediaItem for Photo {}
897
898impl Photo {
899 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
901 pub async fn album(&self) -> Result<Option<PhotoAlbum>> {
902 parent(self, &self.client).await
903 }
904}
905
906#[derive(Debug, Clone)]
907pub struct PhotoAlbum {
908 client: HttpClient,
909 metadata: Metadata,
910}
911
912derive_from_metadata!(PhotoAlbum);
913derive_metadata_item!(PhotoAlbum);
914
915impl PhotoAlbum {
916 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
918 pub async fn contents(&self) -> Result<Vec<PhotoAlbumItem>> {
919 metadata_items(&self.client, &self.metadata.key).await
920 }
921
922 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
924 pub async fn album(&self) -> Result<Option<PhotoAlbum>> {
925 parent(self, &self.client).await
926 }
927}
928
929#[enum_dispatch(MetadataItem)]
930pub enum PhotoAlbumItem {
931 PhotoAlbum,
932 Photo,
933}
934
935impl FromMetadata for PhotoAlbumItem {
936 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
937 if metadata.key.ends_with("/children") {
939 PhotoAlbum::from_metadata(client, metadata).into()
940 } else {
941 Photo::from_metadata(client, metadata).into()
942 }
943 }
944}
945
946#[derive(Debug, Clone)]
947pub struct Clip {
948 client: HttpClient,
949 metadata: Metadata,
950}
951
952derive_from_metadata!(Clip);
953derive_metadata_item!(Clip);
954
955impl MediaItem for Clip {}
956
957#[derive(Debug, Clone)]
958pub struct UnknownItem {
959 client: HttpClient,
960 metadata: Metadata,
961}
962
963derive_from_metadata!(UnknownItem);
964derive_metadata_item!(UnknownItem);
965
966#[enum_dispatch(MetadataItem)]
967#[derive(Debug, Clone)]
968pub enum Item {
969 Movie,
970 Episode,
971 Photo,
972 Show,
973 Artist,
974 MusicAlbum,
975 Season,
976 Track,
977 Clip,
978 MovieCollection(Collection<Movie>),
979 ShowCollection(Collection<Show>),
980 VideoPlaylist(Playlist<Video>),
981 PhotoPlaylist(Playlist<Photo>),
982 MusicPlaylist(Playlist<Track>),
983 UnknownItem,
984}
985
986impl MediaItem for Item {}
987
988impl FromMetadata for Item {
989 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
990 if let Some(ref item_type) = metadata.metadata_type {
991 match item_type {
992 MetadataType::Movie => Movie::from_metadata(client, metadata).into(),
993 MetadataType::Episode => Episode::from_metadata(client, metadata).into(),
994 MetadataType::Photo => Photo::from_metadata(client, metadata).into(),
995 MetadataType::Show => Show::from_metadata(client, metadata).into(),
996 MetadataType::Artist => Artist::from_metadata(client, metadata).into(),
997 MetadataType::MusicAlbum => MusicAlbum::from_metadata(client, metadata).into(),
998 MetadataType::Season => Season::from_metadata(client, metadata).into(),
999 MetadataType::Track => Track::from_metadata(client, metadata).into(),
1000 MetadataType::Clip(_) => Clip::from_metadata(client, metadata).into(),
1001 MetadataType::Collection(CollectionMetadataSubtype::Movie) => {
1002 Collection::<Movie>::from_metadata(client, metadata).into()
1003 }
1004 MetadataType::Collection(CollectionMetadataSubtype::Show) => {
1005 Collection::<Show>::from_metadata(client, metadata).into()
1006 }
1007 MetadataType::Playlist(PlaylistMetadataType::Video) => {
1008 Playlist::<Video>::from_metadata(client, metadata).into()
1009 }
1010 MetadataType::Playlist(PlaylistMetadataType::Audio) => {
1011 Playlist::<Track>::from_metadata(client, metadata).into()
1012 }
1013 MetadataType::Playlist(PlaylistMetadataType::Photo) => {
1014 Playlist::<Photo>::from_metadata(client, metadata).into()
1015 }
1016 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1017 _ => UnknownItem::from_metadata(client, metadata).into(),
1018 }
1019 } else {
1020 UnknownItem::from_metadata(client, metadata).into()
1021 }
1022 }
1023}
1024
1025#[derive(Debug, Clone)]
1026pub struct MovieLibrary {
1027 client: HttpClient,
1028 directory: ServerLibrary,
1029}
1030
1031impl MovieLibrary {
1032 pub fn title(&self) -> &str {
1034 &self.directory.title
1035 }
1036
1037 #[tracing::instrument(level = "debug", skip_all)]
1039 pub async fn movies(&self) -> Result<Vec<Movie>> {
1040 pivot_items(&self.client, &self.directory, "content.library").await
1041 }
1042
1043 #[tracing::instrument(level = "debug", skip_all)]
1045 pub async fn collections(&self) -> Result<Vec<Collection<Movie>>> {
1046 pivot_items(&self.client, &self.directory, "content.collections").await
1047 }
1048
1049 #[tracing::instrument(level = "debug", skip_all)]
1051 pub async fn playlists(&self) -> Result<Vec<Playlist<Video>>> {
1052 pivot_items(&self.client, &self.directory, "content.playlists").await
1053 }
1054}
1055
1056#[derive(Debug, Clone)]
1057pub struct TVLibrary {
1058 client: HttpClient,
1059 directory: ServerLibrary,
1060}
1061
1062impl TVLibrary {
1063 pub fn title(&self) -> &str {
1065 &self.directory.title
1066 }
1067
1068 #[tracing::instrument(level = "debug", skip_all)]
1070 pub async fn shows(&self) -> Result<Vec<Show>> {
1071 pivot_items(&self.client, &self.directory, "content.library").await
1072 }
1073
1074 #[tracing::instrument(level = "debug", skip_all)]
1076 pub async fn collections(&self) -> Result<Vec<Collection<Show>>> {
1077 pivot_items(&self.client, &self.directory, "content.collections").await
1078 }
1079
1080 #[tracing::instrument(level = "debug", skip_all)]
1082 pub async fn playlists(&self) -> Result<Vec<Playlist<Video>>> {
1083 pivot_items(&self.client, &self.directory, "content.playlists").await
1084 }
1085}
1086
1087#[derive(Debug, Clone)]
1088pub struct MusicLibrary {
1089 client: HttpClient,
1090 directory: ServerLibrary,
1091}
1092
1093impl MusicLibrary {
1094 pub fn title(&self) -> &str {
1096 &self.directory.title
1097 }
1098
1099 #[tracing::instrument(level = "debug", skip_all)]
1101 pub async fn artists(&self) -> Result<Vec<Artist>> {
1102 pivot_items(&self.client, &self.directory, "content.library").await
1103 }
1104
1105 #[tracing::instrument(level = "debug", skip_all)]
1107 pub async fn playlists(&self) -> Result<Vec<Playlist<Track>>> {
1108 pivot_items(&self.client, &self.directory, "content.playlists").await
1109 }
1110}
1111
1112#[derive(Debug, Clone)]
1113pub struct PhotoLibrary {
1114 client: HttpClient,
1115 directory: ServerLibrary,
1116}
1117
1118impl PhotoLibrary {
1119 pub fn title(&self) -> &str {
1121 &self.directory.title
1122 }
1123
1124 #[tracing::instrument(level = "debug", skip_all)]
1126 pub async fn albums(&self) -> Result<Vec<PhotoAlbum>> {
1127 pivot_items(&self.client, &self.directory, "content.library").await
1128 }
1129
1130 #[tracing::instrument(level = "debug", skip_all)]
1132 pub async fn playlists(&self) -> Result<Vec<Playlist<Photo>>> {
1133 pivot_items(&self.client, &self.directory, "content.playlists").await
1134 }
1135}
1136
1137#[derive(Debug, Clone)]
1138pub enum Library {
1139 Movie(MovieLibrary),
1140 TV(TVLibrary),
1141 Music(MusicLibrary),
1142 Video(MovieLibrary),
1143 Photo(PhotoLibrary),
1144}
1145
1146impl Library {
1147 pub(super) fn new(client: HttpClient, directory: ServerLibrary) -> Self {
1148 match directory.library_type {
1149 LibraryType::Movie => {
1150 if directory.subtype.as_deref() == Some("clip") {
1151 Library::Video(MovieLibrary { client, directory })
1152 } else {
1153 Library::Movie(MovieLibrary { client, directory })
1154 }
1155 }
1156 LibraryType::Show => Library::TV(TVLibrary { client, directory }),
1157 LibraryType::Artist => Library::Music(MusicLibrary { client, directory }),
1158 LibraryType::Photo => Library::Photo(PhotoLibrary { client, directory }),
1159 LibraryType::Mixed => todo!("Mixed library type is not supported yet"),
1160 LibraryType::Clip => todo!("Clip library type is not supported yet"),
1161 #[cfg(not(feature = "tests_deny_unknown_fields"))]
1162 LibraryType::Unknown => panic!("Unknown library type"),
1163 }
1164 }
1165
1166 fn directory(&self) -> &ServerLibrary {
1167 match self {
1168 Self::Movie(l) => &l.directory,
1169 Self::TV(l) => &l.directory,
1170 Self::Music(l) => &l.directory,
1171 Self::Video(l) => &l.directory,
1172 Self::Photo(l) => &l.directory,
1173 }
1174 }
1175
1176 pub fn id(&self) -> &str {
1178 &self.directory().id
1179 }
1180
1181 pub fn title(&self) -> &str {
1183 &self.directory().title
1184 }
1185
1186 pub fn library_type(&self) -> &LibraryType {
1187 &self.directory().library_type
1188 }
1189}