summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjakka <jakka@jakka.su>2025-09-27 17:40:39 +0300
committerjakka <jakka@jakka.su>2025-09-27 17:40:39 +0300
commit1aa81a1c3d5e56a0a6024beed0ba47ecea1a3492 (patch)
tree2384bb4714c2d234de4d576452ef4708e15149be /src
parent20d6438440de1f63eeb9d80eced475e001be02f0 (diff)
parent9e4796570baf97d2193745a350e9611fa21d6a78 (diff)
merged main
Diffstat (limited to 'src')
-rw-r--r--src/db.rs9
-rw-r--r--src/files.rs35
-rw-r--r--src/flac.rs209
3 files changed, 139 insertions, 114 deletions
diff --git a/src/db.rs b/src/db.rs
index 31c5e93..e75d86f 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -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());