synthwave/src/library/track.rs
2024-08-02 23:28:32 +02:00

136 lines
3.8 KiB
Rust

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<Reference>,
pub artist_id: Option<Reference>,
pub meta: Option<AudioMetadata>,
}
impl Track {
pub async fn create(data: &serde_json::Map<String, serde_json::Value>) {
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<String> {
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<Track> {
Self::find(
doc! {
"artist_id": None::<String>,
"album_id": None::<String>
},
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::<Album>().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::<Artist>(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(())
}
}