feat: add first engine song search

pull/8/head
Davide Polonio 2022-06-08 18:17:22 +02:00
parent 06fff8e972
commit ba2b689e77
3 changed files with 78 additions and 90 deletions

View File

@ -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());
match entry_kind {
None => None,
Some(entry) => match entry { Some(entry) => match entry {
ContentKind::Track(id) => { ContentKind::Track(id) => self.spotify.get_track(id.as_str()).await,
self.spotify.get_track(id.as_str()).await.map(|track| {
MusicData::Track(Track {
name: track.name.clone(),
authors: track
.artists
.iter()
.map(|artist| Author {
name: artist.clone(),
})
.collect(),
duration: track.duration.clone(),
album: None, // FIXME missing metadata
description: None, // FIXME missing metadata
lyrics: None, // FIXME missing metadata
})
})
}
ContentKind::Album(id) => {
self.spotify.get_album(id.as_str()).await.map(|album| {
MusicData::Album(Album {
name: album.name.clone(),
authors: album
.artists
.iter()
.map(|artist| Author {
name: artist.clone(),
})
.collect(),
description: None, // FIXME missing metadata
year: None, // FIXME missing metadata
})
})
}
_ => None, _ => None,
}, },
None => None,
};
if track_info.is_some() {
let ti = track_info.unwrap();
let youtube_search = match self
.youtube
.search_video(
format!(
"{} {}",
ti.artists
.get(0)
.map(|artist| format!("{} -", artist))
.unwrap_or("".to_string()),
ti.name
)
.as_str(),
None,
)
.await
{
Err(_) => None,
Ok(search) => Some(search),
};
return Some(TrackItem {
spotify_track: Some(ti),
youtube_track: youtube_search.map(|search| search.items),
});
} }
} return None;
_ => todo!("Search type not present yet"), //TODO implement }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_search_track_by_spotify_id() {
todo!("Implement me!")
} }
} }

View File

@ -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,

View File

@ -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(),