Skip to content

Commit 8c2d692

Browse files
committed
use GstDiscoverer to get more accurate frame information
1 parent 996fa82 commit 8c2d692

File tree

3 files changed

+169
-22
lines changed

3 files changed

+169
-22
lines changed

Cargo.lock

Lines changed: 62 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

video-processor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2021"
77
gstreamer = "0.23.4"
88
gstreamer-video = "0.23.4"
99
gstreamer-app = "0.23.4"
10+
gstreamer-pbutils = "0.23.4"
1011
anyhow = { workspace = true }
1112
log = {workspace = true}
1213
image = { workspace = true }

video-processor/src/lib.rs

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use gstreamer_video as gst_video;
44
use gstreamer_app as gst_app;
55
use std::path::Path;
66
use log::{info, error, debug};
7-
use gst::MessageView;
87
use gst::prelude::*;
9-
8+
use gstreamer_pbutils::Discoverer;
109
pub use gst_video::video_frame::{VideoFrame, Readable, Writable, VideoFrameExt};
10+
use gstreamer_pbutils::prelude::*;
1111

1212
pub struct VideoProcessor {
1313
pipeline: gst::Pipeline,
@@ -159,32 +159,117 @@ impl VideoProcessor {
159159
let appsrc_weak = appsrc.downgrade();
160160
let frame_count = std::sync::atomic::AtomicUsize::new(0);
161161
let total_frames = {
162-
// a temporary pipeline for frame estimation
163-
let pipeline_str = format!(
164-
"filesrc location=\"{}\" ! decodebin ! videoconvert ! fakesink",
165-
input_path.to_str().unwrap()
166-
);
167-
let temp_pipeline = gst::parse::launch(&pipeline_str)?;
168-
let temp_pipeline = temp_pipeline.downcast::<gst::Pipeline>().unwrap();
169-
temp_pipeline.set_state(gst::State::Playing)?;
170-
171-
// on here, wait for pipeline to process the file
172-
let bus = temp_pipeline.bus().unwrap();
173162
let mut frames = 0;
174-
for msg in bus.iter_timed(gst::ClockTime::from_seconds(5)) {
175-
if let MessageView::Eos(..) = msg.view() {
176-
if let Some(duration) = temp_pipeline.query_duration::<gst::ClockTime>() {
177-
// Estimate frames based on duration. Most videos are 30 fps, but we can adjust this later
178-
frames = (duration.seconds() * 30) as usize;
163+
//a discoverer with 5 second timeout
164+
let timeout = gst::ClockTime::from_seconds(5);
165+
match Discoverer::new(timeout) {
166+
Ok(discoverer) => {
167+
// Win Path
168+
let path_str = input_path.to_str().unwrap();
169+
let uri = if path_str.starts_with("file://") {
170+
path_str.to_string()
171+
} else {
172+
// Convert win path to URI
173+
let absolute_path = std::fs::canonicalize(&input_path)
174+
.unwrap_or(input_path.to_path_buf());
175+
let path_str = absolute_path.to_str().unwrap()
176+
.trim_start_matches("\\?\\")
177+
.replace("\\", "/");
178+
format!("file:///{}", path_str)
179+
};
180+
181+
info!("Trying to discover URI: {}", uri);
182+
match discoverer.discover_uri(&uri) {
183+
Ok(info) => {
184+
//at this point, we will get the video info from the discoverer things going ugly in terms of code
185+
if let Some(video_info) = info.video_streams().get(0) {
186+
if let Some(caps) = video_info.caps() {
187+
if let Some(s) = caps.structure(0) {
188+
if let Ok(framerate) = s.get::<gst::Fraction>("framerate") {
189+
let duration = info.duration();
190+
let duration_secs = duration.unwrap_or(gst::ClockTime::ZERO).seconds();
191+
let fps = framerate.numer() as f64 / framerate.denom() as f64;
192+
frames = (duration_secs as f64 * fps) as usize;
193+
info!("Got exact framerate {}/{} fps, duration {}s, calculated frames: {}",
194+
framerate.numer(), framerate.denom(),
195+
duration_secs, frames);
196+
}
197+
}
198+
}
199+
}
200+
}
201+
Err(e) => {
202+
info!("Failed to discover video: {}", e);
203+
}
204+
}
205+
}
206+
Err(e) => {
207+
info!("Failed to create discoverer: {}", e);
208+
}
209+
}
210+
211+
// If we still couldn't get frames, try the pipeline method as fallback
212+
if frames == 0 {
213+
info!("Falling back to pipeline method");
214+
let input_str = input_path.to_str().unwrap().replace("\\", "\\\\");
215+
let pipeline_str = format!(
216+
"filesrc location=\"{}\" ! decodebin ! video/x-raw ! fakesink",
217+
input_str
218+
);
219+
220+
match gst::parse::launch(&pipeline_str) {
221+
Ok(temp_element) => {
222+
let temp_pipeline = temp_element.dynamic_cast::<gst::Pipeline>().unwrap();
223+
224+
// Set pipeline to PAUSED and wait a bit
225+
if temp_pipeline.set_state(gst::State::Paused).is_ok() {
226+
// Give some time for the pipeline to settle
227+
std::thread::sleep(std::time::Duration::from_secs(1));
228+
229+
if let Some(duration) = temp_pipeline.query_duration::<gst::ClockTime>() {
230+
// Try to get framerate from the decoder pad
231+
if let Some(sink) = temp_pipeline.by_name("fakesink0") {
232+
if let Some(sink_pad) = sink.static_pad("sink") {
233+
if let Some(caps) = sink_pad.current_caps() {
234+
if let Some(s) = caps.structure(0) {
235+
if let Ok(framerate) = s.get::<gst::Fraction>("framerate") {
236+
let duration_secs = duration.seconds();
237+
frames = ((duration_secs as f64) *
238+
(framerate.numer() as f64 /
239+
framerate.denom() as f64)) as usize;
240+
info!("Got framerate from pipeline: {}/{} fps",
241+
framerate.numer(), framerate.denom());
242+
}
243+
}
244+
}
245+
}
246+
}
247+
// If still no frames, use 30fps estimate
248+
if frames == 0 {
249+
frames = (duration.seconds() * 30) as usize;
250+
info!("Using estimated 30fps - duration: {}s, frames: {}",
251+
duration.seconds(), frames);
252+
}
253+
}
254+
255+
let _ = temp_pipeline.set_state(gst::State::Null);
256+
}
257+
}
258+
Err(e) => {
259+
info!("Failed to create pipeline: {}", e);
179260
}
180-
break;
181261
}
182262
}
183-
temp_pipeline.set_state(gst::State::Null)?;
263+
// still no luck?
264+
if frames == 0 {
265+
frames = 1000;
266+
info!("Using default frame count: {}", frames);
267+
}
268+
184269
frames
185270
};
186271

187-
info!("Estimated total frames: {}", total_frames);
272+
info!("Final total frames: {}", total_frames);
188273
// Clone for new_sample closure
189274
let appsrc_weak_cb = appsrc_weak.clone();
190275
// Clone for eos closure

0 commit comments

Comments
 (0)