feat: add youtube search #8
							
								
								
									
										130
									
								
								src/engine.rs
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								src/engine.rs
									
									
									
									
									
								
							| @ -1,37 +1,15 @@ | |||||||
| use crate::spotify::ContentKind; | use crate::spotify::ContentKind; | ||||||
| use crate::{spotify, youtube}; | use crate::youtube::Video; | ||||||
| use chrono::{Date, Utc}; | use crate::{spotify, youtube, TrackInfo}; | ||||||
| use std::time::Duration; |  | ||||||
| 
 |  | ||||||
| pub(crate) enum MusicData { |  | ||||||
|     Track(Track), |  | ||||||
|     Album(Album), |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub(crate) struct Track { | pub(crate) struct TrackItem { | ||||||
|     name: String, |     spotify_track: Option<TrackInfo>, | ||||||
|     authors: Vec<Author>, |     youtube_track: Option<Vec<Video>>, | ||||||
|     duration: Duration, |  | ||||||
|     album: Option<Album>, |  | ||||||
|     description: Option<String>, |  | ||||||
|     lyrics: Option<String>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub(crate) struct Album { |  | ||||||
|     name: String, |  | ||||||
|     authors: Vec<Author>, |  | ||||||
|     description: Option<String>, |  | ||||||
|     year: Option<Date<Utc>>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub(crate) struct Author { |  | ||||||
|     name: String, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // The enum holds all the currently supported type of Id which the engine can search for
 | // The enum holds all the currently supported type of Id which the engine can search for
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub(crate) enum ServiceIdKind { | pub(crate) enum ServiceIdKind { | ||||||
|     Spotify(String), |     Spotify(String), | ||||||
|     Youtube(String), |     Youtube(String), | ||||||
| @ -40,6 +18,7 @@ pub(crate) enum ServiceIdKind { | |||||||
| 
 | 
 | ||||||
| // This struct will allow us in the future to search, cache and store data and metadata regarding
 | // This struct will allow us in the future to search, cache and store data and metadata regarding
 | ||||||
| // tracks, albums and playlists
 | // tracks, albums and playlists
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub(crate) struct MusicEngine { | pub(crate) struct MusicEngine { | ||||||
|     spotify: spotify::Client, |     spotify: spotify::Client, | ||||||
|     youtube: youtube::Client, |     youtube: youtube::Client, | ||||||
| @ -63,56 +42,57 @@ impl MusicEngine { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub(crate) async fn search_by_name(&self, name: &str) { |     pub(crate) async fn search_song_by_name(&self, name: &str) { | ||||||
|         todo!("In the future it would be possible to search for all metadata on a record from this call") |         todo!("In the future it would be possible to search for all metadata on a record from this call") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub(crate) async fn search_by_id(&self, id: ServiceIdKind) -> Option<MusicData> { |     pub(crate) async fn search_song_by_id(&self, id: &str) -> Option<TrackItem> { | ||||||
|         match id { |         let entry_kind = spotify::get_entry_kind(id); | ||||||
|             ServiceIdKind::Spotify(id) => { |         let track_info = match entry_kind { | ||||||
|                 let entry_kind = spotify::get_entry_kind(id.as_str()); |             Some(entry) => match entry { | ||||||
|                 match entry_kind { |                 ContentKind::Track(id) => self.spotify.get_track(id.as_str()).await, | ||||||
|                     None => None, |                 _ => None, | ||||||
|                     Some(entry) => match entry { |             }, | ||||||
|                         ContentKind::Track(id) => { |             None => None, | ||||||
|                             self.spotify.get_track(id.as_str()).await.map(|track| { |         }; | ||||||
|                                 MusicData::Track(Track { | 
 | ||||||
|                                     name: track.name.clone(), |         if track_info.is_some() { | ||||||
|                                     authors: track |             let ti = track_info.unwrap(); | ||||||
|                                         .artists |             let youtube_search = match self | ||||||
|                                         .iter() |                 .youtube | ||||||
|                                         .map(|artist| Author { |                 .search_video( | ||||||
|                                             name: artist.clone(), |                     format!( | ||||||
|                                         }) |                         "{} {}", | ||||||
|                                         .collect(), |                         ti.artists | ||||||
|                                     duration: track.duration.clone(), |                             .get(0) | ||||||
|                                     album: None,       // FIXME missing metadata
 |                             .map(|artist| format!("{} -", artist)) | ||||||
|                                     description: None, // FIXME missing metadata
 |                             .unwrap_or("".to_string()), | ||||||
|                                     lyrics: None,      // FIXME missing metadata
 |                         ti.name | ||||||
|                                 }) |                     ) | ||||||
|                             }) |                     .as_str(), | ||||||
|                         } |                     None, | ||||||
|                         ContentKind::Album(id) => { |                 ) | ||||||
|                             self.spotify.get_album(id.as_str()).await.map(|album| { |                 .await | ||||||
|                                 MusicData::Album(Album { |             { | ||||||
|                                     name: album.name.clone(), |                 Err(_) => None, | ||||||
|                                     authors: album |                 Ok(search) => Some(search), | ||||||
|                                         .artists |             }; | ||||||
|                                         .iter() | 
 | ||||||
|                                         .map(|artist| Author { |             return Some(TrackItem { | ||||||
|                                             name: artist.clone(), |                 spotify_track: Some(ti), | ||||||
|                                         }) |                 youtube_track: youtube_search.map(|search| search.items), | ||||||
|                                         .collect(), |             }); | ||||||
|                                     description: None, // FIXME missing metadata
 |  | ||||||
|                                     year: None,        // FIXME missing metadata
 |  | ||||||
|                                 }) |  | ||||||
|                             }) |  | ||||||
|                         } |  | ||||||
|                         _ => None, |  | ||||||
|                     }, |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             _ => todo!("Search type not present yet"), //TODO implement
 |  | ||||||
|         } |         } | ||||||
|  |         return None; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use super::*; | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn should_search_track_by_spotify_id() { | ||||||
|  |         todo!("Implement me!") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ use std::any::Any; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub enum ContentKind { | pub enum ContentKind { | ||||||
|     Track(String), |     Track(String), | ||||||
|     Album(String), |     Album(String), | ||||||
| @ -14,17 +15,20 @@ pub enum ContentKind { | |||||||
|     Episode(String), |     Episode(String), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub enum PlayableKind { | pub enum PlayableKind { | ||||||
|     Track(TrackInfo), |     Track(TrackInfo), | ||||||
|     Podcast(EpisodeInfo), |     Podcast(EpisodeInfo), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub struct TrackInfo { | pub struct TrackInfo { | ||||||
|     pub(crate) name: String, |     pub(crate) name: String, | ||||||
|     pub(crate) artists: Vec<String>, |     pub(crate) artists: Vec<String>, | ||||||
|     pub(crate) duration: Duration, |     pub(crate) duration: Duration, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub struct EpisodeInfo { | pub struct EpisodeInfo { | ||||||
|     pub(crate) name: String, |     pub(crate) name: String, | ||||||
|     pub(crate) show: String, |     pub(crate) show: String, | ||||||
| @ -34,6 +38,7 @@ pub struct EpisodeInfo { | |||||||
|     pub(crate) release_date: String, |     pub(crate) release_date: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub struct AlbumInfo { | pub struct AlbumInfo { | ||||||
|     pub(crate) name: String, |     pub(crate) name: String, | ||||||
|     pub(crate) artists: Vec<String>, |     pub(crate) artists: Vec<String>, | ||||||
| @ -41,6 +46,7 @@ pub struct AlbumInfo { | |||||||
|     pub(crate) tracks: Vec<TrackInfo>, |     pub(crate) tracks: Vec<TrackInfo>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub struct PlaylistInfo { | pub struct PlaylistInfo { | ||||||
|     pub(crate) name: String, |     pub(crate) name: String, | ||||||
|     pub(crate) artists: Vec<String>, |     pub(crate) artists: Vec<String>, | ||||||
| @ -70,6 +76,7 @@ impl Client { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn get_track(&self, id: &str) -> Option<TrackInfo> { |     pub async fn get_track(&self, id: &str) -> Option<TrackInfo> { | ||||||
|  |         // FIXME should we really return Option here? We're hiding a possible error or a entry not found
 | ||||||
|         let track_id = match TrackId::from_id(id) { |         let track_id = match TrackId::from_id(id) { | ||||||
|             Ok(track) => track, |             Ok(track) => track, | ||||||
|             Err(_e) => return None, |             Err(_e) => return None, | ||||||
|  | |||||||
| @ -2,21 +2,22 @@ use std::error::Error; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| pub(crate) struct VideoSearch { | pub(crate) struct VideoSearch { | ||||||
|     items: Vec<Video>, |     pub(crate) items: Vec<Video>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
| pub(crate) struct Video { | pub(crate) struct Video { | ||||||
|     title: String, |     title: String, | ||||||
|     videoId: String, |     video_id: String, | ||||||
|     author: String, |     author: String, | ||||||
|     authorId: String, |     author_id: String, | ||||||
|     authorUrl: String, |     author_url: String, | ||||||
|     lengthSeconds: u64, |     length_seconds: u64, | ||||||
|     description: String, |     description: String, | ||||||
|     descriptionHtml: String, |     description_html: String, | ||||||
|     viewCount: u64, |     view_count: u64, | ||||||
|     published: u64, |     published: u64, | ||||||
|     publishedText: String, |     published_text: String, | ||||||
|     live_now: bool, |     live_now: bool, | ||||||
|     paid: bool, |     paid: bool, | ||||||
|     premium: bool, |     premium: bool, | ||||||
| @ -88,16 +89,16 @@ impl Client { | |||||||
|                     premium, |                     premium, | ||||||
|                 } => Some(Video { |                 } => Some(Video { | ||||||
|                     title: title.clone(), |                     title: title.clone(), | ||||||
|                     videoId: videoId.clone(), |                     video_id: videoId.clone(), | ||||||
|                     author: author.clone(), |                     author: author.clone(), | ||||||
|                     authorId: authorId.clone(), |                     author_id: authorId.clone(), | ||||||
|                     authorUrl: authorUrl.clone(), |                     author_url: authorUrl.clone(), | ||||||
|                     lengthSeconds: lengthSeconds.clone(), |                     length_seconds: lengthSeconds.clone(), | ||||||
|                     description: description.clone(), |                     description: description.clone(), | ||||||
|                     descriptionHtml: descriptionHtml.clone(), |                     description_html: descriptionHtml.clone(), | ||||||
|                     viewCount: viewCount.clone(), |                     view_count: viewCount.clone(), | ||||||
|                     published: published.clone(), |                     published: published.clone(), | ||||||
|                     publishedText: publishedText.clone(), |                     published_text: publishedText.clone(), | ||||||
|                     live_now: liveNow.clone(), |                     live_now: liveNow.clone(), | ||||||
|                     paid: paid.clone(), |                     paid: paid.clone(), | ||||||
|                     premium: premium.clone(), |                     premium: premium.clone(), | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user