diff --git a/Cargo.lock b/Cargo.lock index 61832fe..26f81b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,18 +179,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.6.0" @@ -381,8 +369,6 @@ dependencies = [ "fs2", "futures-util", "http", - "image", - "image_hasher", "indicatif", "lazy_static", "log", @@ -391,6 +377,7 @@ dependencies = [ "regex", "reqwest", "rustls-native-certs", + "rusty-chromaprint", "serde", "serde_json", "serde_plain", @@ -951,32 +938,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "num-traits", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image_hasher" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481465fe767d92494987319b0b447a5829edf57f09c52bf8639396abaaeaf78" -dependencies = [ - "base64 0.22.0", - "image", - "rustdct", - "serde", - "transpose", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1417,6 +1378,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "realfft" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -1531,21 +1501,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +[[package]] +name = "rubato" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dd52e80cfc21894deadf554a5673002938ae4625f7a283e536f9cf7c17b0d5" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustdct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b61555105d6a9bf98797c063c362a1d24ed8ab0431655e38f1cf51e52089551" -dependencies = [ - "rustfft", -] - [[package]] name = "rustfft" version = "6.2.0" @@ -1628,6 +1601,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rusty-chromaprint" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1755646867c36ecb391776deaa0b557a76d3badf20c142de7282630c34b20440" +dependencies = [ + "rubato", + "rustfft", +] + [[package]] name = "ryu" version = "1.0.17" @@ -2501,18 +2484,3 @@ name = "zeroize" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63381fa6624bf92130a6b87c0d07380116f80b565c42cf0d754136f0238359ef" - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-jpeg" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" -dependencies = [ - "zune-core", -] diff --git a/crunchy-cli-core/Cargo.toml b/crunchy-cli-core/Cargo.toml index e31ef46..517284a 100644 --- a/crunchy-cli-core/Cargo.toml +++ b/crunchy-cli-core/Cargo.toml @@ -24,14 +24,13 @@ derive_setters = "0.1" futures-util = { version = "0.3", features = ["io"] } fs2 = "0.4" http = "1.1" -image = { version = "0.25", features = ["jpeg"], default-features = false } -image_hasher = "2.0" indicatif = "0.17" lazy_static = "1.4" log = { version = "0.4", features = ["std"] } num_cpus = "1.16" regex = "1.10" reqwest = { version = "0.12", features = ["socks", "stream"] } +rusty-chromaprint = "0.2" serde = "1.0" serde_json = "1.0" serde_plain = "1.0" diff --git a/crunchy-cli-core/src/archive/filter.rs b/crunchy-cli-core/src/archive/filter.rs index f638c50..b08fb6c 100644 --- a/crunchy-cli-core/src/archive/filter.rs +++ b/crunchy-cli-core/src/archive/filter.rs @@ -333,7 +333,7 @@ impl Filter for ArchiveFilter { .unwrap() .push(episode.season_number) } - + if episodes.is_empty() { return Ok(None); } diff --git a/crunchy-cli-core/src/utils/download.rs b/crunchy-cli-core/src/utils/download.rs index e359bd1..cfec7b4 100644 --- a/crunchy-cli-core/src/utils/download.rs +++ b/crunchy-cli-core/src/utils/download.rs @@ -281,6 +281,7 @@ impl Downloader { format_id: i, path, locale: locale.clone(), + sample_rate: stream_data.sampling_rate().unwrap(), video_idx: i, }) } diff --git a/crunchy-cli-core/src/utils/sync.rs b/crunchy-cli-core/src/utils/sync.rs index 5dafcac..7535645 100644 --- a/crunchy-cli-core/src/utils/sync.rs +++ b/crunchy-cli-core/src/utils/sync.rs @@ -1,6 +1,9 @@ +use std::io::Read; +use std::process::Stdio; use std::{ cmp, collections::{HashMap, HashSet}, + mem, ops::Not, path::Path, process::Command, @@ -12,6 +15,7 @@ use log::debug; use tempfile::TempPath; use anyhow::{bail, Result}; +use rusty_chromaprint::{Configuration, Fingerprinter}; use super::fmt::format_time_delta; @@ -19,6 +23,7 @@ pub struct SyncAudio { pub format_id: usize, pub path: TempPath, pub locale: Locale, + pub sample_rate: u32, pub video_idx: usize, } @@ -43,11 +48,12 @@ pub fn sync_audios( continue; } formats.insert(audio.format_id); - sync_audios.push((audio.format_id, &audio.path)); + sync_audios.push((audio.format_id, &audio.path, audio.sample_rate)); chromaprints.insert( audio.format_id, generate_chromaprint( &audio.path, + audio.sample_rate, &TimeDelta::zero(), &TimeDelta::zero(), &TimeDelta::zero(), @@ -112,6 +118,7 @@ pub fn sync_audios( for sync_audio in &sync_audios { let chromaprint = generate_chromaprint( sync_audio.1, + sync_audio.2, &start, &end, initial_offsets.get(&sync_audio.0).unwrap(), @@ -127,7 +134,7 @@ pub fn sync_audios( ); chromaprints.insert( base_audio.0, - generate_chromaprint(base_audio.1, &start, &end, &base_offset)?, + generate_chromaprint(base_audio.1, base_audio.2, &start, &end, &base_offset)?, ); for audio in &sync_audios { let initial_offset = initial_offsets.get(&audio.0).copied().unwrap(); @@ -207,6 +214,7 @@ fn find_offset( fn generate_chromaprint( input_file: &Path, + sample_rate: u32, start: &TimeDelta, end: &TimeDelta, offset: &TimeDelta, @@ -218,6 +226,9 @@ fn generate_chromaprint( offset_argument = offset; }; + let mut printer = Fingerprinter::new(&Configuration::preset_test1()); + printer.start(sample_rate, 2)?; + let mut command = Command::new("ffmpeg"); command .arg("-hide_banner") @@ -232,30 +243,42 @@ fn generate_chromaprint( .args(["-itsoffset", format_time_delta(offset_argument).as_str()]) .args(["-i", input_file.to_string_lossy().to_string().as_str()]) .args(["-ac", "2"]) - .args(["-f", "chromaprint"]) - .args(["-fp_format", "raw"]) + .args([ + "-f", + if cfg!(target_endian = "big") { + "s16be" + } else { + "s16le" + }, + ]) .arg("-"); - let extract_output = command.output()?; + let mut handle = command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; - if !extract_output.status.success() { - bail!( - "{}", - String::from_utf8_lossy(extract_output.stderr.as_slice()) - ); + // the stdout is read in chunks because keeping all the raw audio data in memory would take up + // a significant amount of space + let mut stdout = handle.stdout.take().unwrap(); + let mut buf: [u8; 32_000] = [0; 32_000]; + while handle.try_wait()?.is_none() { + loop { + let read_bytes = stdout.read(&mut buf)?; + if read_bytes == 0 { + break; + } + let data: [i16; 16_000] = unsafe { mem::transmute(buf) }; + printer.consume(&data[0..(read_bytes / 2)]) + } } - let raw_chromaprint = extract_output.stdout.as_slice(); - let length = raw_chromaprint.len(); - if length % 4 != 0 { - bail!("chromaprint bytes should be a multiple of 4"); + + if !handle.wait()?.success() { + bail!("{}", std::io::read_to_string(handle.stderr.unwrap())?) } - let mut chromaprint = Vec::with_capacity(length / 4); - for i in 0..length / 4 { - chromaprint.push(as_u32_le( - raw_chromaprint[i * 4..i * 4 + 4].try_into().unwrap(), - )); - } - Ok(chromaprint) + + printer.finish(); + return Ok(printer.fingerprint().into()); } fn compare_chromaprints( @@ -407,11 +430,3 @@ fn timestamps_to_ranges(mut timestamps: Vec) -> Option> { None } } - -fn as_u32_le(array: &[u8; 4]) -> u32 { - #![allow(arithmetic_overflow)] - (array[0] as u32) - | ((array[1] as u32) << 8) - | ((array[2] as u32) << 16) - | ((array[3] as u32) << 24) -}