diff --git a/src/main.rs b/src/main.rs index dbba45d..adb9ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use cosmic::{ }; use iced_video_player::{ gst::{self, prelude::*}, - gst_app, gst_pbutils, Video, VideoPlayer, + gst_pbutils, Video, VideoPlayer, }; use std::{ any::TypeId, @@ -46,6 +46,7 @@ mod menu; mod mpris; mod project; mod thumbnail; +mod video; static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0); @@ -360,71 +361,9 @@ impl App { self.flags.config_state.recent_files.truncate(10); self.save_config_state(); - //TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error - //TODO: remove unwraps and enable playback of files with only audio. - let video = { - gst::init().unwrap(); - - let pipeline = format!( - "playbin uri=\"{}\" video-sink=\"videoscale ! videoconvert ! videoflip method=automatic ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", - url.as_str() - ); - let pipeline = gst::parse::launch(pipeline.as_ref()) - .unwrap() - .downcast::() - .map_err(|_| iced_video_player::Error::Cast) - .unwrap(); - pipeline.connect("element-setup", false, |vals| { - let Ok(elem) = vals[1].get::() else { - return None; - }; - if let Some(factory) = elem.factory() { - if factory.name() == "souphttpsrc" { - elem.set_property("user-agent", "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"); - } - } - None - }); - let video_sink: gst::Element = pipeline.property("video-sink"); - let pad = video_sink.pads().first().cloned().unwrap(); - let pad = pad.dynamic_cast::().unwrap(); - let bin = pad - .parent_element() - .unwrap() - .downcast::() - .unwrap(); - let video_sink = bin.by_name("iced_video").unwrap(); - let video_sink = video_sink.downcast::().unwrap(); - - match Video::from_gst_pipeline(pipeline.clone(), video_sink, None) { - Ok(ok) => ok, - Err(err) => { - log::warn!("failed to open {}: {err}", url); - // Handle codecs required before the file can play - let mut commands = Vec::new(); - while let Some(msg) = pipeline - .bus() - .unwrap() - .pop_filtered(&[gst::MessageType::Element]) - { - match msg.view() { - gst::MessageView::Element(element) => { - if gst_pbutils::MissingPluginMessage::is(&element) { - commands.push(Command::perform( - async { message::app(Message::MissingPlugin(msg)) }, - |x| x, - )); - // Do one codec install at a time - break; - } - } - _ => {} - } - } - pipeline.set_state(gst::State::Null).unwrap(); - return Command::batch(commands); - } - } + let video = match video::new_video(&url) { + Ok(ok) => ok, + Err(err) => return err }; self.duration = video.duration().as_secs_f64(); diff --git a/src/thumbnail.rs b/src/thumbnail.rs index 42ac8f5..6651a68 100644 --- a/src/thumbnail.rs +++ b/src/thumbnail.rs @@ -1,9 +1,11 @@ use cosmic::iced_core::image::Data; -use iced_video_player::{Position, Video}; +use iced_video_player::{Position}; use image::{DynamicImage, ImageFormat, RgbaImage}; use std::{error::Error, num::NonZero, path::Path, time::Duration}; use url::Url; +use super::video; + pub fn main( input: &Url, output: &Path, @@ -11,7 +13,11 @@ pub fn main( ) -> Result<(), Box> { let mut image = { let thumbnails = { - let mut video = Video::new(input)?; + let mut video = match video::new_video(input) { + Ok(ok) => ok, + Err(_err) => return Err(Into::into(format!("missing required plugin"))) + }; + let duration = video.duration(); //TODO: how best to decide time? let position = if duration.as_secs_f64() < 20.0 { diff --git a/src/video.rs b/src/video.rs new file mode 100644 index 0000000..339aead --- /dev/null +++ b/src/video.rs @@ -0,0 +1,74 @@ + +use iced_video_player::{ + gst::{self, prelude::*}, + gst_app, gst_pbutils, Video, +}; + +use cosmic::app::{Command, message}; + +pub fn new_video(url: &url::Url) -> Result>> { + //TODO: this code came from iced_video_player::Video::new and has been modified to stop the pipeline on error + //TODO: remove unwraps and enable playback of files with only audio. + gst::init().unwrap(); + + let pipeline = format!( + "playbin uri=\"{}\" video-sink=\"videoscale ! videoconvert ! videoflip method=automatic ! appsink name=iced_video drop=true caps=video/x-raw,format=NV12,pixel-aspect-ratio=1/1\"", + url.as_str() + ); + let pipeline = gst::parse::launch(pipeline.as_ref()) + .unwrap() + .downcast::() + .map_err(|_| iced_video_player::Error::Cast) + .unwrap(); + pipeline.connect("element-setup", false, |vals| { + let Ok(elem) = vals[1].get::() else { + return None; + }; + if let Some(factory) = elem.factory() { + if factory.name() == "souphttpsrc" { + elem.set_property("user-agent", "Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0"); + } + } + None + }); + let video_sink: gst::Element = pipeline.property("video-sink"); + let pad = video_sink.pads().first().cloned().unwrap(); + let pad = pad.dynamic_cast::().unwrap(); + let bin = pad + .parent_element() + .unwrap() + .downcast::() + .unwrap(); + let video_sink = bin.by_name("iced_video").unwrap(); + let video_sink = video_sink.downcast::().unwrap(); + + match Video::from_gst_pipeline(pipeline.clone(), video_sink, None) { + Ok(ok) => Ok(ok), + Err(err) => { + log::warn!("failed to open {}: {err}", url); + // Handle codecs required before the file can play + let mut commands = Vec::new(); + while let Some(msg) = pipeline + .bus() + .unwrap() + .pop_filtered(&[gst::MessageType::Element]) + { + match msg.view() { + gst::MessageView::Element(element) => { + if gst_pbutils::MissingPluginMessage::is(&element) { + commands.push(Command::perform( + async { message::app(super::Message::MissingPlugin(msg)) }, + |x| x, + )); + // Do one codec install at a time + break; + } + } + _ => {} + } + } + pipeline.set_state(gst::State::Null).unwrap(); + Err(Command::batch(commands)) + } + } +} \ No newline at end of file