use crate::common::gst::{
GStreamer, caps::GStreamerCapsType, element::GStreamerElementType, pipeline::GStreamerPipeline,
};
use gstreamer::{
ClockTime, PadProbeData, PadProbeReturn, PadProbeType,
event::Eos,
prelude::{ElementExt, ElementExtManual, PadExt, PadExtManual},
};
use std::{
fs::create_dir_all,
path::Path,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use wayclip_core::{
app::clips::ffmpeg::{DEFAULT_PREVIEW_CLIP_LENGTH, DEFAULT_PREVIEW_WIDTH, PreviewGenerator},
models::error::WayclipError,
};
pub struct PreviewManager;
impl PreviewManager {
pub fn generate_preview(input_path: &Path, output_path: &Path) -> Result<(), WayclipError> {
if let Some(parent) = output_path.parent() {
create_dir_all(parent)?;
}
if !input_path.exists() {
return Err(WayclipError::Remux("Preview file does not exist".into()));
}
let pipeline = GStreamerPipeline::new();
let file_src = GStreamer::build_element(GStreamerElementType::FileSource {
location: input_path.to_path_buf(),
})?;
let decode_bin = GStreamer::build_element(GStreamerElementType::DecodeBin)?;
let video_convert_1 = GStreamer::build_element(GStreamerElementType::VideoConvert)?;
let video_scale = GStreamer::build_element(GStreamerElementType::VideoScale)?;
let video_convert_2 = GStreamer::build_element(GStreamerElementType::VideoConvert)?;
let caps = GStreamer::build_caps(GStreamerCapsType::VideoXRaw {
height: None,
framerate: None,
format: None,
width: Some(DEFAULT_PREVIEW_WIDTH),
memory: None,
});
let caps_filter = GStreamer::build_element(GStreamerElementType::CapsFilter { caps })?;
let encoder = GStreamer::build_element(GStreamerElementType::X264Enc)?;
let parser = GStreamer::build_element(GStreamerElementType::H264Parse)?;
let mux = GStreamer::build_element(GStreamerElementType::MatroskaMux)?;
let file_sink = GStreamer::build_element(GStreamerElementType::FileSink {
location: output_path.to_path_buf(),
})?;
pipeline.add_many([
&file_src,
&decode_bin,
&video_convert_1,
&video_scale,
&video_convert_2,
&caps_filter,
&encoder,
&parser,
&mux,
&file_sink,
])?;
pipeline.link(&file_src, &decode_bin)?;
pipeline.link(&video_convert_1, &video_scale)?;
pipeline.link(&video_scale, &video_convert_2)?;
pipeline.link(&video_convert_2, &caps_filter)?;
pipeline.link(&caps_filter, &encoder)?;
pipeline.link(&encoder, &parser)?;
GStreamer::link_dynamic_pad(&decode_bin, video_convert_1, "video/");
let mux_sink_pad = mux.request_pad_simple("video_%u").ok_or_else(|| {
WayclipError::Remux("Failed to request video pad from matroskamux".into())
})?;
let parser_pad = parser
.static_pad("src")
.ok_or_else(|| WayclipError::Remux("No static src pad".into()))?;
let eos_sent = Arc::new(AtomicBool::new(false));
let eos_sent_clone = eos_sent.clone();
let first_pts: Arc<std::sync::Mutex<Option<ClockTime>>> =
Arc::new(std::sync::Mutex::new(None));
let first_pts_clone = first_pts.clone();
parser_pad.add_probe(PadProbeType::BUFFER, move |pad, info| {
if eos_sent_clone.load(Ordering::SeqCst) {
return PadProbeReturn::Drop;
}
if let Some(PadProbeData::Buffer(ref buffer)) = info.data {
let pts = buffer.pts().unwrap_or(ClockTime::ZERO);
let mut first = first_pts_clone.lock().unwrap();
let start_pts = *first.get_or_insert(pts);
let elapsed = pts.saturating_sub(start_pts);
if elapsed >= ClockTime::from_seconds(DEFAULT_PREVIEW_CLIP_LENGTH) {
eos_sent_clone.store(true, Ordering::SeqCst);
pad.push_event(Eos::new());
return PadProbeReturn::Drop;
}
}
PadProbeReturn::Ok
});
parser_pad.link(&mux_sink_pad)?;
pipeline.link(&mux, &file_sink)?;
pipeline.play_and_wait_eos(ClockTime::from_seconds(10), ClockTime::from_seconds(30))?;
log::debug!(
"Finished saving MKV preview to {}",
output_path.to_string_lossy()
);
Ok(())
}
}
impl PreviewGenerator for PreviewManager {
fn generate_preview(&self, video_path: &Path, preview_path: &Path) -> Result<(), WayclipError> {
Self::generate_preview(video_path, preview_path)
}
}