summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db.rs73
-rw-r--r--src/files.rs35
-rw-r--r--src/flac.rs209
3 files changed, 168 insertions, 149 deletions
diff --git a/src/db.rs b/src/db.rs
index 7f0ccef..cdce946 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -22,21 +22,21 @@ const GET_MODTIME: &str = "SELECT modtime FROM flacs WHERE path = ?1";
pub trait Database {
type Conn;
- fn new(path: Option<impl AsRef<Path>>) -> Result<Self::Conn>;
- fn insert_file(&self, filename: impl AsRef<Path>) -> Result<()>;
- fn update_file(&self, filename: impl AsRef<Path>) -> Result<()>;
- fn check_file(&self, filename: impl AsRef<Path>) -> Result<bool>;
+ fn new(path: Option<&PathBuf>) -> Result<Self::Conn>;
+ fn insert_file(&self, filename: &Path) -> Result<()>;
+ fn update_file(&self, filename: &Path) -> Result<()>;
+ fn check_file(&self, filename: &Path) -> Result<bool>;
fn init_clean_files(&self) -> Result<Vec<PathBuf>, rusqlite::Error>;
- fn remove_file(&self, filename: impl AsRef<Path>) -> Result<()>;
+ fn remove_file(&self, filename: &Path) -> Result<()>;
fn get_toencode_files(&self) -> Result<Vec<PathBuf>, rusqlite::Error>;
fn get_toencode_number(&self) -> Result<u64, rusqlite::Error>;
- fn get_modtime(&self, file: impl AsRef<Path>) -> Result<u64>;
+ fn get_modtime(&self, file: &Path) -> Result<u64>;
fn vacuum(&self) -> Result<()>;
}
impl Database for Connection {
type Conn = Connection;
- fn new(path: Option<impl AsRef<Path>>) -> Result<Self> {
+ fn new(path: Option<&PathBuf>) -> Result<Self> {
let conn = if let Some(file) = path {
Connection::open(file)?
} else if let Some(base_dir) = BaseDirs::new() {
@@ -49,11 +49,10 @@ impl Database for Connection {
Ok(conn)
}
- fn insert_file(&self, filename: impl AsRef<Path>) -> Result<()> {
+ fn insert_file(&self, filename: &Path) -> Result<()> {
let toencode = !matches!(get_vendor(&filename)?.as_str(), CURRENT_VENDOR);
let modtime = filename
- .as_ref()
.metadata()?
.modified()?
.duration_since(UNIX_EPOCH)?
@@ -61,15 +60,14 @@ impl Database for Connection {
self.execute(
ADD_ITEM,
- params![filename.as_ref().to_str().unwrap(), toencode, modtime],
+ params![filename.to_str().unwrap(), toencode, modtime],
)?;
Ok(())
}
- fn update_file(&self, filename: impl AsRef<Path>) -> Result<()> {
+ fn update_file(&self, filename: &Path) -> Result<()> {
let modtime = filename
- .as_ref()
.metadata()?
.modified()?
.duration_since(UNIX_EPOCH)?
@@ -77,21 +75,17 @@ impl Database for Connection {
self.execute(
UPDATE_ITEM,
- params![filename.as_ref().to_str().unwrap(), false, modtime],
+ params![filename.to_str().unwrap(), false, modtime],
)?;
Ok(())
}
- fn check_file(&self, filename: impl AsRef<Path>) -> Result<bool> {
- if self.query_one(
- CHECK_FILE,
- params!(filename.as_ref().to_str().unwrap()),
- |row| {
- let num: bool = row.get(0)?;
- Ok(num)
- },
- )? {
+ fn check_file(&self, filename: &Path) -> Result<bool> {
+ if self.query_one(CHECK_FILE, params!(filename.to_str().unwrap()), |row| {
+ let num: bool = row.get(0)?;
+ Ok(num)
+ })? {
Ok(true)
} else {
Ok(false)
@@ -110,8 +104,8 @@ impl Database for Connection {
Ok(files)
}
- fn remove_file(&self, filename: impl AsRef<Path>) -> Result<()> {
- self.execute(REMOVE_FILE, params!(filename.as_ref().to_str().unwrap()))?;
+ fn remove_file(&self, filename: &Path) -> Result<()> {
+ self.execute(REMOVE_FILE, params!(filename.to_str().unwrap()))?;
Ok(())
}
@@ -133,15 +127,13 @@ impl Database for Connection {
})
}
- fn get_modtime(&self, file: impl AsRef<Path>) -> Result<u64> {
- Ok(self.query_one(
- GET_MODTIME,
- params![file.as_ref().to_str().unwrap()],
- |row| {
+ fn get_modtime(&self, file: &Path) -> Result<u64> {
+ Ok(
+ self.query_one(GET_MODTIME, params![file.to_str().unwrap()], |row| {
let modtime: u64 = row.get(0)?;
Ok(modtime)
- },
- )?)
+ })?,
+ )
}
fn vacuum(&self) -> Result<()> {
@@ -157,7 +149,7 @@ mod tests {
#[test]
fn check_localfiles() {
- let dbname = String::from("temp1.db");
+ let dbname = PathBuf::from("temp1.db");
let filenames = [
"./samples/16bit.flac",
"./samples/24bit.flac",
@@ -166,7 +158,8 @@ mod tests {
let mut counter = 0;
let conn = Connection::new(Some(&dbname)).unwrap();
for file in filenames {
- conn.insert_file(&file.to_string()).unwrap();
+ let filename = PathBuf::from(file);
+ conn.insert_file(&filename).unwrap();
}
let mut stmt = conn.prepare(TOENCODE_PATHS).unwrap();
let mut returned = stmt.query(()).unwrap();
@@ -180,7 +173,7 @@ mod tests {
#[test]
fn check_update() {
- let dbname = String::from("temp2.db");
+ let dbname = PathBuf::from("temp2.db");
let filenames = [
"./samples/16bit.flac",
"./samples/24bit.flac",
@@ -188,7 +181,7 @@ mod tests {
];
let conn = Connection::new(Some(&dbname)).unwrap();
for file in filenames {
- conn.insert_file(Path::new(file).canonicalize().unwrap())
+ conn.insert_file(&Path::new(file).canonicalize().unwrap())
.unwrap();
}
@@ -206,14 +199,8 @@ mod tests {
)
.unwrap();
- conn.update_file(
- Path::new("./samples/16bit.flac")
- .canonicalize()
- .unwrap()
- .to_str()
- .unwrap(),
- )
- .unwrap();
+ conn.update_file(&Path::new("./samples/16bit.flac").canonicalize().unwrap())
+ .unwrap();
let mut stmt = conn.prepare(TOENCODE_PATHS).unwrap();
let mut returned = stmt.query(()).unwrap();
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());