use serde::de::{Deserialize, Deserializer};
use serde_json;
use std::fmt;
use std::ops::Range;
use query::Query;
use search::SearchPage;
use {Client, Error, HlsPlaylist, Media, Result, Streamable};
#[derive(Debug, Clone)]
pub struct Song {
pub id: u64,
pub title: String,
pub album: Option<String>,
album_id: Option<u64>,
pub artist: Option<String>,
artist_id: Option<u64>,
pub track: Option<u64>,
pub year: Option<u64>,
pub genre: Option<String>,
cover_id: Option<String>,
pub size: u64,
content_type: String,
suffix: String,
transcoded_content_type: Option<String>,
transcoded_suffix: Option<String>,
pub duration: Option<u64>,
path: String,
media_type: String,
stream_br: Option<usize>,
stream_tc: Option<String>,
}
impl Song {
pub fn get(client: &Client, id: u64) -> Result<Song> {
let res = client.get("getSong", Query::with("id", id))?;
Ok(serde_json::from_value(res)?)
}
pub fn similar<U>(&self, client: &Client, count: U) -> Result<Vec<Song>>
where
U: Into<Option<usize>>,
{
let args = Query::with("id", self.id)
.arg("count", count.into())
.build();
let song = client.get("getSimilarSongs2", args)?;
Ok(get_list_as!(song, Song))
}
pub fn random<U>(client: &Client, size: U) -> Result<Vec<Song>>
where
U: Into<Option<usize>>,
{
let arg = Query::with("size", size.into().unwrap_or(10));
let song = client.get("getRandomSongs", arg)?;
Ok(get_list_as!(song, Song))
}
pub fn random_with<'a>(client: &Client) -> RandomSongs {
RandomSongs::new(client, 10)
}
pub fn list_in_genre<U>(
client: &Client,
genre: &str,
page: SearchPage,
folder_id: U,
) -> Result<Vec<Song>>
where
U: Into<Option<u64>>,
{
let args = Query::with("genre", genre)
.arg("count", page.count)
.arg("offset", page.offset)
.arg("musicFolderId", folder_id.into())
.build();
let song = client.get("getSongsByGenre", args)?;
Ok(get_list_as!(song, Song))
}
pub fn hls(&self, client: &Client, bit_rates: &[u64]) -> Result<HlsPlaylist> {
let args = Query::with("id", self.id)
.arg_list("bitrate", bit_rates)
.build();
let raw = client.get_raw("hls", args)?;
Ok(raw.parse::<HlsPlaylist>()?)
}
}
impl Streamable for Song {
fn stream(&self, client: &Client) -> Result<Vec<u8>> {
let mut q = Query::with("id", self.id);
q.arg("maxBitRate", self.stream_br);
client.get_bytes("stream", q)
}
fn stream_url(&self, client: &Client) -> Result<String> {
let mut q = Query::with("id", self.id);
q.arg("maxBitRate", self.stream_br);
client.build_url("stream", q)
}
fn download(&self, client: &Client) -> Result<Vec<u8>> {
client.get_bytes("download", Query::with("id", self.id))
}
fn download_url(&self, client: &Client) -> Result<String> {
client.build_url("download", Query::with("id", self.id))
}
fn encoding(&self) -> &str {
self.transcoded_content_type
.as_ref()
.unwrap_or(&self.content_type)
}
fn set_max_bit_rate(&mut self, bit_rate: usize) {
self.stream_br = Some(bit_rate);
}
fn set_transcoding(&mut self, format: &str) {
self.stream_tc = Some(format.to_string());
}
}
impl Media for Song {
fn has_cover_art(&self) -> bool {
self.cover_id.is_some()
}
fn cover_id(&self) -> Option<&str> {
self.cover_id.as_ref().map(|s| s.as_str())
}
fn cover_art<U: Into<Option<usize>>>(&self, client: &Client, size: U) -> Result<Vec<u8>> {
let cover = self
.cover_id()
.ok_or_else(|| Error::Other("no cover art found"))?;
let query = Query::with("id", cover).arg("size", size.into()).build();
client.get_bytes("getCoverArt", query)
}
fn cover_art_url<U: Into<Option<usize>>>(&self, client: &Client, size: U) -> Result<String> {
let cover = self
.cover_id()
.ok_or_else(|| Error::Other("no cover art found"))?;
let query = Query::with("id", cover).arg("size", size.into()).build();
client.build_url("getCoverArt", query)
}
}
impl fmt::Display for Song {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(ref artist) = self.artist {
write!(f, "{} - ", artist)?;
} else {
write!(f, "Unknown Artist - ")?;
}
if let Some(ref album) = self.album {
write!(f, "{}", album)?;
} else {
write!(f, "Unknown Album")?;
}
if let Some(year) = self.year {
write!(f, " [{}]", year)?;
}
write!(f, " - {}", self.title)?;
Ok(())
}
}
impl<'de> Deserialize<'de> for Song {
fn deserialize<D>(de: D) -> ::std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct _Song {
id: String,
parent: String,
is_dir: bool,
title: String,
album: Option<String>,
artist: Option<String>,
track: Option<u64>,
year: Option<u64>,
genre: Option<String>,
cover_art: Option<String>,
size: u64,
content_type: String,
suffix: String,
transcoded_content_type: Option<String>,
transcoded_suffix: Option<String>,
duration: Option<u64>,
bit_rate: Option<u64>,
path: String,
is_video: Option<bool>,
play_count: u64,
disc_number: Option<u64>,
created: String,
album_id: Option<String>,
artist_id: Option<String>,
#[serde(rename = "type")]
media_type: String,
}
let raw = _Song::deserialize(de)?;
Ok(Song {
id: raw.id.parse().unwrap(),
title: raw.title,
album: raw.album,
album_id: raw.album_id.map(|i| i.parse().unwrap()),
artist: raw.artist,
artist_id: raw.artist_id.map(|i| i.parse().unwrap()),
cover_id: raw.cover_art,
track: raw.track,
year: raw.year,
genre: raw.genre,
size: raw.size,
content_type: raw.content_type,
suffix: raw.suffix,
transcoded_content_type: raw.transcoded_content_type,
transcoded_suffix: raw.transcoded_suffix,
duration: raw.duration,
path: raw.path,
media_type: raw.media_type,
stream_br: None,
stream_tc: None,
})
}
}
#[derive(Debug, Deserialize)]
pub struct Lyrics {
pub title: String,
pub artist: String,
#[serde(rename = "value")]
pub lyrics: String,
}
#[derive(Debug)]
pub struct RandomSongs<'a> {
client: &'a Client,
size: usize,
genre: Option<&'a str>,
from_year: Option<usize>,
to_year: Option<usize>,
folder_id: Option<usize>,
}
impl<'a> RandomSongs<'a> {
fn new(client: &'a Client, n: usize) -> RandomSongs<'a> {
RandomSongs {
client,
size: n,
genre: None,
from_year: None,
to_year: None,
folder_id: None,
}
}
pub fn size(&mut self, n: usize) -> &mut RandomSongs<'a> {
self.size = n;
self
}
pub fn genre(&mut self, genre: &'a str) -> &mut RandomSongs<'a> {
self.genre = Some(genre);
self
}
pub fn from_year(&mut self, year: usize) -> &mut RandomSongs<'a> {
self.from_year = Some(year);
self
}
pub fn to_year(&mut self, year: usize) -> &mut RandomSongs<'a> {
self.to_year = Some(year);
self
}
pub fn in_years(&mut self, years: Range<usize>) -> &mut RandomSongs<'a> {
self.from_year = Some(years.start);
self.to_year = Some(years.end);
self
}
pub fn in_folder(&mut self, id: usize) -> &mut RandomSongs<'a> {
self.folder_id = Some(id);
self
}
pub fn request(&mut self) -> Result<Vec<Song>> {
let args = Query::with("size", self.size)
.arg("genre", self.genre)
.arg("fromYear", self.from_year)
.arg("toYear", self.to_year)
.arg("musicFolderId", self.folder_id)
.build();
let song = self.client.get("getRandomSongs", args)?;
Ok(get_list_as!(song, Song))
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_util;
#[test]
fn parse_song() {
let parsed = serde_json::from_value::<Song>(raw()).unwrap();
assert_eq!(parsed.id, 27);
assert_eq!(parsed.title, String::from("Bellevue Avenue"));
assert_eq!(parsed.track, Some(1));
}
#[test]
fn get_hls() {
let mut srv = test_util::demo_site().unwrap();
let song = serde_json::from_value::<Song>(raw()).unwrap();
let hls = song.hls(&mut srv, &[]).unwrap();
assert_eq!(hls.len(), 20)
}
fn raw() -> serde_json::Value {
serde_json::from_str(
r#"{
"id" : "27",
"parent" : "25",
"isDir" : false,
"title" : "Bellevue Avenue",
"album" : "Bellevue",
"artist" : "Misteur Valaire",
"track" : 1,
"genre" : "(255)",
"coverArt" : "25",
"size" : 5400185,
"contentType" : "audio/mpeg",
"suffix" : "mp3",
"duration" : 198,
"bitRate" : 216,
"path" : "Misteur Valaire/Bellevue/01 - Misteur Valaire - Bellevue Avenue.mp3",
"averageRating" : 3.0,
"playCount" : 706,
"created" : "2017-03-12T11:07:27.000Z",
"starred" : "2017-06-01T19:48:25.635Z",
"albumId" : "1",
"artistId" : "1",
"type" : "music"
}"#,
)
.unwrap()
}
}