use std::path::Path;
use qubit_command::CommandRunner;
use qubit_command::{
Command,
CommandError,
};
use crate::{
FileBasedMediaStreamClassifier,
MediaStreamType,
MimeResult,
};
#[derive(Debug, Clone)]
pub struct FfprobeCommandMediaStreamClassifier {
working_directory: Option<String>,
command_runner: CommandRunner,
}
impl FfprobeCommandMediaStreamClassifier {
pub const COMMAND: &'static str = "ffprobe";
pub const VIDEO_STREAM: &'static str = "video";
pub const AUDIO_STREAM: &'static str = "audio";
pub fn new() -> Self {
Self {
working_directory: None,
command_runner: Self::default_command_runner(),
}
}
pub fn command_runner(&self) -> &CommandRunner {
&self.command_runner
}
pub fn set_command_runner(&mut self, command_runner: CommandRunner) {
self.command_runner = command_runner;
}
pub fn with_command_runner(mut self, command_runner: CommandRunner) -> Self {
self.command_runner = command_runner;
self
}
pub fn set_working_directory(&mut self, working_directory: Option<String>) {
self.working_directory = working_directory;
}
pub fn working_directory(&self) -> Option<&str> {
self.working_directory.as_deref()
}
pub fn classify_stream_listing(output: &str) -> MediaStreamType {
let has_video = output.lines().any(|line| line.trim() == Self::VIDEO_STREAM);
let has_audio = output.lines().any(|line| line.trim() == Self::AUDIO_STREAM);
match (has_video, has_audio) {
(true, true) => MediaStreamType::VideoWithAudio,
(true, false) => MediaStreamType::VideoOnly,
(false, true) => MediaStreamType::AudioOnly,
(false, false) => MediaStreamType::None,
}
}
pub fn is_available() -> bool {
Self::default_command_runner()
.run(Command::new(Self::COMMAND).arg("-version"))
.is_ok()
}
fn classify_with_ffprobe(&self, path: &Path) -> MimeResult<MediaStreamType> {
let mut command = Self::command_for_path(path);
if let Some(working_directory) = &self.working_directory {
command = command.working_directory(working_directory);
}
match self.command_runner.run(command) {
Ok(output) => {
let stdout = output.stdout_lossy_text();
Ok(Self::classify_stream_listing(&stdout))
}
Err(CommandError::UnexpectedExit { .. }) => Ok(MediaStreamType::None),
Err(error) => Err(error.into()),
}
}
fn default_command_runner() -> CommandRunner {
CommandRunner::new().disable_logging(true)
}
fn command_for_path(path: &Path) -> Command {
Command::new(Self::COMMAND)
.arg("-v")
.arg("error")
.arg("-show_entries")
.arg("stream=codec_type")
.arg("-of")
.arg("csv=p=0")
.arg_os(path)
}
}
impl Default for FfprobeCommandMediaStreamClassifier {
fn default() -> Self {
Self::new()
}
}
impl FileBasedMediaStreamClassifier for FfprobeCommandMediaStreamClassifier {
fn classify_by_local_file(&self, file: &Path) -> MimeResult<MediaStreamType> {
self.classify_with_ffprobe(file)
}
}