Skip to content

Commit

Permalink
feat: -speed/-spd option
Browse files Browse the repository at this point in the history
  • Loading branch information
flazepe committed Oct 26, 2024
1 parent 1dc6d34 commit df352f9
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 10 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Combining multiple clips from multiple inputs
clipper -i input1.mp4 -s 2:00-2:30 -s 5:12-5:20 -i input2.mp4 -s 1:15-1:25 -s 7:20-7:27 output.mp4
```

Selecting a video/audio track and burning subtitles from input (this option works per input, like segments)
Selecting a video/audio track and burning subtitles from input (these options work per input)

```
clipper -input input.mp4 -video-track 1 -audio-track 1 -subtitle-track 1 -segment 2:00-2:30 -segment 5:12-5:20 output.mp4
Expand All @@ -26,6 +26,13 @@ clipper -input input.mp4 -vt 1 -at 1 -st 1 -s 2:00-2:30 -s 5:12-5:20 output.mp4
clipper -input input1.mp4 -vt 1 -at 1 -st 1 -s 2:00-2:30 -s 5:12-5:20 -i input2.mp4 -vt 2 -at 2 -st 2 -s 1:15-1:25 -s 7:20-7:27 output.mp4
```

Setting the input's segment speed. This speeds up all segments of the input by the specified multiplier (this option works per input)

```
clipper -i input.mp4 -speed 2 -s 2:00-2:30 -s 5:12-5:20 output.mp4
clipper -i input.mp4 -spd 2 -s 2:00-2:30 -s 5:12-5:20 output.mp4
```

Adding a fade transition between segments with optional duration in seconds (this option applies to all segments, regardless of their inputs)

```
Expand Down
6 changes: 6 additions & 0 deletions src/clipper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl Clipper {
"video-track" | "vt" => current_option = Some(option.into()),
"audio-track" | "at" => current_option = Some(option.into()),
"subtitle-track" | "st" => current_option = Some(option.into()),
"speed" | "spd" => current_option = Some(option.into()),
"segment" | "s" => current_option = Some(option.into()),
"fade" | "f" => inputs.set_fade(arg),
"nvenc" => encoder.set_nvenc(true),
Expand Down Expand Up @@ -66,6 +67,11 @@ impl Clipper {
last_input.set_subtitle_track(arg);
}
}
"speed" | "spd" => {
if let Some(last_input) = inputs.get_last_input_mut() {
last_input.set_speed(arg);
}
}
"segment" | "s" => {
if let Some(last_input) = inputs.get_last_input_mut() {
last_input.add_segment(arg);
Expand Down
14 changes: 14 additions & 0 deletions src/ffmpeg/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Input {
pub video_track: u8,
pub audio_track: u8,
pub subtitle_track: Option<u8>,
pub speed: f64,
}

impl Input {
Expand All @@ -16,6 +17,7 @@ impl Input {
video_track: 0,
audio_track: 0,
subtitle_track: None,
speed: 1.,
}
}

Expand All @@ -40,4 +42,16 @@ impl Input {
error!("Invalid subtitle track: {subtitle_track}");
}));
}

pub fn set_speed(&mut self, speed: String) {
let speed = speed.parse::<f64>().unwrap_or_else(|_| {
error!("Invalid speed multiplier: {speed}");
});

if !(0.5..100.).contains(&speed) {
error!("Speed multiplier must be between 0.5 and 100. Received: {speed}");
}

self.speed = speed;
}
}
24 changes: 15 additions & 9 deletions src/ffmpeg/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl IntoIterator for Inputs {
args.append(&mut string_vec!["-i", input.file]);

let video_label = format!("{input_index}:v:{}", input.video_track);
let subtitled_video_label = input.subtitle_track.as_ref().map(|subtitle_track| {
let label_subtitled_video = input.subtitle_track.as_ref().map(|subtitle_track| {
let label = format!("{video_label}:si={subtitle_track}");
filters.push(format!(
"[{video_label}]subtitles={}:si={subtitle_track}[{label}];[{label}]split={}{}",
Expand All @@ -73,7 +73,7 @@ impl IntoIterator for Inputs {
(0..input.segments.len())
.fold("".into(), |acc, cur| format!("{acc}[{label}:{cur}]")),
));
label
move |segment_index| format!("{label}:{segment_index}")
});

for (segment_index, segment) in input.segments.iter().enumerate() {
Expand All @@ -83,23 +83,26 @@ impl IntoIterator for Inputs {
.unwrap_or_else(|| {
error!("Invalid segment duration range: {segment}");
});
let fade_to = to - self.fade.unwrap_or(0.) - 0.5;
let fade_to = to - self.fade.unwrap_or(0.) * input.speed - 0.5;

if !self.no_video {
let mut video_filters = vec![format!(
"[{}]trim={from}:{to}",
subtitled_video_label.as_ref().map_or_else(
|| video_label.clone(),
|label| format!("{label}:{segment_index}"),
),
label_subtitled_video
.as_ref()
.map_or_else(|| video_label.clone(), |func| func(segment_index)),
)];
if let Some(fade) = self.fade {
if let Some(mut fade) = self.fade {
fade *= input.speed;
video_filters.extend_from_slice(&[
format!("fade=t=in:st={from}:d={fade}"),
format!("fade=t=out:st={fade_to}:d={fade}"),
]);
}
video_filters.push(format!("setpts=PTS-STARTPTS[v{segment_count}]"));
video_filters.push(format!(
"setpts=(PTS-STARTPTS)/{}[v{segment_count}]",
input.speed,
));
filters.push(video_filters.join(","));
}

Expand All @@ -114,6 +117,9 @@ impl IntoIterator for Inputs {
format!("afade=t=out:st={fade_to}:d={fade}"),
]);
}
if input.speed != 1. {
audio_filters.push(format!("atempo={}", input.speed));
}
audio_filters.push(format!("asetpts=PTS-STARTPTS[a{segment_count}]"));
filters.push(audio_filters.join(","));
}
Expand Down

0 comments on commit df352f9

Please sign in to comment.