194 lines
5 KiB
Rust
194 lines
5 KiB
Rust
use std::{
|
|
path::{Path, PathBuf},
|
|
process::Command,
|
|
};
|
|
|
|
use string_error::into_err;
|
|
|
|
use crate::{
|
|
types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags},
|
|
utils::{format_detection::FileFormat, ffprobe},
|
|
};
|
|
|
|
use super::{AudioContainerFormat, AudioFormatError, BoxedError};
|
|
|
|
#[derive(Default)]
|
|
struct Changes {
|
|
title: Option<String>,
|
|
artist: Option<String>,
|
|
replaygain_data: Option<ReplayGainRawData>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ExtractedData {
|
|
tags: Tags,
|
|
replaygain_data: Option<ReplayGainData>,
|
|
}
|
|
|
|
pub struct GenericFFMpegAudioFormat {
|
|
format_type: FileFormat,
|
|
path: Box<PathBuf>,
|
|
|
|
extracted_data: ExtractedData,
|
|
changes: Changes,
|
|
}
|
|
|
|
impl GenericFFMpegAudioFormat {
|
|
fn analyze(&mut self) -> Result<(), BoxedError> {
|
|
let output = ffprobe::analyze(&self.path);
|
|
|
|
if let Err(err) = output {
|
|
return Err(into_err(format!("{}", err)));
|
|
}
|
|
|
|
let output = output.unwrap();
|
|
|
|
self.extracted_data.tags = Tags {
|
|
title: output.tags.title,
|
|
artist: output.tags.artist,
|
|
};
|
|
|
|
if output.tags.replaygain_track_gain.is_some() && output.tags.replaygain_track_peak.is_some() {
|
|
self.extracted_data.replaygain_data = Some(ReplayGainData {
|
|
track_gain: output.tags.replaygain_track_gain.unwrap(),
|
|
track_peak: output.tags.replaygain_track_peak.unwrap(),
|
|
});
|
|
} else {
|
|
self.extracted_data.replaygain_data = None;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AudioContainerFormat for GenericFFMpegAudioFormat {
|
|
fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
|
|
let mut tags = self.extracted_data.tags.clone();
|
|
|
|
if let Some(title) = &self.changes.title {
|
|
tags.title = title.clone();
|
|
}
|
|
|
|
if let Some(artist) = &self.changes.title {
|
|
tags.artist = artist.clone();
|
|
}
|
|
|
|
if !allow_missing {
|
|
if tags.title.is_empty() {
|
|
return Err(Box::new(AudioFormatError::MissingTitle));
|
|
}
|
|
if tags.artist.is_empty() {
|
|
return Err(Box::new(AudioFormatError::MissingArtist));
|
|
}
|
|
}
|
|
|
|
Ok(tags)
|
|
}
|
|
|
|
fn contains_replaygain_tags(&self) -> bool {
|
|
if self.changes.replaygain_data.is_some() {
|
|
return true;
|
|
}
|
|
|
|
if self.extracted_data.replaygain_data.is_some() {
|
|
return true;
|
|
};
|
|
|
|
false
|
|
}
|
|
|
|
fn supports_replaygain(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn set_title(&mut self, title: String) -> Result<(), BoxedError> {
|
|
self.changes.title = Some(title);
|
|
Ok(())
|
|
}
|
|
|
|
fn set_artist(&mut self, artist: String) -> Result<(), BoxedError> {
|
|
self.changes.artist = Some(artist);
|
|
Ok(())
|
|
}
|
|
|
|
fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> {
|
|
self.changes.replaygain_data = Some(data);
|
|
Ok(())
|
|
}
|
|
|
|
fn save_changes(&mut self) -> Result<(), BoxedError> {
|
|
if self.changes.title.is_none()
|
|
&& self.changes.artist.is_none()
|
|
&& self.changes.replaygain_data.is_none()
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
let mut args: Vec<String> = Vec::new();
|
|
|
|
let tempdir = tempfile::tempdir()?;
|
|
let temp_file = tempdir
|
|
.path()
|
|
.join(PathBuf::from(self.path.file_name().unwrap()));
|
|
|
|
args.extend(vec![
|
|
"-i".to_string(),
|
|
self.path.to_string_lossy().to_string(),
|
|
]);
|
|
|
|
args.extend(vec!["-c".to_string(), "copy".to_string()]);
|
|
|
|
if let Some(title) = &self.changes.title {
|
|
args.extend(vec![
|
|
"-metadata".to_string(),
|
|
format!("title=\"{}\"", title.as_str()),
|
|
])
|
|
}
|
|
|
|
if let Some(artist) = &self.changes.artist {
|
|
args.extend(vec![
|
|
"-metadata".to_string(),
|
|
format!("artist={}", artist.as_str()),
|
|
])
|
|
}
|
|
|
|
args.push(temp_file.to_string_lossy().to_string());
|
|
|
|
let output = Command::new(crate::meta::FFMPEG).args(args).output()?;
|
|
|
|
println!("{:?}", String::from_utf8(output.stderr));
|
|
|
|
std::fs::copy(temp_file, self.path.to_path_buf())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_audio_file_info(
|
|
&mut self,
|
|
allow_missing_tags: bool,
|
|
) -> Result<AudioFileInfo, BoxedError> {
|
|
Ok(AudioFileInfo {
|
|
tags: self.get_tags(allow_missing_tags)?,
|
|
contains_replaygain: self.contains_replaygain_tags(),
|
|
supports_replaygain: self.supports_replaygain(),
|
|
format: Some(self.format_type),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn new_generic_ffmpeg_format_handler(
|
|
path: &Path,
|
|
format_type: FileFormat,
|
|
) -> Result<GenericFFMpegAudioFormat, BoxedError> {
|
|
let mut handler = GenericFFMpegAudioFormat {
|
|
format_type,
|
|
path: Box::new(path.to_path_buf()),
|
|
extracted_data: ExtractedData::default(),
|
|
changes: Changes::default(),
|
|
};
|
|
|
|
handler.analyze()?;
|
|
|
|
Ok(handler)
|
|
}
|