use mongod::{ assert_reference_of, derive::{Model, Referencable}, Model, Referencable, Reference, Validate, }; use mongodb::bson::doc; use serde::{Deserialize, Serialize}; use serde_json::json; use crate::{ library::{album::Album, artist::Artist}, route::ToAPI, }; use super::metadata::AudioMetadata; #[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)] pub struct Track { pub _id: String, pub path: String, pub title: String, pub album_id: Option, pub artist_id: Option, pub meta: Option, } impl Track { pub async fn create(data: &serde_json::Map) { let mut t = Self { _id: uuid::Uuid::new_v4().to_string(), path: data.get("path").unwrap().as_str().unwrap().to_string(), title: data.get("title").unwrap().as_str().unwrap().to_string(), album_id: None, artist_id: None, meta: data.get("meta").map(|x| AudioMetadata(x.clone())), }; t.insert().await.unwrap(); t.update(&serde_json::to_value(&data).unwrap()) .await .unwrap(); } /// Transcode audio to OPUS with `bitrate` pub fn get_opus(&self, bitrate: u32) -> Option { let transcoded = format!("./data/transcode/opus/{}/{}.opus", bitrate, self._id); if std::path::Path::new(&transcoded).exists() { return Some(transcoded); } log::info!("Transcoding {} to OPUS {}", self._id, bitrate); std::fs::create_dir_all(format!("./data/transcode/opus/{bitrate}")).unwrap(); let out = std::process::Command::new("ffmpeg") .arg("-i") .arg(&self.path) .arg("-c:a") .arg("libopus") .arg("-b:a") .arg(format!("{bitrate}k")) .arg(&transcoded) .output() .unwrap(); if !out.status.success() { return None; } Some(transcoded) } /// Find tracks with no album or artist pub async fn get_orphans() -> Vec { Self::find( doc! { "artist_id": None::, "album_id": None:: }, None, ) .await .unwrap() } } impl ToAPI for Track { async fn api(&self) -> serde_json::Value { let (cover, album_title) = if let Some(album_ref) = &self.album_id { let album = album_ref.get::().await; (album.get_cover().await.is_some(), album.title) } else { (false, String::new()) }; let artist_title = if let Some(artist_ref) = &self.artist_id { artist_ref .get_partial::(json!({"name": 1})) .await .name } else { None }; json!({ "id": self._id, "title": self.title, "track_number": self.meta.as_ref().map(|x| x.track_number()), "meta": serde_json::to_value(&self.meta).unwrap(), "album_id": self.album_id.as_ref().map(|x| x.id().to_string()), "album": album_title, "cover": if cover { Some(format!("/album/{}/cover", self._id)) } else { None }, "artist_id": self.artist_id.as_ref().map(|x| x.id().to_string()), "artist": artist_title }) } } impl Validate for Track { async fn validate(&self) -> Result<(), String> { if let Some(artist_id) = &self.artist_id { assert_reference_of!(artist_id, Artist); } if let Some(album_id) = &self.artist_id { assert_reference_of!(album_id, Artist); } Ok(()) } }