feat: basic music backend
This commit is contained in:
96
src/covers.rs
Normal file
96
src/covers.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{fs, path::Path, time::Instant};
|
||||
|
||||
use axum::Router;
|
||||
use tower_http::services::ServeDir;
|
||||
use webp::Encoder;
|
||||
|
||||
use crate::music::metadata::CoverData;
|
||||
|
||||
pub fn get_cover_base_path() -> String {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
std::env::var("COVER_URL").expect("Error getting cover directory URL")
|
||||
}
|
||||
|
||||
pub fn get_all_cover_hashes() -> Vec<String> {
|
||||
let path = get_cover_base_path();
|
||||
|
||||
let base_path = Path::new(&path);
|
||||
|
||||
if !base_path.exists() {
|
||||
let _ = fs::create_dir_all(base_path);
|
||||
}
|
||||
|
||||
|
||||
let walkdir = walkdir::WalkDir::new(path).min_depth(1).max_depth(1);
|
||||
|
||||
let hashes: Vec<String> = walkdir
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
let entry = e.unwrap();
|
||||
|
||||
let file_name = entry.file_name().to_str().unwrap();
|
||||
|
||||
// len - 5 because the file names end with .webp
|
||||
let hash = file_name[0..(file_name.len() - 5)].to_string();
|
||||
|
||||
return hash;
|
||||
})
|
||||
.collect();
|
||||
|
||||
hashes
|
||||
}
|
||||
|
||||
pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let now = Instant::now();
|
||||
|
||||
if cover.mime_type != "image/jpeg"
|
||||
&& cover.mime_type != "image/png"
|
||||
&& cover.mime_type != "image/webp"
|
||||
{
|
||||
return Err(format!("Invalid cover MIME type: {}", cover.mime_type).into());
|
||||
}
|
||||
|
||||
let base_path = get_cover_base_path();
|
||||
|
||||
let path = Path::new(&base_path).join(format!("{hash}.webp"));
|
||||
|
||||
let dynamic_image = image::load_from_memory_with_format(
|
||||
&cover.bytes,
|
||||
match cover.mime_type.as_str() {
|
||||
"image/png" => image::ImageFormat::Png,
|
||||
"image/jpeg" => image::ImageFormat::Jpeg,
|
||||
"image/webp" => image::ImageFormat::WebP,
|
||||
_ => panic!("Invalid cover MIME type (this should never happen)"),
|
||||
},
|
||||
)?;
|
||||
|
||||
let resized_image = if dynamic_image.width() > 640 || dynamic_image.height() > 640 {
|
||||
dynamic_image.resize_to_fill(640, 640, image::imageops::FilterType::Lanczos3)
|
||||
} else {
|
||||
dynamic_image
|
||||
};
|
||||
|
||||
let webp_encoder = Encoder::from_image(&resized_image)?;
|
||||
let encoded_image = webp_encoder.encode_lossless().to_vec();
|
||||
|
||||
fs::write(path, encoded_image)?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
println!("Writing '{}' cover took {:.?}", hash, elapsed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_cover_server() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let path = get_cover_base_path();
|
||||
|
||||
let service = ServeDir::new(path);
|
||||
|
||||
let app = Router::new().nest_service("/", service);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("[::1]:39994").await?;
|
||||
|
||||
Ok(axum::serve(listener, app).await?)
|
||||
}
|
||||
Reference in New Issue
Block a user