136 lines
3.8 KiB
Rust
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(())
|
|
}
|
|
}
|