180 lines
6.9 KiB
Rust
180 lines
6.9 KiB
Rust
// Copyright (C) 2026 Elias Wendland <eliaswendland@pm.me>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 3 exclusively.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use crate::plugin::Plugin;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
|
|
pub struct PluginImpl;
|
|
|
|
impl Plugin for PluginImpl {
|
|
fn name(&self) -> &'static str {
|
|
"ffmpeg_audio"
|
|
}
|
|
|
|
fn from_formats(&self) -> Vec<&'static str> {
|
|
vec![
|
|
"mp3", "ogg", "aac", "m4a", "opus", "wma", "mka", "mp2", "ra", "wav", "flac", "alac",
|
|
"aiff", "au", "caf", "w64", "ac3", "eac3", "dts", "truehd", "thd", "ape", "wv", "tta",
|
|
"amr", "spx", "gsm", "voc",
|
|
]
|
|
}
|
|
|
|
fn to_formats(&self) -> Vec<&'static str> {
|
|
vec![
|
|
"mp3", "ogg", "aac", "m4a", "opus", "wma", "mka", "mp2", "ra", "wav", "flac", "alac",
|
|
"aiff", "au", "caf", "w64", "ac3", "eac3", "dts", "truehd", "thd", "ape", "wv", "tta",
|
|
"amr", "spx", "gsm", "voc",
|
|
]
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
fn is_available(&self) -> bool {
|
|
tracing::trace!("Checking availability of ffmpeg for ffmpeg_audio plugin");
|
|
let available = std::process::Command::new("ffmpeg")
|
|
.arg("-version")
|
|
.output()
|
|
.is_ok();
|
|
tracing::debug!("ffmpeg_audio plugin available: {}", available);
|
|
available
|
|
}
|
|
|
|
fn familiarity(&self, _from: &str, to: &str) -> u8 {
|
|
match to {
|
|
"mp3" | "wav" => 255,
|
|
"m4a" | "aac" | "flac" => 240,
|
|
"ogg" | "opus" | "alac" => 220,
|
|
"ac3" | "eac3" | "dts" => 180,
|
|
"wma" | "amr" | "mka" | "mp2" => 150,
|
|
"aiff" | "au" | "caf" | "w64" => 120,
|
|
"ape" | "wv" | "tta" | "truehd" | "thd" => 100,
|
|
"spx" | "gsm" | "voc" | "ra" => 80,
|
|
_ => 128,
|
|
}
|
|
}
|
|
|
|
fn quality(&self, _from: &str, to: &str) -> u8 {
|
|
match to {
|
|
"flac" | "wav" | "aiff" | "au" | "alac" | "ape" | "wv" | "tta" | "caf" | "w64"
|
|
| "truehd" | "thd" => 255,
|
|
"opus" | "ogg" | "aac" | "m4a" | "eac3" | "dts" => 220,
|
|
"mp3" | "ac3" | "mka" | "mp2" | "ra" => 200,
|
|
"wma" | "amr" | "spx" | "gsm" | "voc" => 150,
|
|
_ => 128,
|
|
}
|
|
}
|
|
|
|
fn speed(&self, _from: &str, to: &str) -> u8 {
|
|
match to {
|
|
"wav" | "aiff" | "au" | "caf" | "w64" | "voc" => 255,
|
|
"flac" | "alac" | "wv" | "mp2" | "ac3" | "dts" => 200,
|
|
"mp3" | "aac" | "m4a" | "ogg" | "mka" | "eac3" => 180,
|
|
"opus" | "wma" | "amr" | "ape" | "tta" | "truehd" | "thd" | "spx" | "gsm" | "ra" => 150,
|
|
_ => 128,
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self, input, temp_dir))]
|
|
fn convert(
|
|
&self,
|
|
input: &[u8],
|
|
_from: &str,
|
|
to: &str,
|
|
temp_dir: &Path,
|
|
) -> Result<Vec<u8>, String> {
|
|
tracing::debug!("ffmpeg_audio starting conversion: {} -> {}", _from, to);
|
|
let mut temp_in = tempfile::Builder::new()
|
|
.suffix(&format!(".{}", _from))
|
|
.tempfile_in(temp_dir)
|
|
.map_err(|e| {
|
|
tracing::error!("Failed to create temp input file: {}", e);
|
|
e.to_string()
|
|
})?;
|
|
temp_in.write_all(input).map_err(|e| {
|
|
tracing::error!("Failed to write to temp input file: {}", e);
|
|
e.to_string()
|
|
})?;
|
|
|
|
let temp_out = tempfile::Builder::new()
|
|
.suffix(&format!(".{}", to))
|
|
.tempfile_in(temp_dir)
|
|
.map_err(|e| {
|
|
tracing::error!("Failed to create temp output file: {}", e);
|
|
e.to_string()
|
|
})?;
|
|
let temp_out_path = temp_out.into_temp_path();
|
|
|
|
let in_path = temp_in.path().to_path_buf();
|
|
tracing::trace!("Temp files created. In: {:?}, Out: {:?}", in_path, temp_out_path);
|
|
|
|
let mut raw_args = vec![];
|
|
match to {
|
|
"mp3" => raw_args.extend(vec!["-c:a", "libmp3lame", "-q:a", "2"]),
|
|
"ogg" => raw_args.extend(vec!["-c:a", "libvorbis", "-q:a", "4"]),
|
|
"aac" | "m4a" | "mka" => raw_args.extend(vec!["-c:a", "aac", "-b:a", "192k"]),
|
|
"opus" => raw_args.extend(vec!["-c:a", "libopus", "-b:a", "128k"]),
|
|
"mp2" => raw_args.extend(vec!["-c:a", "mp2", "-b:a", "192k"]),
|
|
"wma" => raw_args.extend(vec!["-c:a", "wmav2", "-b:a", "192k"]),
|
|
"flac" => raw_args.extend(vec!["-c:a", "flac"]),
|
|
"alac" => raw_args.extend(vec!["-c:a", "alac"]),
|
|
"ape" => raw_args.extend(vec!["-c:a", "ape"]),
|
|
"wv" => raw_args.extend(vec!["-c:a", "wavpack"]),
|
|
"tta" => raw_args.extend(vec!["-c:a", "tta"]),
|
|
"ac3" => raw_args.extend(vec!["-c:a", "ac3", "-b:a", "384k"]),
|
|
"eac3" => raw_args.extend(vec!["-c:a", "eac3", "-b:a", "640k"]),
|
|
"dts" => raw_args.extend(vec!["-c:a", "dca", "-strict", "-2", "-b:a", "1536k"]),
|
|
"truehd" | "thd" => raw_args.extend(vec!["-c:a", "truehd", "-strict", "-2"]),
|
|
"wav" | "aiff" | "au" | "caf" | "w64" => raw_args.extend(vec!["-c:a", "pcm_s16le"]),
|
|
"amr" => raw_args.extend(vec![
|
|
"-ar",
|
|
"8000",
|
|
"-c:a",
|
|
"libopencore_amrnb",
|
|
"-b:a",
|
|
"12.2k",
|
|
]),
|
|
"spx" => raw_args.extend(vec!["-c:a", "libspeex"]),
|
|
"gsm" => raw_args.extend(vec!["-ar", "8000", "-c:a", "libgsm"]),
|
|
_ => raw_args.extend(vec!["-c:a", "copy"]),
|
|
}
|
|
|
|
tracing::debug!("Built ffmpeg arguments: {:?}", raw_args);
|
|
|
|
tracing::trace!("Executing ffmpeg command...");
|
|
let output = std::process::Command::new("ffmpeg")
|
|
.arg("-y") // Overwrite output files without asking
|
|
.arg("-i")
|
|
.arg(&in_path)
|
|
.args(&raw_args)
|
|
.arg(&temp_out_path)
|
|
.output()
|
|
.map_err(|e| {
|
|
tracing::error!("Failed to execute ffmpeg: {}", e);
|
|
e.to_string()
|
|
})?;
|
|
|
|
if !output.status.success() {
|
|
let err = String::from_utf8_lossy(&output.stderr);
|
|
tracing::error!("ffmpeg execution failed: {}", err);
|
|
return Err(format!("ffmpeg failed: {}", err));
|
|
}
|
|
|
|
tracing::debug!("FFmpeg execution succeeded. Reading output file...");
|
|
std::fs::read(&temp_out_path).map_err(|e| {
|
|
tracing::error!("Failed to read output file {:?}: {}", temp_out_path, e);
|
|
e.to_string()
|
|
})
|
|
}
|
|
}
|