diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs index 56635cb..65f12c9 100644 --- a/src/ffmpeg.rs +++ b/src/ffmpeg.rs @@ -23,9 +23,10 @@ struct FfprobeOut { } #[derive(serde::Deserialize)] -struct StreamInfo { - width: usize, - height: usize, +#[serde(tag = "codec_type", rename_all = "lowercase")] +enum StreamInfo { + Video { width: usize, height: usize }, + Audio, } #[derive(serde::Deserialize)] @@ -37,6 +38,7 @@ struct FormatInfo { pub struct VideoMeta { pub width: usize, pub height: usize, + pub audio_streams: usize, pub duration_us: usize, } @@ -62,10 +64,8 @@ pub async fn get_video_meta(path: &Path) -> Result { .args([ "-v", "error", - "-select_streams", - "v:0", "-show_entries", - "stream=width,height : format=duration", + "stream=width,height,codec_type : format=duration", "-of", "json", ]) @@ -77,17 +77,28 @@ pub async fn get_video_meta(path: &Path) -> Result { let str = String::from_utf8(output.stdout)?; let output: FfprobeOut = serde_json::from_str(&str)?; - let stream = output + let (width, height) = output .streams - .first() + .iter() + .find_map(|s| match s { + StreamInfo::Video { width, height } => Some((*width, *height)), + _ => None, + }) .wrap_err("could not get stream information from ffprobe")?; + let audio_streams = output + .streams + .iter() + .filter(|s| matches!(s, StreamInfo::Audio)) + .count(); + let duration_sec = output.format.duration.parse::()?; let duration_us = (duration_sec * 1_000_000.0).ceil() as usize; Ok(VideoMeta { - width: stream.width, - height: stream.height, + width, + height, + audio_streams, duration_us, }) } @@ -115,7 +126,6 @@ pub async fn convert_video( Codec::VP9 => &["-c:v", "libvpx-vp9", "-row-mt", "1"], }; - // TODO: maybe check if the video has 2 audio tracks? or at least use a "fail-safe" method let merge_args: &[&str] = if merge_tracks { &["-ac", "2", "-filter_complex", "amerge=inputs=2"] } else { diff --git a/src/main.rs b/src/main.rs index 19caac3..a445eee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,6 +94,7 @@ struct Tyrolienne { step: Step, progress: usize, total: usize, + can_merge: bool, video_path: Option, out_filename: Option, out_codec: ffmpeg::Codec, @@ -116,7 +117,7 @@ impl Tyrolienne { config: self.config.clone(), out_filename: self.out_filename.clone(), out_codec: self.out_codec, - merge_tracks: self.merge_tracks, + merge_tracks: self.can_merge && self.merge_tracks, video_path: self.video_path.clone(), folder: self.folder.clone(), } @@ -190,7 +191,10 @@ impl AsyncComponent for Tyrolienne { adw::SwitchRow { set_title: "Merge audio tracks", - set_active: true, + #[watch] + set_sensitive: model.can_merge, + #[watch] + set_active: model.can_merge && model.merge_tracks, connect_active_notify[sender] => move |s| sender.input(Message::SetMergeTracks(s.is_active())), }, @@ -246,6 +250,7 @@ impl AsyncComponent for Tyrolienne { step: Step::Waiting, progress: 0, total: 1, + can_merge: false, video_path: None, out_filename: None, out_codec: ffmpeg::Codec::VP9, @@ -306,6 +311,8 @@ impl AsyncComponent for Tyrolienne { .await; if let Some(file) = file { + let meta = ffmpeg::get_video_meta(file.path()).await; + self.can_merge = meta.map(|m| m.audio_streams == 2).unwrap_or(false); self.video_path = Some(file.path().to_owned()); } }