One shared FFmpeg binding (ffmpeg-next 9, dynamically linked: the system FFmpeg on Linux, the OBS-bundled DLLs on Windows) for clip export and playback, with a probe of segment duration, codec, size and audio tracks.
70 lines
2.1 KiB
Rust
70 lines
2.1 KiB
Rust
//! Reading recorded segments with FFmpeg: clip export (remuxing without
|
|
//! re-encoding) and decoding for playback.
|
|
//!
|
|
//! FFmpeg is linked dynamically: the system's libraries on Linux (the OBS
|
|
//! runtime already depends on them) and, on Windows, the FFmpeg DLLs shipped
|
|
//! in the official OBS release that scripts/obs/assemble-windows.sh bundles.
|
|
|
|
use std::{path::Path, sync::Once, time::Duration};
|
|
|
|
pub use ffmpeg_next as ffmpeg;
|
|
|
|
/// Initialises FFmpeg once per process.
|
|
pub fn init() {
|
|
static INIT: Once = Once::new();
|
|
INIT.call_once(|| {
|
|
let _ = ffmpeg::init();
|
|
ffmpeg::log::set_level(ffmpeg::log::Level::Error);
|
|
});
|
|
}
|
|
|
|
/// Basic facts about a segment file.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct Probe {
|
|
pub duration: Duration,
|
|
pub video_codec: String,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub audio_tracks: usize,
|
|
}
|
|
|
|
pub fn probe(path: &Path) -> Result<Probe, ffmpeg::Error> {
|
|
init();
|
|
let input = ffmpeg::format::input(path)?;
|
|
let video = input
|
|
.streams()
|
|
.best(ffmpeg::media::Type::Video)
|
|
.ok_or(ffmpeg::Error::StreamNotFound)?;
|
|
let decoder = ffmpeg::codec::context::Context::from_parameters(video.parameters())?
|
|
.decoder()
|
|
.video()?;
|
|
let micros = input.duration().max(0) as u64;
|
|
Ok(Probe {
|
|
duration: Duration::from_micros(micros),
|
|
video_codec: decoder.id().name().to_owned(),
|
|
width: decoder.width(),
|
|
height: decoder.height(),
|
|
audio_tracks: input
|
|
.streams()
|
|
.filter(|stream| stream.parameters().medium() == ffmpeg::media::Type::Audio)
|
|
.count(),
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// Set CLIPBIN_TEST_SEGMENT to a recorded segment to run this.
|
|
#[test]
|
|
fn probes_a_recorded_segment() {
|
|
let Some(path) = std::env::var_os("CLIPBIN_TEST_SEGMENT") else {
|
|
return;
|
|
};
|
|
let probe = probe(Path::new(&path)).unwrap();
|
|
assert!(probe.duration > Duration::ZERO);
|
|
assert!(probe.width > 0 && probe.height > 0);
|
|
println!("{probe:?}");
|
|
}
|
|
}
|