diff options
| author | jakka <jakkadoujin@gmail.com> | 2025-06-11 22:07:55 +0300 |
|---|---|---|
| committer | jakka <jakkadoujin@gmail.com> | 2025-06-11 22:07:55 +0300 |
| commit | 0f111dc8f998b7882776009b42f2b08cbee4b7f5 (patch) | |
| tree | 607b6294abfb03975b4c0df6ea33354cbca3f5bf /src | |
| parent | 6de78f23f658f92ffa2c4be70aa0eb71fed89bcd (diff) | |
cleaned flac code. started writing db logic
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.rs | 145 | ||||
| -rw-r--r-- | src/flac.rs | 150 | ||||
| -rw-r--r-- | src/main.rs | 73 |
3 files changed, 274 insertions, 94 deletions
@@ -1 +1,146 @@ +use anyhow::{Ok, Result, anyhow}; +use directories::BaseDirs; +use futures_util::StreamExt; +use libsql::{Builder, Connection, params}; +use std::{ + ffi::OsStr, + fmt::Display, + path::{Path, absolute}, +}; +use crate::flac; + +pub async fn open_db() -> Result<Connection> { + let conn = if let Some(base_dir) = BaseDirs::new() { + let db_name = Path::new(base_dir.data_dir()).join("reencoder.db"); + Builder::new_local(db_name).build().await?.connect()? + } else { + return Err(anyhow!("Failed to locate data directory")); + }; + + conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, vendor TEXT, toencode BOOLEAN NOT NULL)", ()).await?; + + Ok(conn) +} + +#[derive(Debug)] +enum Errors { + EmptyQuery, +} + +impl Display for Errors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Errors::EmptyQuery => write!(f, "Empty query"), + } + } +} + +trait Reencoder { + async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>; + async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>; + async fn get_files_toencode(&self) -> Result<Vec<String>>; +} + +impl Reencoder for Connection { + async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> { + let file = Path::new(filename); + let vendor = flac::get_vendor(file); + let toencode = !matches!(vendor.as_str(), flac::CURRENT_VENDOR); + + let abs_filename = absolute(file)?; + + self.execute( + "INSERT INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)", + params![abs_filename.to_str().unwrap(), vendor, toencode], + ) + .await?; + + Ok(()) + } + + async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> { + let abs_filename = absolute(Path::new(filename))?; + + self.execute( + "REPLACE INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)", + params![abs_filename.to_str().unwrap(), flac::CURRENT_VENDOR, false], + ) + .await?; + + Ok(()) + } + + async fn get_files_toencode(&self) -> Result<Vec<String>> { + let rows = self + .query("SELECT path FROM flacs WHERE toencode", ()) + .await?; + if rows.column_count() == 0 { + return Err(anyhow!(Errors::EmptyQuery)); + }; + + let filenames = rows + .into_stream() + .map(|row| row.unwrap().get_str(0).unwrap().to_string()) + .collect::<Vec<String>>() + .await; + + Ok(filenames) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + async fn dummy_db(name: impl AsRef<Path>) -> Connection { + let conn = Builder::new_local(name) + .build() + .await + .unwrap() + .connect() + .unwrap(); + conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, vendor TEXT, toencode BOOLEAN NOT NULL)", ()).await.unwrap(); + conn + } + + #[tokio::test] + async fn check_localfiles() { + let dbname = String::from("temp1.db"); + let filenames = ["16bit.flac", "24bit.flac", "32bit.flac"]; + let conn = dummy_db(&dbname).await; + for file in filenames { + let _ = conn.insert_file(&file.to_string()).await; + } + let returned = conn.get_files_toencode().await.unwrap(); + std::fs::remove_file(dbname).unwrap(); + assert!(returned.is_empty()) + } + + #[tokio::test] + async fn check_update() { + let dbname = String::from("temp2.db"); + let filenames = ["16bit.flac", "24bit.flac", "32bit.flac"]; + let conn = dummy_db(&dbname).await; + for file in filenames { + let _ = conn.insert_file(&file.to_string()).await; + } + + let _ = conn + .execute( + "REPLACE INTO flacs (path, vendor, toencode) VALUES (?1, ?2, ?3)", + params![ + absolute(Path::new("16bit.flac")).unwrap().to_str(), + "", + true + ], + ) + .await; + + conn.update_file(&"16bit.flac".to_string()).await.unwrap(); + + let returned = conn.get_files_toencode().await.unwrap(); + std::fs::remove_file(dbname).unwrap(); + assert!(returned.is_empty()) + } +} diff --git a/src/flac.rs b/src/flac.rs index 9b1b7f3..a4c204e 100644 --- a/src/flac.rs +++ b/src/flac.rs @@ -3,7 +3,7 @@ use flac_bound::{FlacEncoder, WriteWrapper}; use i24::i24; use md5::{Digest, Md5, Md5Core, digest::core_api::CoreWrapper}; use metaflac::{Block, Tag}; -use std::fs::File; +use std::{ffi::OsStr, fs::File, path::Path}; use symphonia::core::{ audio::{Audio, GenericAudioBufferRef}, codecs::audio::AudioDecoder, @@ -12,6 +12,9 @@ use symphonia::core::{ meta::MetadataOptions, }; +#[allow(dead_code)] +pub const CURRENT_VENDOR: &str = "reference libFLAC 1.5.0 20250211"; + struct StreamConfig { channels: u32, bits_per_sample: Bps, @@ -201,8 +204,14 @@ fn encode_cycle_32( Ok(()) } -pub fn encode_file(file: &std::path::Path) -> Result<()> { - let src = std::fs::File::open(file)?; +fn init_decoder( + filename: impl AsRef<Path>, +) -> Result<( + Box<dyn FormatReader>, + Box<dyn AudioDecoder + 'static>, + StreamConfig, +)> { + let src = std::fs::File::open(filename)?; let mss = MediaSourceStream::new(Box::new(src), Default::default()); let mut hint = Hint::new(); hint.with_extension("flac"); @@ -231,9 +240,44 @@ pub fn encode_file(file: &std::path::Path) -> Result<()> { sample_rate: params.sample_rate.unwrap(), }; - let tempname = format!("{}.tmp", file.display()); + Ok((format, decoder, config)) +} + +fn write_tags( + file: impl AsRef<Path>, + tempname: impl AsRef<Path>, + hasher: impl Digest, +) -> Result<()> { + let tags = Tag::read_from_path(file)?; + let mut output = Tag::read_from_path(&tempname)?; + let mut streaminfo = tags.get_streaminfo().unwrap().clone(); + + streaminfo.md5 = hasher.finalize()[..].to_vec(); + output.set_streaminfo(streaminfo); + + for block in tags.blocks() { + match block { + Block::VorbisComment(comment) => { + for (key, val) in comment.comments.clone() { + output.set_vorbis(key, val); + } + } + Block::StreamInfo(_) => {} + _ => output.push_block(block.clone()), + } + } + + output.write_to_path(&tempname)?; + Ok(()) +} + +pub fn encode_file(filename: impl AsRef<OsStr>) -> Result<()> { + let file = Path::new(&filename); + let tempname = &format!("{}.tmp", file.to_str().unwrap()); - let mut outf = File::create(&tempname)?; + let (format, decoder, config) = init_decoder(file)?; + + let mut outf = File::create(tempname)?; let mut outw = WriteWrapper(&mut outf); let enc = FlacEncoder::new() .unwrap() @@ -253,28 +297,86 @@ pub fn encode_file(file: &std::path::Path) -> Result<()> { Bps::_32 => encode_cycle_32(format, decoder, enc, &mut hasher), }?; - let tags = Tag::read_from_path(file)?; - let mut output = Tag::read_from_path(&tempname)?; - let mut streaminfo = tags.get_streaminfo().unwrap().clone(); + write_tags(file, tempname, hasher)?; - streaminfo.md5 = hasher.finalize()[..].to_vec(); - output.set_streaminfo(streaminfo); + std::fs::rename(tempname, file)?; - for block in tags.blocks() { - match block { - Block::VorbisComment(comment) => { - for (key, val) in comment.comments.clone() { - output.set_vorbis(key, val); - } - } - Block::StreamInfo(_) => {} - _ => output.push_block(block.clone()), - } - } + Ok(()) +} - output.write_to_path(&tempname)?; +pub fn get_vendor(file: &Path) -> String { + let tag = Tag::read_from_path(file).unwrap(); + tag.vorbis_comments().unwrap().vendor_string.clone() +} - std::fs::rename(&tempname, file)?; +#[cfg(test)] +mod tests { + use super::*; + use metaflac::Tag; - Ok(()) + #[test] + fn bit16() { + let name = "16bit.flac"; + let tempname = "16bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(std::path::Path::new(name)).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(tempname).unwrap(); + assert_eq!(target_md5, encoded_md5); + } + + #[test] + fn bit24() { + let name = "24bit.flac"; + let tempname = "24bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(std::path::Path::new(name)).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(tempname).unwrap(); + assert_eq!(target_md5, encoded_md5); + } + + #[test] + fn bit32() { + let name = "32bit.flac"; + let tempname = "32bit.flac.temp"; + std::fs::copy(name, tempname).unwrap(); + encode_file(std::path::Path::new(name)).unwrap(); + let target_md5 = Tag::read_from_path(tempname) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + let encoded_md5 = Tag::read_from_path(name) + .unwrap() + .get_streaminfo() + .unwrap() + .md5 + .clone(); + std::fs::remove_file(tempname).unwrap(); + assert_eq!(target_md5, encoded_md5); + } } diff --git a/src/main.rs b/src/main.rs index 3f73a25..6a67fc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,74 +1,7 @@ mod db; mod flac; -fn main() { - if let Err(err) = flac::encode_file(std::path::Path::new("32bit.flac")) { - println!("{}", err) - }; -} - -#[cfg(test)] -mod tests { - use super::*; - use metaflac::Tag; - - #[test] - fn bit16() { - std::fs::copy("16bit.flac", "16bit.flac.temp").unwrap(); - flac::encode_file(std::path::Path::new("16bit.flac")).unwrap(); - let target_md5 = Tag::read_from_path("16bit.flac.temp") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path("16bit.flac") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file("16bit.flac.temp").unwrap(); - assert_eq!(target_md5, encoded_md5); - } - - #[test] - fn bit24() { - std::fs::copy("24bit.flac", "24bit.flac.temp").unwrap(); - flac::encode_file(std::path::Path::new("24bit.flac")).unwrap(); - let target_md5 = Tag::read_from_path("24bit.flac.temp") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path("24bit.flac") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file("24bit.flac.temp").unwrap(); - assert_eq!(target_md5, encoded_md5); - } - - #[test] - fn bit32() { - std::fs::copy("32bit.flac", "32bit.flac.temp").unwrap(); - flac::encode_file(std::path::Path::new("32bit.flac")).unwrap(); - let target_md5 = Tag::read_from_path("32bit.flac.temp") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - let encoded_md5 = Tag::read_from_path("32bit.flac") - .unwrap() - .get_streaminfo() - .unwrap() - .md5 - .clone(); - std::fs::remove_file("32bit.flac.temp").unwrap(); - assert_eq!(target_md5, encoded_md5); - } +#[tokio::main] +async fn main() { + todo!() } |
