diff options
| author | jakka <jakka@jakka.su> | 2025-09-27 17:40:39 +0300 |
|---|---|---|
| committer | jakka <jakka@jakka.su> | 2025-09-27 17:40:39 +0300 |
| commit | 1aa81a1c3d5e56a0a6024beed0ba47ecea1a3492 (patch) | |
| tree | 2384bb4714c2d234de4d576452ef4708e15149be /src | |
| parent | 20d6438440de1f63eeb9d80eced475e001be02f0 (diff) | |
| parent | 9e4796570baf97d2193745a350e9611fa21d6a78 (diff) | |
merged main
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.rs | 9 | ||||
| -rw-r--r-- | src/files.rs | 35 | ||||
| -rw-r--r-- | src/flac.rs | 209 |
3 files changed, 139 insertions, 114 deletions
@@ -88,9 +88,6 @@ async fn init_clean_files(conn: &Connection) -> Result<Vec<PathBuf>, turso::Erro while let Ok(Some(row)) = rows.next().await { let path = row.get::<String>(0)?; files.push(PathBuf::from(path)); - } - Ok(files) -} async fn remove_file(conn: &Connection, filename: &Path) -> Result<()> { conn.execute(REMOVE_FILE, params!(filename.to_str().unwrap())) @@ -104,9 +101,6 @@ async fn get_toencode_files(conn: &Connection) -> Result<Vec<PathBuf>, turso::Er while let Ok(Some(row)) = rows.next().await { let path = row.get::<String>(0)?; files.push(PathBuf::from(path)); - } - Ok(files) -} async fn get_toencode_number(conn: &Connection) -> Result<u64, turso::Error> { Ok(conn @@ -133,6 +127,7 @@ async fn vacuum(conn: &Connection) -> Result<()> { Ok(()) } + #[cfg(test)] mod tests { @@ -157,7 +152,6 @@ mod tests { insert_file(&conn, &path).await.unwrap(); } let mut returned = conn.query(TOENCODE_PATHS, ()).await.unwrap(); - while let Ok(Some(_)) = returned.next().await { counter += 1 } @@ -183,7 +177,6 @@ mod tests { .await .unwrap(); } - conn.execute( UPDATE_FILE, params![ diff --git a/src/files.rs b/src/files.rs index 0407469..afb42d7 100644 --- a/src/files.rs +++ b/src/files.rs @@ -29,9 +29,9 @@ struct FileError { } impl FileError { - fn new(file: impl AsRef<Path>, error: anyhow::Error) -> Self { + fn new(file: &Path, error: anyhow::Error) -> Self { FileError { - file: file.as_ref().to_path_buf(), + file: file.to_path_buf(), error, } } @@ -50,35 +50,34 @@ impl Display for FileError { impl Error for FileError {} -fn handle_file(file: impl AsRef<Path>, conn: &Connection) -> Result<()> { - if conn.check_file(&file)? { +fn handle_file(file: &Path, conn: &Connection) -> Result<()> { + if conn.check_file(file)? { let modtime = file - .as_ref() .metadata()? .modified()? .duration_since(UNIX_EPOCH)? .as_secs(); - let db_modtime = conn.get_modtime(&file)?; + let db_modtime = conn.get_modtime(file)?; if modtime != db_modtime { - conn.update_file(&file)?; + conn.update_file(file)?; } return Ok(()); } - conn.insert_file(&file)?; + conn.insert_file(file)?; Ok(()) } pub fn index_files_recursively( - path: impl AsRef<Path>, + path: &Path, conn: &Connection, handler: Arc<AtomicBool>, ) -> Result<()> { - if !path.as_ref().is_dir() { + if !path.is_dir() { return Err(anyhow!("Invalid root directory")); } - let abspath = path.as_ref().canonicalize()?; + let abspath = path.canonicalize()?; #[cfg(not(test))] let bar = ProgressBar::with_draw_target(Some(0), ProgressDrawTarget::stdout_with_hz(60)) @@ -108,7 +107,7 @@ pub fn index_files_recursively( } if path.extension().is_some_and(|x| x == "flac") { if let Err(error) = handle_file(&path, conn) { - eprintln!("{}", FileError::new(path, error)); + eprintln!("{}", FileError::new(&path, error)); } else { #[cfg(not(test))] bar.inc(1); @@ -172,7 +171,7 @@ pub fn reencode_files(conn: Connection, handler: Arc<AtomicBool>, threads: usize Err(error) => eprintln!("{}", FileError::new(&file, error)), Ok(false) => { if let Err(error) = lock.lock().unwrap().update_file(&file) { - eprintln!("{}", FileError::new(file, error)); + eprintln!("{}", FileError::new(&file, error)); } #[cfg(not(test))] bar.inc(1) @@ -205,6 +204,7 @@ pub fn clean_files(conn: &Connection, handler: Arc<AtomicBool>) -> Result<()> { spinner.tick(); files.iter().for_each(|file| { + #[allow(clippy::collapsible_if)] if handler.load(Ordering::SeqCst) && !file.exists() { if let Err(error) = conn.remove_file(file) { eprintln!("{}", FileError::new(file, error)) @@ -227,7 +227,7 @@ mod tests { #[test] fn test_index_lots_of_files() { - let dbname = "temp3.db"; + let dbname = PathBuf::from("temp3.db"); let handler = Arc::new(AtomicBool::new(true)); let conn = Connection::new(Some(&dbname)).unwrap(); index_files_recursively(Path::new("./testfiles"), &conn, handler).unwrap(); @@ -236,7 +236,7 @@ mod tests { #[test] fn test_clean_files() { - let dbname = "temp4.db"; + let dbname = PathBuf::from("temp4.db"); let handler = Arc::new(AtomicBool::new(true)); let conn = Connection::new(Some(&dbname)).unwrap(); let filenames = [ @@ -247,7 +247,8 @@ mod tests { ]; std::fs::copy("./samples/32bit.flac", "./samples/nonexisting.flac").unwrap(); for file in filenames { - conn.insert_file(&file).unwrap(); + let filename = PathBuf::from(file); + conn.insert_file(&filename).unwrap(); } std::fs::remove_file("./samples/nonexisting.flac").unwrap(); @@ -260,7 +261,7 @@ mod tests { #[test] fn test_reencode_lots_of_files() { - let dbname = "temp5.db"; + let dbname = PathBuf::from("temp5.db"); let handler = Arc::new(AtomicBool::new(true)); let conn = Connection::new(Some(&dbname)).unwrap(); let temp = handler.clone(); diff --git a/src/flac.rs b/src/flac.rs index 7b542ac..e882cd7 100644 --- a/src/flac.rs +++ b/src/flac.rs @@ -1,7 +1,9 @@ use anyhow::{Result, anyhow}; -use claxon::{FlacReader, FlacReaderOptions}; use flac_bound::FlacEncoder; -use metaflac::{Block, Tag}; +use flac_codec::{ + decode::{Metadata, verify}, + *, +}; use std::{ path::Path, sync::{ @@ -10,52 +12,63 @@ use std::{ }, }; -pub const CURRENT_VENDOR: &str = "reference libFLAC 1.5.0 20250211"; - +pub(crate) const CURRENT_VENDOR: &str = "reference libFLAC 1.5.0 20250211"; const BADTAGS: [&str; 3] = ["encoded_by", "encodedby", "encoder"]; -fn write_tags(filename: impl AsRef<Path>) -> Result<()> { - let tags = Tag::read_from_path(&filename)?; - let temp_name = filename.as_ref().with_extension("tmp"); - let mut output = Tag::read_from_path(&temp_name)?; - - for block in tags.blocks() { - match block { - Block::VorbisComment(block) => { - for (key, val) in block.comments.iter() { - if !BADTAGS.contains(&key.to_lowercase().as_str()) { - output.set_vorbis(key, val.to_owned()); - } - } - } - Block::Padding(_) => {} - _ => output.push_block(block.to_owned()), - } - } - - output.write_to_path(temp_name)?; - Ok(()) -} +fn encode_file(filename: &Path, handler: Arc<AtomicBool>) -> Result<bool> { + if verify(filename).is_err() { + return Err(anyhow!("corrupt file")); + }; -fn encode_file(filename: impl AsRef<Path>, handler: Arc<AtomicBool>) -> Result<bool> { - let temp_name = filename.as_ref().with_extension("tmp"); + let temp_name = filename.with_extension("tmp"); if temp_name.exists() { std::fs::remove_file(&temp_name)?; } - let mut decoder = FlacReader::open(&filename)?; - let streaminfo = decoder.streaminfo(); - let num_channels: usize = streaminfo.channels.try_into()?; + let mut reader = decode::FlacSampleReader::open(filename)?; + + let blocklist = reader.metadata(); + + let streaminfo = blocklist.streaminfo(); + + let channels = streaminfo.channel_count() as u32; + + let metadata = blocklist + .blocks() + .filter_map(|block| { + use metadata::Block; + use metadata::BlockRef::*; + match block { + SeekTable(table) => Some(Block::SeekTable(table.clone())), + Application(app) => Some(Block::Application(app.clone())), + Cuesheet(sheet) => Some(Block::Cuesheet(sheet.clone())), + Picture(picture) => Some(Block::Picture(picture.clone())), + VorbisComment(comments) => { + let mut cloned = comments.clone(); + for tag in BADTAGS { + cloned.remove(tag); + } + cloned.vendor_string = CURRENT_VENDOR.to_string(); + Some(Block::VorbisComment(cloned)) + } + _ => None, + } + }) + .collect::<Vec<metadata::Block>>(); let mut encoder = if let Some(encoder) = FlacEncoder::new() { - if let Ok(encoder) = encoder - .channels(streaminfo.channels) - .bits_per_sample(streaminfo.bits_per_sample) - .sample_rate(streaminfo.sample_rate) - .compression_level(8) - .verify(false) - .init_file(&temp_name) - { + if let Ok(encoder) = { + let mut encoder = encoder + .channels(streaminfo.channel_count() as u32) + .bits_per_sample(streaminfo.bits_per_sample()) + .sample_rate(streaminfo.sample_rate()) + .compression_level(8) + .verify(false); + if let Some(size) = reader.total_samples() { + encoder = encoder.total_samples_estimate(size) + } + encoder.init_file(&temp_name) + } { encoder } else { return Err(anyhow!("failed to create encoder")); @@ -64,28 +77,26 @@ fn encode_file(filename: impl AsRef<Path>, handler: Arc<AtomicBool>) -> Result<b return Err(anyhow!("failed to create encoder")); }; - let mut frame_reader = decoder.blocks(); - let mut buffer = Vec::new(); - let mut block_buffer = Vec::with_capacity(streaminfo.max_block_size as usize * num_channels); - while handler.load(Ordering::SeqCst) { - match frame_reader.read_next_or_eof(block_buffer) { - Ok(Some(block)) => { - for ch in 0..block.channels() { - buffer.push(block.channel(ch)); - } + match reader.fill_buf() { + Ok(buf) => { + if !buf.is_empty() { + let length = buf.len(); + if encoder + .process_interleaved(buf, length as u32 / channels) + .is_err() + { + return Err(anyhow!( + "Error while processing samples:\t{:?}", + encoder.state() + )); + }; - if encoder.process(&buffer).is_err() { - return Err(anyhow!( - "Error while processing samples:\t{:?}", - encoder.state() - )); - }; - buffer.clear(); - buffer = buffer.into_iter().map(|_| unreachable!()).collect(); - block_buffer = block.into_buffer(); + reader.consume(length); + } else { + break; + } } - Ok(None) => break, Err(error) => return Err(error.into()), } } @@ -99,32 +110,51 @@ fn encode_file(filename: impl AsRef<Path>, handler: Arc<AtomicBool>) -> Result<b if let Err(enc) = encoder.finish() { return Err(anyhow!("Encoding failed:\t{:?}", enc.state())); } - write_tags(&filename)?; - std::fs::rename(temp_name, filename)?; + + metadata::update(&temp_name, |blocklist| { + for block in metadata { + use metadata::Block::*; + match block { + Application(b) => { + let _ = blocklist.insert(b); + } + Picture(b) => { + let _ = blocklist.insert(b); + } + VorbisComment(b) => { + let _ = blocklist.insert(b); + } + Cuesheet(b) => { + let _ = blocklist.insert(b); + } + SeekTable(b) => { + let _ = blocklist.insert(b); + } + _ => {} + } + } + Ok::<(), flac_codec::Error>(()) + })?; + + std::fs::rename(&temp_name, filename)?; + Ok(false) } -pub fn handle_encode(filename: impl AsRef<Path>, handler: Arc<AtomicBool>) -> Result<bool> { - match encode_file(&filename, handler) { +pub fn handle_encode(filename: &Path, handler: Arc<AtomicBool>) -> Result<bool> { + match encode_file(filename, handler) { Err(error) => { - let _ = std::fs::remove_file(filename.as_ref().with_extension("tmp")); + let _ = std::fs::remove_file(filename.with_extension("tmp")); Err(error) } Ok(res) => Ok(res), } } -pub fn get_vendor(file: impl AsRef<Path>) -> Result<String> { - if let Some(vendor) = FlacReader::open_ext( - file, - FlacReaderOptions { - metadata_only: true, - read_vorbis_comment: true, - }, - )? - .vendor() - { - Ok(vendor.to_string()) +pub fn get_vendor(file: &Path) -> Result<String> { + let blocklist = metadata::BlockList::open(file)?; + if let Some(data) = blocklist.get::<metadata::VorbisComment>() { + Ok(data.vendor_string.to_owned()) } else { Err(anyhow!("Vendor string not found")) } @@ -133,17 +163,18 @@ pub fn get_vendor(file: impl AsRef<Path>) -> Result<String> { #[cfg(test)] mod tests { use super::*; + use std::path::PathBuf; #[test] fn bit16() { - let name = "./samples/16bit.flac"; - let tempname = "./samples/16bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); + let name = PathBuf::from("./samples/16bit.flac"); + let tempname = PathBuf::from("./samples/16bit.flac.temp"); + std::fs::copy(&name, &tempname).unwrap(); let handler = Arc::new(AtomicBool::new(true)); - encode_file(name, handler).unwrap(); + encode_file(&name, handler).unwrap(); let output = std::process::Command::new("flac") .arg("-wts") - .arg(name) + .arg(&name) .status(); std::fs::rename(tempname, name).unwrap(); assert!(output.unwrap().success()); @@ -151,14 +182,14 @@ mod tests { #[test] fn bit24() { - let name = "./samples/24bit.flac"; - let tempname = "./samples/24bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); + let name = PathBuf::from("./samples/24bit.flac"); + let tempname = PathBuf::from("./samples/24bit.flac.temp"); + std::fs::copy(&name, &tempname).unwrap(); let handler = Arc::new(AtomicBool::new(true)); - encode_file(name, handler).unwrap(); + encode_file(&name, handler).unwrap(); let output = std::process::Command::new("flac") .arg("-wts") - .arg(name) + .arg(&name) .status(); std::fs::rename(tempname, name).unwrap(); assert!(output.unwrap().success()); @@ -166,14 +197,14 @@ mod tests { #[test] fn bit32() { - let name = "./samples/32bit.flac"; - let tempname = "./samples/32bit.flac.temp"; - std::fs::copy(name, tempname).unwrap(); + let name = PathBuf::from("./samples/32bit.flac"); + let tempname = PathBuf::from("./samples/32bit.flac.temp"); + std::fs::copy(&name, &tempname).unwrap(); let handler = Arc::new(AtomicBool::new(true)); - encode_file(name, handler).unwrap(); + encode_file(&name, handler).unwrap(); let output = std::process::Command::new("flac") .arg("-wts") - .arg(name) + .arg(&name) .status(); std::fs::rename(tempname, name).unwrap(); assert!(output.unwrap().success()); |
