nathanbabcock / ffmpeg-sidecar

Wrap a standalone FFmpeg binary in an intuitive Iterator interface. 🏍
MIT License
284 stars 20 forks source link

Get number of total frames in input file to feed progress bar #45

Closed PanCakeConnaisseur closed 2 months ago

PanCakeConnaisseur commented 2 months ago

Hi Nathan,

thank you for this great crate.

Is there a built-in feature to get the number of frames in an input video? I want to convert a video and to calculate the progress bar, I do have the current frame but not how many there are in total. Or is there another way to feed a progress bar during encoding?

nathanbabcock commented 2 months ago

Hi Alexey,

Well, there is an event on the iterator called Progress which has all the progress info ffmpeg outputs like time in the video, also speed and a few others. All of these would depend on you knowing the video's duration in advance, though. Does that cover your use case or is the duration unknown also?

nathanbabcock commented 2 months ago

Oh yeah, there are a couple other events that might help. Some of the initial metadata about input & output streams is captured in the events FfmpegEvent::ParsedInput and FfmpegEvent::ParsedDuration. Sometimes the "Duration" field is missing in the case of streaming inputs, but for a file on disk it should hopefully be there and then you can calculate progress based on time / duration.

PanCakeConnaisseur commented 2 months ago

Thanks for the hints, Nathan. This is also what I had found after checking the documentation further. In case anyone else needs to extract this data. Here is some Rust code that might be useful.

#[derive(Debug, Clone, PartialEq)]
pub struct InputFileMetadata {
    pub fps: f32,
    pub duration: Duration,
}

fn detect_metadata(file_path: &str) -> Result<InputFileMetadata, &'static str> {
    let ffmpeg = FfmpegCommand::new()
        .input(file_path)
        .spawn()
        .unwrap()
        .iter()
        .unwrap();

    let mut video_fps: Option<f32> = None;
    let mut video_duration: Option<Duration> = None;

    for event in ffmpeg {
        match event {
            FfmpegEvent::Log(LogLevel::Error, e) => debug!("Ffmpeg error: {}", e),
            FfmpegEvent::ParsedDuration(e) => {
                video_duration = Some(Duration::new(e.duration as u64, 0));
            }
            FfmpegEvent::ParsedInputStream(e) => {
                video_fps = Some(e.fps);
            }
            _ => debug!("Other event"),
        }

        if video_fps.is_some() && video_duration.is_some() {
            break;
        }
    }

    if let (Some(fps), Some(duration)) = (video_fps, video_duration) {
        debug!(
            "Detected fps {} and duration {:?} from file {}.",
            fps, duration, file_path
        );
        Ok(InputFileMetadata { fps, duration })
    } else {
        Err("Failed to detect metadata from given file.")
    }
}

I am still learning Rust, so this might not be idiomatic, but it works.