Merge pull request #183 from Dewb/feat/thumb-orientation

Correctly orient videos in thumbnail mode
This commit is contained in:
Jeremy Soller 2025-11-11 18:59:32 -07:00 committed by GitHub
commit fa1637fe51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 68 deletions

View file

@ -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::<gst::Pipeline>()
.map_err(|_| iced_video_player::Error::Cast)
.unwrap();
pipeline.connect("element-setup", false, |vals| {
let Ok(elem) = vals[1].get::<gst::Element>() 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::<gst::GhostPad>().unwrap();
let bin = pad
.parent_element()
.unwrap()
.downcast::<gst::Bin>()
.unwrap();
let video_sink = bin.by_name("iced_video").unwrap();
let video_sink = video_sink.downcast::<gst_app::AppSink>().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();

View file

@ -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<dyn Error>> {
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 {

74
src/video.rs Normal file
View file

@ -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<Video, cosmic::Command<cosmic::app::Message<super::Message>>> {
//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::<gst::Pipeline>()
.map_err(|_| iced_video_player::Error::Cast)
.unwrap();
pipeline.connect("element-setup", false, |vals| {
let Ok(elem) = vals[1].get::<gst::Element>() 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::<gst::GhostPad>().unwrap();
let bin = pad
.parent_element()
.unwrap()
.downcast::<gst::Bin>()
.unwrap();
let video_sink = bin.by_name("iced_video").unwrap();
let video_sink = video_sink.downcast::<gst_app::AppSink>().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))
}
}
}