improvement(indexing): cover extraction performance gains
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
use std::{fs, path::Path, time::Instant};
|
use std::{fs, path::Path};
|
||||||
|
use image::EncodableLayout;
|
||||||
|
use webp::Encoder;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use webp::Encoder;
|
|
||||||
|
|
||||||
use crate::music::metadata::CoverData;
|
use crate::music::metadata::CoverData;
|
||||||
|
|
||||||
@@ -40,9 +40,7 @@ pub fn get_all_cover_hashes() -> Vec<String> {
|
|||||||
hashes
|
hashes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn write_cover(hash: &str, cover: &CoverData, base_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
if cover.mime_type != "image/jpeg"
|
if cover.mime_type != "image/jpeg"
|
||||||
&& cover.mime_type != "image/png"
|
&& cover.mime_type != "image/png"
|
||||||
&& cover.mime_type != "image/webp"
|
&& cover.mime_type != "image/webp"
|
||||||
@@ -50,8 +48,6 @@ pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::er
|
|||||||
return Err(format!("Invalid cover MIME type: {}", cover.mime_type).into());
|
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 path = Path::new(&base_path).join(format!("{hash}.webp"));
|
||||||
|
|
||||||
let dynamic_image = image::load_from_memory_with_format(
|
let dynamic_image = image::load_from_memory_with_format(
|
||||||
@@ -65,19 +61,16 @@ pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::er
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let resized_image = if dynamic_image.width() > 640 || dynamic_image.height() > 640 {
|
let resized_image = if dynamic_image.width() > 640 || dynamic_image.height() > 640 {
|
||||||
dynamic_image.resize_to_fill(640, 640, image::imageops::FilterType::Lanczos3)
|
let a = dynamic_image.resize_to_fill(640, 640, image::imageops::FilterType::Lanczos3);
|
||||||
|
drop(dynamic_image);
|
||||||
|
a
|
||||||
} else {
|
} else {
|
||||||
dynamic_image
|
dynamic_image
|
||||||
};
|
};
|
||||||
|
|
||||||
let webp_encoder = Encoder::from_image(&resized_image)?;
|
let webp = Encoder::from_image(&resized_image)?.encode_lossless();
|
||||||
let encoded_image = webp_encoder.encode_lossless().to_vec();
|
|
||||||
|
|
||||||
fs::write(path, encoded_image)?;
|
fs::write(path, webp.as_bytes())?;
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
|
||||||
|
|
||||||
println!("Writing '{}' cover took {:.?}", hash, elapsed);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ use r2d2_sqlite::SqliteConnectionManager;
|
|||||||
use rusqlite::{params, Row};
|
use rusqlite::{params, Row};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
covers::{get_all_cover_hashes, write_cover},
|
covers::{get_all_cover_hashes, get_cover_base_path, write_cover},
|
||||||
music::metadata::TrackMetadata,
|
music::metadata::TrackMetadata,
|
||||||
proto,
|
proto,
|
||||||
};
|
};
|
||||||
@@ -145,13 +146,25 @@ pub fn insert_tracks(
|
|||||||
// BEGIN TRANSACTION
|
// BEGIN TRANSACTION
|
||||||
let tx = connection.transaction()?;
|
let tx = connection.transaction()?;
|
||||||
|
|
||||||
let mut cover_handles: Vec<JoinHandle<()>> = Vec::new();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let base_path = get_cover_base_path();
|
||||||
|
|
||||||
|
std::thread::scope(|s| {
|
||||||
|
for (hash, meta) in tracks.iter() {
|
||||||
|
if let Some(ref cover) = &meta.cover {
|
||||||
|
if !existing_covers.contains(&hash) {
|
||||||
|
s.spawn(|| {
|
||||||
|
let _ = write_cover(hash, cover, &base_path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut statement =
|
let mut statement =
|
||||||
tx.prepare("INSERT OR REPLACE INTO tracks (hash, library_path_id, name, artist_id, path, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6)")?;
|
tx.prepare("INSERT OR REPLACE INTO tracks (hash, library_path_id, name, artist_id, path, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6)")?;
|
||||||
|
|
||||||
for (hash, meta) in tracks {
|
for (hash, meta) in tracks.iter() {
|
||||||
statement.execute(params![
|
statement.execute(params![
|
||||||
&hash,
|
&hash,
|
||||||
library_path_id,
|
library_path_id,
|
||||||
@@ -160,23 +173,11 @@ pub fn insert_tracks(
|
|||||||
meta.path,
|
meta.path,
|
||||||
meta.total_seconds * 1000
|
meta.total_seconds * 1000
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
if let Some(cover) = meta.cover {
|
|
||||||
if !existing_covers.contains(&hash) {
|
|
||||||
cover_handles.push(std::thread::spawn(|| {
|
|
||||||
let _ = write_cover(hash, cover);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// COMMIT
|
// COMMIT
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
|
|
||||||
for handle in cover_handles {
|
|
||||||
let _ = handle.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user