use super::spawning::map_exe_error;
use super::*;
use failure::*;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::io::BufReader;
use std::process::*;
static EXTENSIONS: &[&str] = &["mkv", "mp4", "avi"];
lazy_static! {
static ref METADATA: AdapterMeta = AdapterMeta {
name: "ffmpeg".to_owned(),
version: 1,
description: "Uses ffmpeg to extract video metadata/chapters and subtitles".to_owned(),
recurses: false,
fast_matchers: EXTENSIONS
.iter()
.map(|s| FastMatcher::FileExtension(s.to_string()))
.collect(),
slow_matchers: None
};
}
#[derive(Default)]
pub struct FFmpegAdapter;
impl FFmpegAdapter {
pub fn new() -> FFmpegAdapter {
FFmpegAdapter
}
}
impl GetMetadata for FFmpegAdapter {
fn metadata(&self) -> &AdapterMeta {
&METADATA
}
}
#[derive(Serialize, Deserialize)]
struct FFprobeOutput {
streams: Vec<FFprobeStream>,
}
#[derive(Serialize, Deserialize)]
struct FFprobeStream {
codec_type: String, }
impl FileAdapter for FFmpegAdapter {
fn adapt(&self, ai: AdaptInfo, _detection_reason: &SlowMatcher) -> Fallible<()> {
let AdaptInfo {
is_real_file,
filepath_hint,
oup,
line_prefix,
..
} = ai;
if !is_real_file {
writeln!(oup, "{}[rga: skipping video in archive]", line_prefix,)?;
return Ok(());
}
let inp_fname = filepath_hint;
let spawn_fail = |e| map_exe_error(e, "ffprobe", "Make sure you have ffmpeg installed.");
let has_subtitles = {
let probe = Command::new("ffprobe")
.args(vec![
"-v",
"error",
"-select_streams",
"s",
"-of",
"json",
"-show_entries",
"stream=codec_type",
])
.arg("-i")
.arg(inp_fname)
.output()
.map_err(spawn_fail)?;
if !probe.status.success() {
return Err(format_err!("ffprobe failed: {:?}", probe.status));
}
let p: FFprobeOutput = serde_json::from_slice(&probe.stdout)?;
(p.streams.iter().count() > 0)
};
{
let mut probe = Command::new("ffprobe")
.args(vec![
"-v",
"error",
"-show_format",
"-show_streams",
"-of",
"flat",
"-show_error",
"-show_programs",
"-show_chapters",
])
.arg("-i")
.arg(inp_fname)
.stdout(Stdio::piped())
.spawn()?;
for line in BufReader::new(probe.stdout.as_mut().unwrap()).lines() {
writeln!(oup, "metadata: {}", line?)?;
}
let exit = probe.wait()?;
if !exit.success() {
return Err(format_err!("ffprobe failed: {:?}", exit));
}
}
if has_subtitles {
let mut cmd = Command::new("ffmpeg");
cmd.arg("-hide_banner")
.arg("-loglevel")
.arg("panic")
.arg("-i")
.arg(inp_fname)
.arg("-f")
.arg("webvtt")
.arg("-");
let mut cmd = cmd.stdout(Stdio::piped()).spawn().map_err(spawn_fail)?;
let stdo = cmd.stdout.as_mut().expect("is piped");
let time_re = Regex::new(r".*\d.*-->.*\d.*").unwrap();
let mut time: String = "".to_owned();
for line in BufReader::new(stdo).lines() {
let line = line?;
if time_re.is_match(&line) {
time = line.to_owned();
} else if line.is_empty() {
oup.write_all(b"\n")?;
} else {
writeln!(oup, "{}: {}", time, line)?;
}
}
}
Ok(())
}
}