summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjakka <jakkadoujin@gmail.com>2025-06-11 22:07:55 +0300
committerjakka <jakkadoujin@gmail.com>2025-06-11 22:07:55 +0300
commit0f111dc8f998b7882776009b42f2b08cbee4b7f5 (patch)
tree607b6294abfb03975b4c0df6ea33354cbca3f5bf /src
parent6de78f23f658f92ffa2c4be70aa0eb71fed89bcd (diff)
cleaned flac code. started writing db logic
Diffstat (limited to 'src')
-rw-r--r--src/db.rs145
-rw-r--r--src/flac.rs150
-rw-r--r--src/main.rs73
3 files changed, 274 insertions, 94 deletions
diff --git a/src/db.rs b/src/db.rs
index 8b13789..e42d235 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -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!()
}