1use std::{marker::PhantomData, ops::RangeBounds};
2
3use enum_dispatch::enum_dispatch;
4use futures::AsyncWrite;
5use http::StatusCode;
6use isahc::AsyncReadResponseExt;
7
8use crate::{
9 media_container::{
10 server::library::{
11 CollectionMetadataSubtype, LibraryType, Media as MediaMetadata, Metadata,
12 MetadataMediaContainer, MetadataType, Part as PartMetadata, PlaylistMetadataType,
13 Protocol, SearchType, ServerLibrary,
14 },
15 MediaContainerWrapper,
16 },
17 transcode::{MusicTranscodeOptions, TranscodeSession, VideoTranscodeOptions},
18 Error, HttpClient, Result,
19};
20
21use super::transcode::{create_transcode_session, Context, TranscodeOptions};
22
23pub trait FromMetadata {
24 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self;
27}
28
29macro_rules! derive_from_metadata {
32 ($typ:ident) => {
33 impl FromMetadata for $typ {
34 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
35 Self { client, metadata }
36 }
37 }
38 };
39}
40
41#[enum_dispatch]
43pub trait MetadataItem {
44 fn metadata(&self) -> &Metadata;
46 fn client(&self) -> &HttpClient;
48
49 fn rating_key(&self) -> &str {
54 self.metadata().rating_key.as_str()
55 }
56
57 fn title(&self) -> &str {
59 &self.metadata().title
60 }
61}
62
63macro_rules! derive_metadata_item {
66 ($typ:ident) => {
67 impl MetadataItem for $typ {
68 fn metadata(&self) -> &Metadata {
69 &self.metadata
70 }
71
72 fn client(&self) -> &HttpClient {
73 &self.client
74 }
75 }
76 };
77 ($typ:ident<$gen:ident>) => {
78 impl<$gen> MetadataItem for $typ<$gen> {
79 fn metadata(&self) -> &Metadata {
80 &self.metadata
81 }
82
83 fn client(&self) -> &HttpClient {
84 &self.client
85 }
86 }
87 };
88}
89
90#[tracing::instrument(level = "trace", skip(client))]
92pub(crate) async fn metadata_items<T>(client: &HttpClient, path: &str) -> Result<Vec<T>>
93where
94 T: FromMetadata,
95{
96 let wrapper: MediaContainerWrapper<MetadataMediaContainer> = client.get(path).json().await?;
97
98 let media = wrapper
99 .media_container
100 .metadata
101 .into_iter()
102 .map(|metadata| {
103 T::from_metadata(
104 client.clone(),
105 Metadata {
106 library_section_id: metadata
107 .library_section_id
108 .or(wrapper.media_container.library_section_id),
109 library_section_title: metadata
110 .library_section_title
111 .or(wrapper.media_container.library_section_title.clone()),
112 ..metadata
113 },
114 )
115 })
116 .collect();
117 Ok(media)
118}
119
120#[tracing::instrument(level = "trace", skip_all, fields(item.rating_key = item.rating_key()))]
122async fn parent<T, P>(item: &T, client: &HttpClient) -> Result<Option<P>>
123where
124 T: MetadataItem,
125 P: FromMetadata,
126{
127 if let Some(ref parent_key) = item.metadata().parent.parent_key {
128 Ok(metadata_items(client, parent_key).await?.into_iter().next())
129 } else {
130 Ok(None)
131 }
132}
133
134#[tracing::instrument(level = "trace", skip(client, directory), fields(directory.key = directory.key))]
136async fn pivot_items<M>(
137 client: &HttpClient,
138 directory: &ServerLibrary,
139 context: &str,
140) -> Result<Vec<M>>
141where
142 M: FromMetadata,
143{
144 if let Some(pivot) = directory.pivots.iter().find(|p| p.context == context) {
145 metadata_items(client, &pivot.key).await
146 } else {
147 Ok(Vec::new())
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct Media<'a, M: MediaItem> {
154 _options: PhantomData<M>,
155 client: &'a HttpClient,
156 media_index: usize,
157 media: &'a MediaMetadata,
158 parent_metadata: &'a Metadata,
159}
160
161impl<'a, M: MediaItem> Media<'a, M> {
162 pub fn parts(&self) -> Vec<Part<M>> {
165 self.media
166 .parts
167 .iter()
168 .enumerate()
169 .map(|(index, part)| Part {
170 _options: self._options,
171 client: self.client,
172 media_index: self.media_index,
173 part_index: index,
174 parent_metadata: self.parent_metadata,
175 part,
176 })
177 .collect()
178 }
179
180 pub fn metadata(&self) -> &MediaMetadata {
182 self.media
183 }
184}
185
186#[derive(Debug, Clone)]
188pub struct Part<'a, M: MediaItem> {
189 _options: PhantomData<M>,
190 pub(crate) client: &'a HttpClient,
191 pub media_index: usize,
192 pub part_index: usize,
193 part: &'a PartMetadata,
194 parent_metadata: &'a Metadata,
195}
196
197impl<'a, M: MediaItem> Part<'a, M> {
198 #[allow(clippy::len_without_is_empty)]
200 pub fn len(&self) -> Option<u64> {
201 self.part.size
202 }
203
204 #[tracing::instrument(level = "debug", skip_all)]
210 pub async fn download<W, R>(&self, writer: W, range: R) -> Result
211 where
212 W: AsyncWrite + Unpin,
213 R: RangeBounds<u64>,
214 {
215 let path = format!("{}?download=1", self.part.key.as_ref().unwrap());
216
217 let start = match range.start_bound() {
218 std::ops::Bound::Included(v) => *v,
219 std::ops::Bound::Excluded(v) => v + 1,
220 std::ops::Bound::Unbounded => 0,
221 };
222
223 let end = match range.end_bound() {
224 std::ops::Bound::Included(v) => Some(*v),
225 std::ops::Bound::Excluded(v) => Some(v - 1),
226 std::ops::Bound::Unbounded => None,
227 };
228
229 let mut builder = self.client.get(path).timeout(None);
230 if start != 0 || (end.is_some() && end != self.part.size) {
231 let end = end.map(|v| v.to_string()).unwrap_or_default();
233 builder = builder.header("Range", format!("bytes={start}-{end}"))
234 }
235
236 let mut response = builder.send().await?;
237 match response.status() {
238 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
239 response.copy_to(writer).await?;
240 Ok(())
241 }
242 _ => Err(crate::Error::from_response(response).await),
243 }
244 }
245
246 pub fn metadata(&self) -> &PartMetadata {
248 self.part
249 }
250}
251
252impl<'a, M: MediaItemWithTranscoding> Part<'a, M> {
253 #[tracing::instrument(level = "debug", skip_all)]
260 pub async fn create_download_session(&self, options: M::Options) -> Result<TranscodeSession> {
261 create_transcode_session(
262 self.parent_metadata,
263 self,
264 Context::Static,
265 Protocol::Http,
266 options,
267 )
268 .await
269 }
270
271 #[tracing::instrument(level = "debug", skip_all)]
276 pub async fn create_streaming_session(
277 &self,
278 protocol: Protocol,
279 options: M::Options,
280 ) -> Result<TranscodeSession> {
281 create_transcode_session(
282 self.parent_metadata,
283 self,
284 Context::Streaming,
285 protocol,
286 options,
287 )
288 .await
289 }
290}
291
292pub trait MediaItem: MetadataItem + Sized {
296 fn media(&self) -> Vec<Media<Self>> {
298 let metadata = self.metadata();
299 if let Some(ref media) = metadata.media {
300 media
301 .iter()
302 .enumerate()
303 .map(|(index, media)| Media {
304 _options: PhantomData,
305 client: self.client(),
306 media_index: index,
307 parent_metadata: metadata,
308 media,
309 })
310 .collect()
311 } else {
312 Vec::new()
313 }
314 }
315}
316
317pub trait MediaItemWithTranscoding: MediaItem {
318 type Options: TranscodeOptions;
319}
320
321#[enum_dispatch(MetadataItem)]
323#[derive(Debug, Clone)]
324pub enum Video {
325 Movie,
326 Episode,
327}
328
329impl FromMetadata for Video {
330 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
331 if let Some(MetadataType::Episode) = metadata.metadata_type {
332 Episode::from_metadata(client, metadata).into()
333 } else {
334 Movie::from_metadata(client, metadata).into()
335 }
336 }
337}
338
339impl MediaItem for Video {}
340impl MediaItemWithTranscoding for Video {
341 type Options = VideoTranscodeOptions;
342}
343
344#[derive(Debug, Clone)]
345pub struct Playlist<M> {
346 _items: PhantomData<M>,
347 client: HttpClient,
348 metadata: Metadata,
349}
350
351impl<M> FromMetadata for Playlist<M> {
352 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
353 Self {
354 _items: PhantomData,
355 client,
356 metadata,
357 }
358 }
359}
360
361derive_metadata_item!(Playlist<M>);
362
363impl<M> Playlist<M>
364where
365 M: FromMetadata,
366{
367 #[tracing::instrument(level = "debug", skip_all)]
368 pub async fn children(&self) -> Result<Vec<M>> {
369 metadata_items(&self.client, &self.metadata.key).await
370 }
371}
372
373#[derive(Debug, Clone)]
374pub struct Collection<M> {
375 _items: PhantomData<M>,
376 client: HttpClient,
377 metadata: Metadata,
378}
379
380impl<M> FromMetadata for Collection<M> {
381 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
382 Self {
383 _items: PhantomData,
384 client,
385 metadata,
386 }
387 }
388}
389
390derive_metadata_item!(Collection<M>);
391
392impl<M> Collection<M>
393where
394 M: FromMetadata,
395{
396 #[tracing::instrument(level = "debug", skip_all)]
397 pub async fn children(&self) -> Result<Vec<M>> {
398 metadata_items(&self.client, &self.metadata.key).await
399 }
400}
401
402#[derive(Debug, Clone)]
403pub struct Movie {
404 client: HttpClient,
405 metadata: Metadata,
406}
407
408derive_from_metadata!(Movie);
409derive_metadata_item!(Movie);
410
411impl MediaItem for Movie {}
412impl MediaItemWithTranscoding for Movie {
413 type Options = VideoTranscodeOptions;
414}
415
416#[derive(Debug, Clone)]
417pub struct Show {
418 client: HttpClient,
419 metadata: Metadata,
420}
421
422derive_from_metadata!(Show);
423derive_metadata_item!(Show);
424
425impl Show {
426 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
428 pub async fn seasons(&self) -> Result<Vec<Season>> {
429 metadata_items(&self.client, &self.metadata.key).await
430 }
431
432 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.rating_key = self.metadata.rating_key))]
434 pub async fn episodes(&self) -> Result<Vec<Episode>> {
435 let path = format!("/library/metadata/{}/allLeaves", self.metadata.rating_key);
436 metadata_items(&self.client, &path).await
437 }
438}
439
440#[derive(Debug, Clone)]
441pub struct Season {
442 client: HttpClient,
443 metadata: Metadata,
444}
445
446derive_from_metadata!(Season);
447derive_metadata_item!(Season);
448
449impl Season {
450 pub fn season_number(&self) -> Option<u32> {
451 self.metadata.index
452 }
453
454 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
456 pub async fn episodes(&self) -> Result<Vec<Episode>> {
457 metadata_items(&self.client, &self.metadata.key).await
458 }
459
460 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
462 pub async fn show(&self) -> Result<Option<Show>> {
463 parent(self, &self.client).await
464 }
465}
466
467#[derive(Debug, Clone)]
468pub struct Episode {
469 client: HttpClient,
470 metadata: Metadata,
471}
472
473derive_from_metadata!(Episode);
474derive_metadata_item!(Episode);
475
476impl MediaItem for Episode {}
477impl MediaItemWithTranscoding for Episode {
478 type Options = VideoTranscodeOptions;
479}
480
481impl Episode {
482 pub fn season_number(&self) -> Option<u32> {
484 self.metadata.parent.parent_index
485 }
486
487 pub fn episode_number(&self) -> Option<u32> {
489 self.metadata.index
490 }
491
492 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
494 pub async fn season(&self) -> Result<Option<Season>> {
495 parent(self, &self.client).await
496 }
497}
498
499#[derive(Debug, Clone)]
500pub struct Artist {
501 client: HttpClient,
502 metadata: Metadata,
503}
504
505derive_from_metadata!(Artist);
506derive_metadata_item!(Artist);
507
508impl Artist {
509 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
511 pub async fn full_studio_albums(&self) -> Result<Vec<MusicAlbum>> {
512 metadata_items(&self.client, &self.metadata.key).await
513 }
514
515 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
517 pub async fn albums(&self) -> Result<Vec<MusicAlbum>> {
518 let section_id = match &self.metadata.library_section_id {
519 Some(id) => id,
520 None => return Err(Error::UnexpectedError),
521 };
522
523 let albums_search_path = format!(
524 "/library/sections/{}/all?type={}&artist.id={}",
525 section_id,
526 SearchType::Album,
527 self.metadata.rating_key
528 );
529 metadata_items(&self.client, &albums_search_path).await
530 }
531}
532
533#[derive(Debug, Clone)]
534pub struct MusicAlbum {
535 client: HttpClient,
536 metadata: Metadata,
537}
538
539derive_from_metadata!(MusicAlbum);
540derive_metadata_item!(MusicAlbum);
541
542impl MusicAlbum {
543 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
545 pub async fn tracks(&self) -> Result<Vec<Track>> {
546 metadata_items(&self.client, &self.metadata.key).await
547 }
548
549 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
551 pub async fn artist(&self) -> Result<Option<Artist>> {
552 parent(self, &self.client).await
553 }
554}
555
556#[derive(Debug, Clone)]
557pub struct Track {
558 client: HttpClient,
559 metadata: Metadata,
560}
561
562derive_from_metadata!(Track);
563derive_metadata_item!(Track);
564
565impl MediaItem for Track {}
566impl MediaItemWithTranscoding for Track {
567 type Options = MusicTranscodeOptions;
568}
569
570impl Track {
571 pub fn track_number(&self) -> Option<u32> {
573 self.metadata.index
574 }
575
576 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
578 pub async fn album(&self) -> Result<Option<MusicAlbum>> {
579 parent(self, &self.client).await
580 }
581}
582
583#[derive(Debug, Clone)]
584pub struct Photo {
585 client: HttpClient,
586 metadata: Metadata,
587}
588
589derive_from_metadata!(Photo);
590derive_metadata_item!(Photo);
591
592impl MediaItem for Photo {}
593
594impl Photo {
595 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
597 pub async fn album(&self) -> Result<Option<PhotoAlbum>> {
598 parent(self, &self.client).await
599 }
600}
601
602#[derive(Debug, Clone)]
603pub struct PhotoAlbum {
604 client: HttpClient,
605 metadata: Metadata,
606}
607
608derive_from_metadata!(PhotoAlbum);
609derive_metadata_item!(PhotoAlbum);
610
611impl PhotoAlbum {
612 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
614 pub async fn contents(&self) -> Result<Vec<PhotoAlbumItem>> {
615 metadata_items(&self.client, &self.metadata.key).await
616 }
617
618 #[tracing::instrument(level = "debug", skip_all, fields(self.metadata.key = self.metadata.key))]
620 pub async fn album(&self) -> Result<Option<PhotoAlbum>> {
621 parent(self, &self.client).await
622 }
623}
624
625#[enum_dispatch(MetadataItem)]
626pub enum PhotoAlbumItem {
627 PhotoAlbum,
628 Photo,
629}
630
631impl FromMetadata for PhotoAlbumItem {
632 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
633 if metadata.key.ends_with("/children") {
635 PhotoAlbum::from_metadata(client, metadata).into()
636 } else {
637 Photo::from_metadata(client, metadata).into()
638 }
639 }
640}
641
642#[derive(Debug, Clone)]
643pub struct Clip {
644 client: HttpClient,
645 metadata: Metadata,
646}
647
648derive_from_metadata!(Clip);
649derive_metadata_item!(Clip);
650
651impl MediaItem for Clip {}
652
653#[derive(Debug, Clone)]
654pub struct UnknownItem {
655 client: HttpClient,
656 metadata: Metadata,
657}
658
659derive_from_metadata!(UnknownItem);
660derive_metadata_item!(UnknownItem);
661
662#[enum_dispatch(MetadataItem)]
663#[derive(Debug, Clone)]
664pub enum Item {
665 Movie,
666 Episode,
667 Photo,
668 Show,
669 Artist,
670 MusicAlbum,
671 Season,
672 Track,
673 Clip,
674 MovieCollection(Collection<Movie>),
675 ShowCollection(Collection<Show>),
676 VideoPlaylist(Playlist<Video>),
677 PhotoPlaylist(Playlist<Photo>),
678 MusicPlaylist(Playlist<Track>),
679 UnknownItem,
680}
681
682impl MediaItem for Item {}
683
684impl FromMetadata for Item {
685 fn from_metadata(client: HttpClient, metadata: Metadata) -> Self {
686 if let Some(ref item_type) = metadata.metadata_type {
687 match item_type {
688 MetadataType::Movie => Movie::from_metadata(client, metadata).into(),
689 MetadataType::Episode => Episode::from_metadata(client, metadata).into(),
690 MetadataType::Photo => Photo::from_metadata(client, metadata).into(),
691 MetadataType::Show => Show::from_metadata(client, metadata).into(),
692 MetadataType::Artist => Artist::from_metadata(client, metadata).into(),
693 MetadataType::MusicAlbum => MusicAlbum::from_metadata(client, metadata).into(),
694 MetadataType::Season => Season::from_metadata(client, metadata).into(),
695 MetadataType::Track => Track::from_metadata(client, metadata).into(),
696 MetadataType::Clip(_) => Clip::from_metadata(client, metadata).into(),
697 MetadataType::Collection(CollectionMetadataSubtype::Movie) => {
698 Collection::<Movie>::from_metadata(client, metadata).into()
699 }
700 MetadataType::Collection(CollectionMetadataSubtype::Show) => {
701 Collection::<Show>::from_metadata(client, metadata).into()
702 }
703 MetadataType::Playlist(PlaylistMetadataType::Video) => {
704 Playlist::<Video>::from_metadata(client, metadata).into()
705 }
706 MetadataType::Playlist(PlaylistMetadataType::Audio) => {
707 Playlist::<Track>::from_metadata(client, metadata).into()
708 }
709 MetadataType::Playlist(PlaylistMetadataType::Photo) => {
710 Playlist::<Photo>::from_metadata(client, metadata).into()
711 }
712 #[cfg(not(feature = "tests_deny_unknown_fields"))]
713 _ => UnknownItem::from_metadata(client, metadata).into(),
714 }
715 } else {
716 UnknownItem::from_metadata(client, metadata).into()
717 }
718 }
719}
720
721#[derive(Debug, Clone)]
722pub struct MovieLibrary {
723 client: HttpClient,
724 directory: ServerLibrary,
725}
726
727impl MovieLibrary {
728 pub fn title(&self) -> &str {
730 &self.directory.title
731 }
732
733 #[tracing::instrument(level = "debug", skip_all)]
735 pub async fn movies(&self) -> Result<Vec<Movie>> {
736 pivot_items(&self.client, &self.directory, "content.library").await
737 }
738
739 #[tracing::instrument(level = "debug", skip_all)]
741 pub async fn collections(&self) -> Result<Vec<Collection<Movie>>> {
742 pivot_items(&self.client, &self.directory, "content.collections").await
743 }
744
745 #[tracing::instrument(level = "debug", skip_all)]
747 pub async fn playlists(&self) -> Result<Vec<Playlist<Video>>> {
748 pivot_items(&self.client, &self.directory, "content.playlists").await
749 }
750}
751
752#[derive(Debug, Clone)]
753pub struct TVLibrary {
754 client: HttpClient,
755 directory: ServerLibrary,
756}
757
758impl TVLibrary {
759 pub fn title(&self) -> &str {
761 &self.directory.title
762 }
763
764 #[tracing::instrument(level = "debug", skip_all)]
766 pub async fn shows(&self) -> Result<Vec<Show>> {
767 pivot_items(&self.client, &self.directory, "content.library").await
768 }
769
770 #[tracing::instrument(level = "debug", skip_all)]
772 pub async fn collections(&self) -> Result<Vec<Collection<Show>>> {
773 pivot_items(&self.client, &self.directory, "content.collections").await
774 }
775
776 #[tracing::instrument(level = "debug", skip_all)]
778 pub async fn playlists(&self) -> Result<Vec<Playlist<Video>>> {
779 pivot_items(&self.client, &self.directory, "content.playlists").await
780 }
781}
782
783#[derive(Debug, Clone)]
784pub struct MusicLibrary {
785 client: HttpClient,
786 directory: ServerLibrary,
787}
788
789impl MusicLibrary {
790 pub fn title(&self) -> &str {
792 &self.directory.title
793 }
794
795 #[tracing::instrument(level = "debug", skip_all)]
797 pub async fn artists(&self) -> Result<Vec<Artist>> {
798 pivot_items(&self.client, &self.directory, "content.library").await
799 }
800
801 #[tracing::instrument(level = "debug", skip_all)]
803 pub async fn playlists(&self) -> Result<Vec<Playlist<Track>>> {
804 pivot_items(&self.client, &self.directory, "content.playlists").await
805 }
806}
807
808#[derive(Debug, Clone)]
809pub struct PhotoLibrary {
810 client: HttpClient,
811 directory: ServerLibrary,
812}
813
814impl PhotoLibrary {
815 pub fn title(&self) -> &str {
817 &self.directory.title
818 }
819
820 #[tracing::instrument(level = "debug", skip_all)]
822 pub async fn albums(&self) -> Result<Vec<PhotoAlbum>> {
823 pivot_items(&self.client, &self.directory, "content.library").await
824 }
825
826 #[tracing::instrument(level = "debug", skip_all)]
828 pub async fn playlists(&self) -> Result<Vec<Playlist<Photo>>> {
829 pivot_items(&self.client, &self.directory, "content.playlists").await
830 }
831}
832
833#[derive(Debug, Clone)]
834pub enum Library {
835 Movie(MovieLibrary),
836 TV(TVLibrary),
837 Music(MusicLibrary),
838 Video(MovieLibrary),
839 Photo(PhotoLibrary),
840}
841
842impl Library {
843 pub(super) fn new(client: HttpClient, directory: ServerLibrary) -> Self {
844 match directory.library_type {
845 LibraryType::Movie => {
846 if directory.subtype.as_deref() == Some("clip") {
847 Library::Video(MovieLibrary { client, directory })
848 } else {
849 Library::Movie(MovieLibrary { client, directory })
850 }
851 }
852 LibraryType::Show => Library::TV(TVLibrary { client, directory }),
853 LibraryType::Artist => Library::Music(MusicLibrary { client, directory }),
854 LibraryType::Photo => Library::Photo(PhotoLibrary { client, directory }),
855 LibraryType::Mixed => todo!("Mixed library type is not supported yet"),
856 LibraryType::Clip => todo!("Clip library type is not supported yet"),
857 #[cfg(not(feature = "tests_deny_unknown_fields"))]
858 LibraryType::Unknown => panic!("Unknown library type"),
859 }
860 }
861
862 fn directory(&self) -> &ServerLibrary {
863 match self {
864 Self::Movie(l) => &l.directory,
865 Self::TV(l) => &l.directory,
866 Self::Music(l) => &l.directory,
867 Self::Video(l) => &l.directory,
868 Self::Photo(l) => &l.directory,
869 }
870 }
871
872 pub fn id(&self) -> &str {
874 &self.directory().id
875 }
876
877 pub fn title(&self) -> &str {
879 &self.directory().title
880 }
881
882 pub fn library_type(&self) -> &LibraryType {
883 &self.directory().library_type
884 }
885}