//! 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 { 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:?}"); } } pub mod audio; pub mod decode; pub mod export; pub mod player; pub mod thumbnail; mod priority; #[cfg(windows)] #[allow(unsafe_code)] mod windows_priority;