diff --git a/Cargo.toml b/Cargo.toml index b32873c..2b0ed87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ taglib = { path = "./modules/taglib", optional = true } html-escape = "0.2" urlencoding = "2" - # error handling thiserror = "1" string-error = "0.1" diff --git a/flake.nix b/flake.nix index 4de8e64..3e24cc4 100644 --- a/flake.nix +++ b/flake.nix @@ -28,7 +28,7 @@ version = "latest"; src = ./.; - cargoLock = {lockFile = ./Cargo.lock;}; + cargoLock.lockFile = ./Cargo.lock; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; diff --git a/src/utils/ffprobe/errors.rs b/src/utils/ffprobe/errors.rs new file mode 100644 index 0000000..372a079 --- /dev/null +++ b/src/utils/ffprobe/errors.rs @@ -0,0 +1,35 @@ +use std::{fmt, io, process}; + +#[derive(Debug)] +pub enum AnalyzeError { + FFProbeError(FFProbeError), + IOError(io::Error), + ParseError(serde_json::Error), +} + +impl fmt::Display for AnalyzeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AnalyzeError::FFProbeError(err) => write!(f, "{}", err), + AnalyzeError::IOError(err) => write!(f, "{}", err), + AnalyzeError::ParseError(err) => write!(f, "{}", err), + } + } +} + +#[derive(Debug, Clone)] +pub struct FFProbeError { + pub exit_status: process::ExitStatus, + pub stderr: String, +} + +impl fmt::Display for FFProbeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "ffprobe exited with error code {}, stderr: {}", + self.exit_status.code().unwrap(), + self.stderr + ) + } +} \ No newline at end of file diff --git a/src/utils/ffprobe/ffprobe_output.rs b/src/utils/ffprobe/ffprobe_output.rs new file mode 100644 index 0000000..19541c7 --- /dev/null +++ b/src/utils/ffprobe/ffprobe_output.rs @@ -0,0 +1,35 @@ +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct FFProbeOutput { + pub format: FFProbeOutputFormat, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct FFProbeOutputFormat { + pub tags: FFProbeOutputTags, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct FFProbeOutputTags { + #[serde(alias = "TITLE")] + pub title: String, + #[serde(default, alias = "ARTIST")] + pub artist: String, + + #[serde(default, alias = "REPLAYGAIN_TRACK_PEAK")] + pub replaygain_track_peak: Option, + #[serde(default, alias = "REPLAYGAIN_TRACK_GAIN")] + pub replaygain_track_gain: Option, +} + +impl Default for FFProbeOutputTags { + fn default() -> Self { + FFProbeOutputTags { + title: "".to_string(), + artist: "".to_string(), + replaygain_track_peak: None, + replaygain_track_gain: None, + } + } +} \ No newline at end of file diff --git a/src/utils/ffprobe/mod.rs b/src/utils/ffprobe/mod.rs new file mode 100644 index 0000000..8b2a5ea --- /dev/null +++ b/src/utils/ffprobe/mod.rs @@ -0,0 +1,47 @@ +mod ffprobe_output; +pub mod errors; +pub mod types; + +use std::{ + convert::Into, + path::PathBuf, + process::Command, +}; + +use self::errors::{AnalyzeError, FFProbeError}; + +pub fn analyze(path: &PathBuf) -> Result { + let output = Command::new(crate::meta::FFPROBE) + .args([ + "-v", + "quiet", + "-print_format", + "json", + "-show_format", + path.to_str().unwrap(), + ]) + .output(); + + if let Err(err) = output { + return Err(AnalyzeError::IOError(err)); + } + + let output = output.unwrap(); + + if !output.status.success() { + return Err(AnalyzeError::FFProbeError(FFProbeError { + exit_status: output.status, + stderr: String::from_utf8(output.stderr).unwrap(), + })); + } + + let output = String::from_utf8(output.stdout).unwrap(); + let ffprobe_out: serde_json::Result = + serde_json::from_str(output.as_str()); + + let ffprobe_out = ffprobe_out.unwrap(); + + return Ok(types::FFProbeData { + tags: ffprobe_out.format.tags.into(), + }); +} diff --git a/src/utils/ffprobe/types.rs b/src/utils/ffprobe/types.rs new file mode 100644 index 0000000..5274d35 --- /dev/null +++ b/src/utils/ffprobe/types.rs @@ -0,0 +1,27 @@ +use serde::Serialize; + +use super::ffprobe_output; + +#[derive(Debug, Clone, Serialize)] +pub struct FFProbeTags { + pub title: String, + pub artist: String, + pub replaygain_track_peak: Option, + pub replaygain_track_gain: Option, +} + +impl Into for ffprobe_output::FFProbeOutputTags { + fn into(self) -> FFProbeTags { + FFProbeTags { + title: self.title, + artist: self.artist, + replaygain_track_peak: self.replaygain_track_peak, + replaygain_track_gain: self.replaygain_track_gain, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct FFProbeData { + pub tags: FFProbeTags, +} \ No newline at end of file diff --git a/src/utils/formats/generic_ffmpeg.rs b/src/utils/formats/generic_ffmpeg.rs index efd92a8..93b82df 100644 --- a/src/utils/formats/generic_ffmpeg.rs +++ b/src/utils/formats/generic_ffmpeg.rs @@ -3,12 +3,11 @@ use std::{ process::Command, }; -use serde::Deserialize; -use string_error::static_err; +use string_error::into_err; use crate::{ types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags}, - utils::format_detection::FileFormat, + utils::{format_detection::FileFormat, ffprobe}, }; use super::{AudioContainerFormat, AudioFormatError, BoxedError}; @@ -26,40 +25,6 @@ struct ExtractedData { replaygain_data: Option, } -#[derive(Debug, Clone, Deserialize)] -struct FFProbeOutput { - pub format: FFProbeFormat, -} - -#[derive(Debug, Clone, Deserialize)] -struct FFProbeFormat { - pub tags: FFProbeTags, -} - -#[derive(Debug, Clone, Deserialize)] -struct FFProbeTags { - #[serde(alias = "TITLE")] - pub title: String, - #[serde(default, alias = "ARTIST")] - pub artist: String, - - #[serde(default, alias = "REPLAYGAIN_TRACK_PEAK")] - pub replaygain_track_peak: Option, - #[serde(default, alias = "REPLAYGAIN_TRACK_GAIN")] - pub replaygain_track_gain: Option, -} - -impl Default for FFProbeTags { - fn default() -> Self { - FFProbeTags { - title: "".to_string(), - artist: "".to_string(), - replaygain_track_peak: None, - replaygain_track_gain: None, - } - } -} - pub struct GenericFFMpegAudioFormat { format_type: FileFormat, path: Box, @@ -70,36 +35,23 @@ pub struct GenericFFMpegAudioFormat { impl GenericFFMpegAudioFormat { fn analyze(&mut self) -> Result<(), BoxedError> { - let output = Command::new(crate::meta::FFPROBE) - .args([ - "-v", - "quiet", - "-print_format", - "json", - "-show_format", - &self.path.to_string_lossy(), - ]) - .output()?; + let output = ffprobe::analyze(&self.path); - if !output.status.success() { - print!("{:?}", String::from_utf8(output.stderr).unwrap()); - return Err(static_err("FFprobe Crashed")); + if let Err(err) = output { + return Err(into_err(format!("{}", err))); } - - let output_str = String::from_utf8(output.stdout).unwrap(); - let ffprobe_out: FFProbeOutput = serde_json::from_str(output_str.as_str())?; - - let tags = ffprobe_out.format.tags; + + let output = output.unwrap(); self.extracted_data.tags = Tags { - title: tags.title, - artist: tags.artist, + title: output.tags.title, + artist: output.tags.artist, }; - if tags.replaygain_track_gain.is_some() && tags.replaygain_track_peak.is_some() { + if output.tags.replaygain_track_gain.is_some() && output.tags.replaygain_track_peak.is_some() { self.extracted_data.replaygain_data = Some(ReplayGainData { - track_gain: tags.replaygain_track_gain.unwrap(), - track_peak: tags.replaygain_track_peak.unwrap(), + track_gain: output.tags.replaygain_track_gain.unwrap(), + track_peak: output.tags.replaygain_track_peak.unwrap(), }); } else { self.extracted_data.replaygain_data = None; @@ -234,6 +186,7 @@ pub fn new_generic_ffmpeg_format_handler( extracted_data: ExtractedData::default(), changes: Changes::default(), }; + handler.analyze()?; Ok(handler) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b5ded79..ea2eb2b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,6 +2,7 @@ pub mod ascii_reduce; pub mod format_detection; pub mod replaygain; pub mod transcoder; +pub mod ffprobe; pub mod formats; pub(self) mod music_scanner;