1
0
Fork 0
musicutil/src/utils/formats/generic_ffmpeg.rs

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)
}