From d5ab74d17a1bb6607bb095917ea98ade474eb75d Mon Sep 17 00:00:00 2001 From: chaos Date: Mon, 30 Oct 2023 23:17:52 +0000 Subject: [PATCH] move replaygain stuff into its own crate, run clippy --- Cargo.lock | 9 ++ Cargo.toml | 5 +- modules/ascii_reduce/build.rs | 4 +- modules/ascii_reduce/src/lib.rs | 8 +- modules/ffprobe/src/lib.rs | 12 ++- modules/replaygain/Cargo.toml | 8 ++ .../mod.rs => modules/replaygain/src/lib.rs | 91 ++++++++++++++++--- src/commands/process.rs | 7 +- src/types.rs | 31 +------ src/utils/formats/handlers/ffprobe.rs | 15 ++- src/utils/formats/handlers/flac.rs | 10 +- src/utils/formats/handlers/id3.rs | 10 +- src/utils/formats/handlers/taglib.rs | 10 +- src/utils/formats/mod.rs | 9 +- src/utils/mod.rs | 2 - 15 files changed, 169 insertions(+), 62 deletions(-) create mode 100644 modules/replaygain/Cargo.toml rename src/utils/replaygain/mod.rs => modules/replaygain/src/lib.rs (53%) diff --git a/Cargo.lock b/Cargo.lock index a8f0cc2..4e63271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,6 +683,7 @@ dependencies = [ "lazy_static", "metaflac", "notify", + "replaygain", "serde", "serde_json", "serde_with", @@ -876,6 +877,14 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "replaygain" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 93dba9e..a1697f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,9 @@ metaflac = { version = "0.2", optional = true } taglib = { path = "./modules/taglib", optional = true } ffprobe = { path = "./modules/ffprobe" } +# replaygain_analysis +replaygain = { path = "./modules/replaygain", optional = true } + # for genhtml command html-escape = { version = "0.2", optional = true } urlencoding = { version = "2", optional = true } @@ -73,4 +76,4 @@ ffprobe_extractor = [] # If to allow using ffmpeg/ffprobe as a fallback tag extr command_genhtml = ["dep:html-escape", "dep:urlencoding"] -replaygain = [] \ No newline at end of file +replaygain = ["dep:replaygain"] \ No newline at end of file diff --git a/modules/ascii_reduce/build.rs b/modules/ascii_reduce/build.rs index 5ee25a2..f7c92e4 100644 --- a/modules/ascii_reduce/build.rs +++ b/modules/ascii_reduce/build.rs @@ -37,12 +37,12 @@ fn main() { } let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); - let mut file = BufWriter::new(File::create(&path).unwrap()); + let mut file = BufWriter::new(File::create(path).unwrap()); write!( &mut file, "static MAPPINGS: phf::Map = {}", map.build() ) .unwrap(); - write!(&mut file, ";\n").unwrap(); + writeln!(&mut file, ";").unwrap(); } diff --git a/modules/ascii_reduce/src/lib.rs b/modules/ascii_reduce/src/lib.rs index 7c114bb..57111eb 100644 --- a/modules/ascii_reduce/src/lib.rs +++ b/modules/ascii_reduce/src/lib.rs @@ -2,10 +2,10 @@ include!(concat!(env!("OUT_DIR"), "/codegen.rs")); #[cfg(test)] mod tests { - #[test] - fn test() { - assert_eq!(crate::reduce("öwo owö 😊".to_string()), "owo owo "); - } + #[test] + fn test() { + assert_eq!(crate::reduce("öwo owö 😊".to_string()), "owo owo "); + } } pub fn reduce(input: String) -> String { diff --git a/modules/ffprobe/src/lib.rs b/modules/ffprobe/src/lib.rs index 64f38b7..b1824e4 100644 --- a/modules/ffprobe/src/lib.rs +++ b/modules/ffprobe/src/lib.rs @@ -1,12 +1,15 @@ pub mod errors; -pub mod types; mod ffprobe_output; +pub mod types; use std::{convert::Into, path::Path, process::Command}; use self::errors::{AnalyzeError, FFProbeError}; -fn extract(path: &Path, ffprobe_command: Option<&str>) -> Result { +fn extract( + path: &Path, + ffprobe_command: Option<&str>, +) -> Result { let output = Command::new(ffprobe_command.unwrap_or("ffprobe")) .args([ "-v", @@ -39,7 +42,10 @@ fn extract(path: &Path, ffprobe_command: Option<&str>) -> Result) -> Result { +pub fn analyze( + path: &Path, + ffprobe_command: Option<&str>, +) -> Result { let raw_data = extract(path, ffprobe_command)?; let mut data = types::FFProbeData { diff --git a/modules/replaygain/Cargo.toml b/modules/replaygain/Cargo.toml new file mode 100644 index 0000000..57ed301 --- /dev/null +++ b/modules/replaygain/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "replaygain" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.0", features = ["derive"] } +serde_json = "1.0" \ No newline at end of file diff --git a/src/utils/replaygain/mod.rs b/modules/replaygain/src/lib.rs similarity index 53% rename from src/utils/replaygain/mod.rs rename to modules/replaygain/src/lib.rs index 91e834d..3966f62 100644 --- a/src/utils/replaygain/mod.rs +++ b/modules/replaygain/src/lib.rs @@ -1,15 +1,75 @@ use std::{ + fmt, io::{BufRead, BufReader}, path::PathBuf, - process::Command, + process::{self, Command}, }; -use string_error::static_err; +#[derive(Debug, Clone)] +pub struct ReplayGainRawData { + pub track_gain: f64, + pub track_peak: f64, +} -use crate::types::ReplayGainRawData; +#[derive(Debug, Clone)] +pub struct ReplayGainData { + pub track_gain: String, + pub track_peak: String, +} -pub fn analyze_replaygain_track(path: PathBuf) -> Result> { - let output = Command::new(crate::meta::FFMPEG) +impl ReplayGainRawData { + pub fn to_normal(&self, is_ogg_opus: bool) -> ReplayGainData { + if is_ogg_opus { + ReplayGainData { + track_gain: format!("{:.6}", (self.track_gain * 256.0).ceil()), + track_peak: "".to_string(), // Not Required + } + } else { + ReplayGainData { + track_gain: format!("{:.2} dB", self.track_gain), + track_peak: format!("{:.6}", self.track_peak), + } + } + } +} + +#[derive(Debug, Clone)] +pub struct FFMpegError { + pub exit_status: process::ExitStatus, + pub stderr: String, +} + +impl fmt::Display for FFMpegError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "ffmpeg exited with error code {}, stderr: {}", + self.exit_status.code().unwrap(), + self.stderr + ) + } +} + +#[derive(Debug)] +pub enum Error { + FFMpegError(FFMpegError), + ParseError(std::num::ParseFloatError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::FFMpegError(err) => write!(f, "{}", err), + Error::ParseError(err) => write!(f, "{}", err), + } + } +} + +pub fn analyze_replaygain_track( + path: PathBuf, + ffmpeg_command: Option<&str>, +) -> Result { + let output = Command::new(ffmpeg_command.unwrap_or("ffmpeg")) .args([ "-hide_banner", "-nostats", @@ -25,13 +85,16 @@ pub fn analyze_replaygain_track(path: PathBuf) -> Result Result()?; + let gain = match gain.parse::() { + Ok(parsed) => Ok(parsed), + Err(err) => Err(Error::ParseError(err)), + }?; // https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Gain_calculation // "In order to maintain backwards compatibility with RG1, RG2 uses a -18 LUFS reference, which based on lots of music, can give similar loudness compared to RG1." @@ -87,7 +153,10 @@ pub fn analyze_replaygain_track(path: PathBuf) -> Result()?; + let peak = match peak.parse::() { + Ok(parsed) => Ok(parsed), + Err(err) => Err(Error::ParseError(err)), + }?; let peak = f64::powf(10_f64, peak / 20.0_f64); track_peak = peak; } diff --git a/src/commands/process.rs b/src/commands/process.rs index c6c8fdc..5054a79 100644 --- a/src/commands/process.rs +++ b/src/commands/process.rs @@ -6,11 +6,11 @@ use crate::args::CLIArgs; use crate::types::AudioFileInfo; use crate::types::File; use crate::utils::formats::get_format_handler; -#[cfg(feature = "replaygain")] -use crate::utils::replaygain::analyze_replaygain_track; use crate::utils::scan_for_music; use ascii_reduce::reduce; +#[cfg(feature = "replaygain")] +use replaygain::analyze_replaygain_track; #[derive(Debug, Clone, clap::Args)] pub struct ProcessCommandArgs { @@ -153,7 +153,8 @@ pub fn add_replaygain_tags(file: &File, force: bool) -> Result<(), Box ReplayGainData { - if is_ogg_opus { - ReplayGainData { - track_gain: format!("{:.6}", (self.track_gain * 256.0).ceil()), - track_peak: "".to_string(), // Not Required - } - } else { - ReplayGainData { - track_gain: format!("{:.2} dB", self.track_gain), - track_peak: format!("{:.6}", self.track_peak), - } - } - } -} - #[derive(Default, Debug, Clone)] pub struct AudioFileInfo { pub tags: Tags, + #[cfg(feature = "replaygain")] pub contains_replaygain: bool, + #[cfg(feature = "replaygain")] pub supports_replaygain: bool, pub format: Option, } diff --git a/src/utils/formats/handlers/ffprobe.rs b/src/utils/formats/handlers/ffprobe.rs index 5fc6e2e..0423698 100644 --- a/src/utils/formats/handlers/ffprobe.rs +++ b/src/utils/formats/handlers/ffprobe.rs @@ -6,11 +6,15 @@ use std::{ use string_error::into_err; use crate::{ - types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags}, + meta, + types::{AudioFileInfo, Tags}, utils::format_detection::{detect_format, FileFormat}, - utils::formats::{AudioFormatError, BoxedError, FormatHandler}, meta, + utils::formats::{AudioFormatError, BoxedError, FormatHandler}, }; +#[cfg(feature = "replaygain")] +use replaygain::{ReplayGainData, ReplayGainRawData}; + #[derive(Default)] struct Changes { title: Option, @@ -20,6 +24,7 @@ struct Changes { #[derive(Default)] struct ExtractedData { tags: Tags, + #[cfg(feature = "replaygain")] replaygain_data: Option, } @@ -47,6 +52,7 @@ impl GenericFFMpegAudioFormat { track_number: output.tags.track_number, }; + #[cfg(feature = "replaygain")] if output.tags.replaygain_track_gain.is_some() && output.tags.replaygain_track_peak.is_some() { @@ -86,10 +92,12 @@ impl FormatHandler for GenericFFMpegAudioFormat { Ok(tags) } + #[cfg(feature = "replaygain")] fn contains_replaygain_tags(&self) -> bool { false } + #[cfg(feature = "replaygain")] fn supports_replaygain(&self) -> bool { false } @@ -104,6 +112,7 @@ impl FormatHandler for GenericFFMpegAudioFormat { Ok(()) } + #[cfg(feature = "replaygain")] fn set_replaygain_data(&mut self, _data: ReplayGainRawData) -> Result<(), BoxedError> { panic!("ffprobe doesn't support setting replaygain data, check supports_replaygain()") } @@ -158,7 +167,9 @@ impl FormatHandler for GenericFFMpegAudioFormat { ) -> Result { Ok(AudioFileInfo { tags: self.get_tags(allow_missing_tags)?, + #[cfg(feature = "replaygain")] contains_replaygain: self.contains_replaygain_tags(), + #[cfg(feature = "replaygain")] supports_replaygain: self.supports_replaygain(), format: Some(self.file_format), }) diff --git a/src/utils/formats/handlers/flac.rs b/src/utils/formats/handlers/flac.rs index d0c3e8d..4580c61 100644 --- a/src/utils/formats/handlers/flac.rs +++ b/src/utils/formats/handlers/flac.rs @@ -1,11 +1,14 @@ use std::path::PathBuf; use crate::{ - types::{AudioFileInfo, ReplayGainRawData, Tags}, + types::{AudioFileInfo, Tags}, utils::format_detection::FileFormat, utils::formats::{AudioFormatError, BoxedError, FormatHandler}, }; +#[cfg(feature = "replaygain")] +use replaygain::ReplayGainRawData; + pub struct FLACAudioFormat { flac_tags: metaflac::Tag, path: Box, @@ -58,6 +61,7 @@ impl FormatHandler for FLACAudioFormat { }) } + #[cfg(feature = "replaygain")] fn contains_replaygain_tags(&self) -> bool { let track_gain = flac_get_first(&self.flac_tags, "REPLAYGAIN_TRACK_GAIN"); let track_peak = flac_get_first(&self.flac_tags, "REPLAYGAIN_TRACK_PEAK"); @@ -69,6 +73,7 @@ impl FormatHandler for FLACAudioFormat { true } + #[cfg(feature = "replaygain")] fn supports_replaygain(&self) -> bool { true } @@ -87,6 +92,7 @@ impl FormatHandler for FLACAudioFormat { Ok(()) } + #[cfg(feature = "replaygain")] fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { self.flac_tags.remove_vorbis("REPLAYGAIN_TRACK_GAIN"); self.flac_tags.remove_vorbis("REPLAYGAIN_TRACK_PEAK"); @@ -114,7 +120,9 @@ impl FormatHandler for FLACAudioFormat { ) -> Result { Ok(AudioFileInfo { tags: self.get_tags(allow_missing_tags)?, + #[cfg(feature = "replaygain")] contains_replaygain: self.contains_replaygain_tags(), + #[cfg(feature = "replaygain")] supports_replaygain: self.supports_replaygain(), format: Some(FileFormat::FLAC), }) diff --git a/src/utils/formats/handlers/id3.rs b/src/utils/formats/handlers/id3.rs index a5aebd9..fefd46d 100644 --- a/src/utils/formats/handlers/id3.rs +++ b/src/utils/formats/handlers/id3.rs @@ -3,11 +3,14 @@ use std::path::PathBuf; use id3::TagLike; use crate::{ - types::{AudioFileInfo, ReplayGainRawData, Tags}, + types::{AudioFileInfo, Tags}, utils::format_detection::FileFormat, utils::formats::{AudioFormatError, BoxedError, FormatHandler}, }; +#[cfg(feature = "replaygain")] +use replaygain::ReplayGainRawData; + pub struct ID3AudioFormat { id3_tags: id3::Tag, path: Box, @@ -37,6 +40,7 @@ impl FormatHandler for ID3AudioFormat { }) } + #[cfg(feature = "replaygain")] fn contains_replaygain_tags(&self) -> bool { let frames = self.id3_tags.frames(); @@ -61,6 +65,7 @@ impl FormatHandler for ID3AudioFormat { contains_replaygain_tags } + #[cfg(feature = "replaygain")] fn supports_replaygain(&self) -> bool { true } @@ -77,6 +82,7 @@ impl FormatHandler for ID3AudioFormat { Ok(()) } + #[cfg(feature = "replaygain")] fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { let frames = self.id3_tags.remove("TXXX"); @@ -121,7 +127,9 @@ impl FormatHandler for ID3AudioFormat { Ok(AudioFileInfo { tags: self.get_tags(allow_missing_tags)?, format: Some(FileFormat::MP3), + #[cfg(feature = "replaygain")] supports_replaygain: self.supports_replaygain(), + #[cfg(feature = "replaygain")] contains_replaygain: self.contains_replaygain_tags(), }) } diff --git a/src/utils/formats/handlers/taglib.rs b/src/utils/formats/handlers/taglib.rs index c4d725a..6912f01 100644 --- a/src/utils/formats/handlers/taglib.rs +++ b/src/utils/formats/handlers/taglib.rs @@ -7,11 +7,14 @@ use taglib::{ }; use crate::{ - types::{AudioFileInfo, ReplayGainRawData, Tags}, + types::{AudioFileInfo, Tags}, utils::format_detection::FileFormat, utils::formats::{AudioFormatError, BoxedError, FormatHandler}, }; +#[cfg(feature = "replaygain")] +use replaygain::ReplayGainRawData; + pub struct TaglibAudioFormat { file: taglib::TagLibFile, file_format: Option, @@ -46,6 +49,7 @@ impl FormatHandler for TaglibAudioFormat { }) } + #[cfg(feature = "replaygain")] fn contains_replaygain_tags(&self) -> bool { if let Some(format) = self.file_format { if format == FileFormat::OggOpus { @@ -67,6 +71,7 @@ impl FormatHandler for TaglibAudioFormat { false } + #[cfg(feature = "replaygain")] fn supports_replaygain(&self) -> bool { if let Some(format) = self.file_format { return matches!( @@ -99,6 +104,7 @@ impl FormatHandler for TaglibAudioFormat { Ok(()) } + #[cfg(feature = "replaygain")] fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> { if let Some(format) = self.file_format { match format { @@ -143,7 +149,9 @@ impl FormatHandler for TaglibAudioFormat { ) -> Result { Ok(AudioFileInfo { tags: self.get_tags(allow_missing_tags)?, + #[cfg(feature = "replaygain")] contains_replaygain: self.contains_replaygain_tags(), + #[cfg(feature = "replaygain")] supports_replaygain: self.supports_replaygain(), format: self.file_format, }) diff --git a/src/utils/formats/mod.rs b/src/utils/formats/mod.rs index 1e7114f..7ec83e9 100644 --- a/src/utils/formats/mod.rs +++ b/src/utils/formats/mod.rs @@ -5,19 +5,24 @@ use std::path::Path; use thiserror::Error; -use crate::types::{AudioFileInfo, File, ReplayGainRawData, Tags}; - use super::format_detection::detect_format; +use crate::types::{AudioFileInfo, File, Tags}; + +#[cfg(feature = "replaygain")] +use replaygain::ReplayGainRawData; type BoxedError = Box; pub trait FormatHandler { fn get_tags(&self, allow_missing: bool) -> Result; + #[cfg(feature = "replaygain")] fn contains_replaygain_tags(&self) -> bool; + #[cfg(feature = "replaygain")] fn supports_replaygain(&self) -> bool; fn set_title(&mut self, title: String) -> Result<(), BoxedError>; fn set_artist(&mut self, artist: String) -> Result<(), BoxedError>; + #[cfg(feature = "replaygain")] fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError>; fn save_changes(&mut self) -> Result<(), BoxedError>; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e715016..ad5b98e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,4 @@ pub mod format_detection; -#[cfg(feature = "replaygain")] -pub mod replaygain; pub mod transcoder; pub mod formats;