move replaygain stuff into its own crate, run clippy

This commit is contained in:
chaos 2023-10-30 23:17:52 +00:00
parent feee847463
commit d5ab74d17a
No known key found for this signature in database
15 changed files with 169 additions and 62 deletions

9
Cargo.lock generated
View file

@ -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"

View file

@ -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 = []
replaygain = ["dep:replaygain"]

View file

@ -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<char, &'static str> = {}",
map.build()
)
.unwrap();
write!(&mut file, ";\n").unwrap();
writeln!(&mut file, ";").unwrap();
}

View file

@ -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 {

View file

@ -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<ffprobe_output::FFProbeOutput, AnalyzeError> {
fn extract(
path: &Path,
ffprobe_command: Option<&str>,
) -> Result<ffprobe_output::FFProbeOutput, AnalyzeError> {
let output = Command::new(ffprobe_command.unwrap_or("ffprobe"))
.args([
"-v",
@ -39,7 +42,10 @@ fn extract(path: &Path, ffprobe_command: Option<&str>) -> Result<ffprobe_output:
Ok(ffprobe_out.unwrap())
}
pub fn analyze(path: &Path, ffprobe_command: Option<&str>) -> Result<types::FFProbeData, AnalyzeError> {
pub fn analyze(
path: &Path,
ffprobe_command: Option<&str>,
) -> Result<types::FFProbeData, AnalyzeError> {
let raw_data = extract(path, ffprobe_command)?;
let mut data = types::FFProbeData {

View file

@ -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"

View file

@ -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<ReplayGainRawData, Box<dyn std::error::Error>> {
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<ReplayGainRawData, Error> {
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<ReplayGainRawData, Box<
"null",
"/dev/null",
])
.output()?;
.output();
let output = output.unwrap();
if !output.status.success() {
print!("{:?}", String::from_utf8(output.stderr).unwrap());
return Err(static_err("FFmpeg Crashed"));
return Err(Error::FFMpegError(FFMpegError {
exit_status: output.status,
stderr: String::from_utf8(output.stderr).unwrap(),
}));
}
// info we need is in stdout
let output_str = String::from_utf8(output.stderr).unwrap();
@ -72,7 +135,10 @@ pub fn analyze_replaygain_track(path: PathBuf) -> Result<ReplayGainRawData, Box<
l.next();
let gain = l.next().unwrap().trim().trim_end_matches(" LUFS");
let gain = gain.parse::<f64>()?;
let gain = match gain.parse::<f64>() {
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<ReplayGainRawData, Box<
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Loudness_normalization
let peak = l.next().unwrap().trim().trim_end_matches(" dBFS");
let peak = peak.parse::<f64>()?;
let peak = match peak.parse::<f64>() {
Ok(parsed) => Ok(parsed),
Err(err) => Err(Error::ParseError(err)),
}?;
let peak = f64::powf(10_f64, peak / 20.0_f64);
track_peak = peak;
}

View file

@ -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<dyn std::
file.join_path_from_source()
);
let replaygain_data = analyze_replaygain_track(file.join_path_to())?;
let replaygain_data = analyze_replaygain_track(file.join_path_to(), Some(crate::meta::FFMPEG))
.expect("could not analyze replaygain");
let mut handler = get_format_handler(file)?;

View file

@ -21,39 +21,12 @@ impl Default for Tags {
}
}
#[derive(Debug, Clone)]
pub struct ReplayGainData {
pub track_gain: String,
pub track_peak: String,
}
#[derive(Debug, Clone)]
pub struct ReplayGainRawData {
pub track_gain: f64,
pub track_peak: f64,
}
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(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<FileFormat>,
}

View file

@ -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<String>,
@ -20,6 +24,7 @@ struct Changes {
#[derive(Default)]
struct ExtractedData {
tags: Tags,
#[cfg(feature = "replaygain")]
replaygain_data: Option<ReplayGainData>,
}
@ -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<AudioFileInfo, BoxedError> {
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),
})

View file

@ -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<PathBuf>,
@ -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<AudioFileInfo, BoxedError> {
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),
})

View file

@ -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<PathBuf>,
@ -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(),
})
}

View file

@ -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<FileFormat>,
@ -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<AudioFileInfo, BoxedError> {
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,
})

View file

@ -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<dyn Error>;
pub trait FormatHandler {
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError>;
#[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>;

View file

@ -1,6 +1,4 @@
pub mod format_detection;
#[cfg(feature = "replaygain")]
pub mod replaygain;
pub mod transcoder;
pub mod formats;