feat: add clipbin-media for reading segments with FFmpeg
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.
This commit is contained in:
Generated
+39
@@ -881,6 +881,14 @@ dependencies = [
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipbin-media"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clipbin-core",
|
||||
"ffmpeg-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipbin-obs"
|
||||
version = "0.1.0"
|
||||
@@ -1680,6 +1688,31 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6380599799e175191eb7ffe82c97f36a2a90a36cbc54c738a903e5287d7f516a"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b939bf79dd5949412a4b81cfe21a07f48ea21b47fcbb5f57816c8c2de5ae30b"
|
||||
dependencies = [
|
||||
"bindgen 0.72.1",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.3"
|
||||
@@ -5965,6 +5998,12 @@ dependencies = [
|
||||
"sval_serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"crates/clipbin-core",
|
||||
"crates/clipbin-desktop",
|
||||
"crates/clipbin-media",
|
||||
"crates/clipbin-obs",
|
||||
"crates/clipbin-probe",
|
||||
"crates/rig-pointer",
|
||||
@@ -19,6 +20,7 @@ publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
clipbin-core = { path = "crates/clipbin-core" }
|
||||
clipbin-media = { path = "crates/clipbin-media" }
|
||||
clipbin-obs = { path = "crates/clipbin-obs" }
|
||||
gpui = "=0.2.2"
|
||||
# The tested OBS set: the libobs crate's bundled headers are OBS 32.0.4, matching
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "clipbin-media"
|
||||
description = "Reading recorded segments: clip export (remux) and playback decoding, via FFmpeg."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clipbin-core.workspace = true
|
||||
ffmpeg-next = { version = "9", default-features = false, features = ["codec", "format", "software-scaling", "software-resampling"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,69 @@
|
||||
//! 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:?}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user