Skip to content

Commit

Permalink
feat: resize inputs accordingly if resize option was not provided
Browse files Browse the repository at this point in the history
* add a silent track for inputs without audio to prevent errors during encoding
  • Loading branch information
flazepe committed Nov 22, 2024
1 parent 448b953 commit cd5e140
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 12 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ path = "src/lib.rs"

[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
47 changes: 38 additions & 9 deletions src/ffmpeg/inputs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
ffmpeg::{escape_ffmpeg_chars, Input},
ffprobe::get_input_metadata,
string_vec,
};
use anyhow::{bail, Context, Result};
Expand Down Expand Up @@ -49,11 +50,11 @@ impl Inputs {
);

if width % 2 != 0 {
width += 1;
width -= 1;
}

if height % 2 != 0 {
height += 1;
height -= 1;
}

self.resize = Some((width, height));
Expand Down Expand Up @@ -82,11 +83,32 @@ impl Inputs {
bail!("Video and audio track cannot be disabled at the same time.");
}

let mut input_metadata = vec![];
let auto_resize = self.resize.is_none();

for input in self.inputs.iter() {
let metadata = get_input_metadata(input)?;

if auto_resize {
if let Some((resize_width, _)) = self.resize {
if metadata.width > resize_width {
self.resize = Some((metadata.width, metadata.height));
}
} else {
self.resize = Some((metadata.width, metadata.height));
}
}

input_metadata.push(metadata);
}

let mut args = vec![];
let mut filters = vec![];
let mut segment_count = 0;

for (input_index, input) in self.inputs.iter().enumerate() {
let metadata = &input_metadata[input_index];

args.append(&mut string_vec!["-i", input.file]);

let video_label = format!("{input_index}:v:{}", input.video_track);
Expand Down Expand Up @@ -119,11 +141,13 @@ impl Inputs {
format!("fade=t=out:st={fade_to}:d={}", self.fade),
]);
}
if let Some((width, height)) = self.resize {
video_filters.extend_from_slice(&[
format!("scale={width}:{height}:force_original_aspect_ratio=decrease"),
format!("pad={width}:{height}:-1:-1,setsar=1"),
]);
if let Some((resize_width, resize_height)) = self.resize {
if resize_width != metadata.width || resize_height != metadata.height {
video_filters.extend_from_slice(&[
format!("scale={resize_width}:{resize_height}:force_original_aspect_ratio=decrease"),
format!("pad={resize_width}:{resize_height}:-1:-1,setsar=1"),
]);
}
}
video_filters.push(format!(
"setpts=(PTS-STARTPTS)/{}[v{segment_count}]",
Expand All @@ -133,10 +157,15 @@ impl Inputs {
}

if !self.no_audio {
let mut audio_filters = vec![format!(
let mut audio_filters = vec![];
if metadata.no_audio {
audio_filters
.push(format!("anullsrc[{input_index}:a:{}]", input.audio_track));
}
audio_filters.push(format!(
"[{input_index}:a:{}]atrim={from}:{to}",
input.audio_track,
)];
));
if self.fade > 0. {
audio_filters.extend_from_slice(&[
format!("afade=t=in:st={from}:d={}", self.fade),
Expand Down
79 changes: 79 additions & 0 deletions src/ffprobe/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::ffmpeg::Input;
use anyhow::{Context, Result};
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::{from_str, Value};
use std::process::{Command, Stdio};

#[derive(Debug)]
pub struct InputMetadata {
pub width: u64,
pub height: u64,
pub no_audio: bool,
}

#[derive(Deserialize, Debug)]
struct InputStreams<T> {
streams: Vec<T>,
}

#[derive(Deserialize, Debug)]
struct InputVideoStream {
width: u64,
height: u64,
}

pub fn get_input_metadata(input: &Input) -> Result<InputMetadata> {
let video_metadata = run_ffprobe::<InputStreams<InputVideoStream>>(
&input.file,
&format!("v:{}", input.video_track),
"stream=width,height",
)?;

let (mut width, mut height) = video_metadata
.streams
.first()
.map(|stream: &InputVideoStream| (stream.width, stream.height))
.context(format!(
r#"Input "{}" has no video track {}."#,
input.file, input.video_track,
))?;

if width % 2 != 0 {
width -= 1;
}

if height % 2 != 0 {
height -= 1;
}

let audio_metadata = run_ffprobe::<InputStreams<Value>>(
&input.file,
&format!("a:{}", input.audio_track),
"stream=index",
)?;

Ok(InputMetadata {
width,
height,
no_audio: audio_metadata.streams.is_empty(),
})
}

pub fn run_ffprobe<T: DeserializeOwned>(file: &str, stream: &str, entries: &str) -> Result<T> {
let output = Command::new("ffprobe")
.args([
"-i",
file,
"-select_streams",
stream,
"-show_entries",
entries,
"-of",
"json",
])
.stdout(Stdio::piped())
.spawn()?
.wait_with_output()?;

Ok(from_str::<T>(&String::from_utf8(output.stdout)?)?)
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod clipper;
mod ffmpeg;
mod ffprobe;

pub use clipper::Clipper;
3 changes: 1 addition & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::process::exit;

use clipper::Clipper;
use std::process::exit;

fn main() {
if let Err(error) = Clipper::from_env_args().and_then(|clipper| clipper.run()) {
Expand Down

0 comments on commit cd5e140

Please sign in to comment.