Provide thumbnailing
This commit is contained in:
parent
9cc1660537
commit
55654e1231
7 changed files with 128 additions and 11 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,4 +1,8 @@
|
||||||
/.cargo/
|
/.cargo/
|
||||||
|
/debian/*debhelper*
|
||||||
|
/debian/cosmic-player.substvars
|
||||||
|
/debian/cosmic-player/
|
||||||
|
/debian/files
|
||||||
/target/
|
/target/
|
||||||
/vendor/
|
/vendor/
|
||||||
/vendor.tar
|
/vendor.tar
|
||||||
|
|
|
||||||
3
build.rs
3
build.rs
|
|
@ -1,6 +1,5 @@
|
||||||
use vergen::EmitBuilder;
|
use vergen::EmitBuilder;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
EmitBuilder::builder().git_sha(true).emit().unwrap();
|
EmitBuilder::builder().git_sha(true).emit().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
justfile
13
justfile
|
|
@ -20,6 +20,10 @@ metainfo := APPID + '.metainfo.xml'
|
||||||
metainfo-src := 'res' / metainfo
|
metainfo-src := 'res' / metainfo
|
||||||
metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo
|
metainfo-dst := clean(rootdir / prefix) / 'share' / 'metainfo' / metainfo
|
||||||
|
|
||||||
|
thumbnailer := APPID + '.thumbnailer'
|
||||||
|
thumbnailer-src := 'res' / thumbnailer
|
||||||
|
thumbnailer-dst := clean(rootdir / prefix) / 'share' / 'thumbnailers' / thumbnailer
|
||||||
|
|
||||||
icons-src := 'res' / 'icons' / 'hicolor'
|
icons-src := 'res' / 'icons' / 'hicolor'
|
||||||
icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor'
|
icons-dst := clean(rootdir / prefix) / 'share' / 'icons' / 'hicolor'
|
||||||
|
|
||||||
|
|
@ -72,13 +76,20 @@ install:
|
||||||
install -Dm0755 {{bin-src}} {{bin-dst}}
|
install -Dm0755 {{bin-src}} {{bin-dst}}
|
||||||
install -Dm0644 {{desktop-src}} {{desktop-dst}}
|
install -Dm0644 {{desktop-src}} {{desktop-dst}}
|
||||||
install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
|
install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
|
||||||
|
install -Dm0644 {{thumbnailer-src}} {{thumbnailer-dst}}
|
||||||
for size in `ls {{icons-src}}`; do \
|
for size in `ls {{icons-src}}`; do \
|
||||||
install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
|
install -Dm0644 "{{icons-src}}/$size/apps/{{APPID}}.svg" "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
|
||||||
done
|
done
|
||||||
|
|
||||||
# Uninstalls installed files
|
# Uninstalls installed files
|
||||||
uninstall:
|
uninstall:
|
||||||
rm {{bin-dst}}
|
rm -f {{bin-dst}}
|
||||||
|
rm -f {{desktop-dst}}
|
||||||
|
rm -f {{metainfo-dst}}
|
||||||
|
rm -f {{thumbnailer-dst}}
|
||||||
|
for size in `ls {{icons-src}}`; do \
|
||||||
|
rm -f "{{icons-dst}}/$size/apps/{{APPID}}.svg"; \
|
||||||
|
done
|
||||||
|
|
||||||
# Vendor dependencies locally
|
# Vendor dependencies locally
|
||||||
vendor:
|
vendor:
|
||||||
|
|
|
||||||
4
res/com.system76.CosmicPlayer.thumbnailer
Normal file
4
res/com.system76.CosmicPlayer.thumbnailer
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[Thumbnailer Entry]
|
||||||
|
TryExec=cosmic-player
|
||||||
|
Exec=cosmic-player --thumbnail %o --size %s %u
|
||||||
|
MimeType=application/mxf;application/ram;application/sdp;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.ms-wpl;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flash-video;application/x-matroska;application/x-netshow-channel;application/x-quicktimeplayer;application/x-shorten;image/vnd.rn-realpix;image/x-pict;misc/ultravox;text/x-google-video-pointer;video/3gp;video/3gpp;video/3gpp2;video/dv;video/divx;video/fli;video/flv;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vivo;video/vnd.divx;video/vnd.mpegurl;video/vnd.rn-realvideo;video/vnd.vivo;video/webm;video/x-anim;video/x-avi;video/x-flc;video/x-fli;video/x-flic;video/x-flv;video/x-m4v;video/x-matroska;video/x-mjpeg;video/x-mpeg;video/x-mpeg2;video/x-ms-asf;video/x-ms-asf-plugin;video/x-ms-asx;video/x-msvideo;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvx;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;video/x-totem-stream;audio/x-pn-realaudio;application/smil;application/smil+xml;application/x-quicktime-media-link;application/x-smil;text/google-video-pointer;x-content/video-dvd;x-scheme-handler/pnm;x-scheme-handler/mms;x-scheme-handler/net;x-scheme-handler/rtp;x-scheme-handler/rtmp;x-scheme-handler/rtsp;x-scheme-handler/mmsh;x-scheme-handler/uvox;x-scheme-handler/icy;x-scheme-handler/icyx;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2024 System76 <info@system76.com>
|
// Copyright 2024 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{fs, io};
|
use std::{fs, io, path::PathBuf};
|
||||||
|
|
||||||
use clap_lex::RawArgs;
|
use clap_lex::RawArgs;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
@ -25,9 +25,42 @@ pub fn parse() -> Arguments {
|
||||||
Err(os_str) => warn!("unexpected flag: -{}", os_str.to_string_lossy()),
|
Err(os_str) => warn!("unexpected flag: -{}", os_str.to_string_lossy()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some((long, _opt_value)) = arg.to_long() {
|
} else if let Some((long, opt_value)) = arg.to_long() {
|
||||||
match long {
|
match long {
|
||||||
Ok("help") => print_help(),
|
Ok("help") => print_help(),
|
||||||
|
Ok("size") => {
|
||||||
|
if let Some(value) = opt_value
|
||||||
|
.or_else(|| raw_args.next_os(&mut cursor))
|
||||||
|
.map(|x| x.to_string_lossy())
|
||||||
|
{
|
||||||
|
let mut parts = value.split('x');
|
||||||
|
let width_str = parts.next().unwrap_or("");
|
||||||
|
let width = match width_str.parse::<u32>() {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("failed to parse size '{}': {}", value, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let height = match parts.next().unwrap_or(width_str).parse::<u32>() {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("failed to parse size '{}': {}", value, err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
arguments.size_opt = Some((width, height));
|
||||||
|
} else {
|
||||||
|
warn!("size requires value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok("thumbnail") => {
|
||||||
|
if let Some(value) = opt_value.or_else(|| raw_args.next_os(&mut cursor)) {
|
||||||
|
arguments.thumbnail_opt = Some(PathBuf::from(value));
|
||||||
|
} else {
|
||||||
|
warn!("thumbnail requires value");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok("version") => print_version(),
|
Ok("version") => print_version(),
|
||||||
_ => warn!("unexpected flag: {}", arg.display()),
|
_ => warn!("unexpected flag: {}", arg.display()),
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +94,8 @@ pub struct Arguments {
|
||||||
pub urls: Option<Vec<Url>>,
|
pub urls: Option<Vec<Url>>,
|
||||||
/// Single URL only
|
/// Single URL only
|
||||||
pub url_opt: Option<Url>,
|
pub url_opt: Option<Url>,
|
||||||
|
pub thumbnail_opt: Option<PathBuf>,
|
||||||
|
pub size_opt: Option<(u32, u32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
|
|
@ -113,8 +148,10 @@ libcosmic-based multimedia player for music and videos.
|
||||||
Project home page: https://github.com/pop-os/cosmic-player
|
Project home page: https://github.com/pop-os/cosmic-player
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this message
|
-h, --help Show this message
|
||||||
-V, --version Show the version of cosmic-player"#
|
-V, --version Show the version of cosmic-player
|
||||||
|
--thumbnail <output> Generate thumbnail and save in output
|
||||||
|
--size <width>x<height> Thumbnail size in pixels"#
|
||||||
);
|
);
|
||||||
|
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
|
|
|
||||||
24
src/main.rs
24
src/main.rs
|
|
@ -45,6 +45,7 @@ mod menu;
|
||||||
#[cfg(feature = "mpris-server")]
|
#[cfg(feature = "mpris-server")]
|
||||||
mod mpris;
|
mod mpris;
|
||||||
mod project;
|
mod project;
|
||||||
|
mod thumbnail;
|
||||||
|
|
||||||
static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0);
|
static CONTROLS_TIMEOUT: Duration = Duration::new(2, 0);
|
||||||
|
|
||||||
|
|
@ -71,6 +72,25 @@ fn language_name(code: &str) -> Option<String> {
|
||||||
/// Runs application with these settings
|
/// Runs application with these settings
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||||
|
|
||||||
|
let args = argparse::parse();
|
||||||
|
|
||||||
|
if let Some(output) = args.thumbnail_opt {
|
||||||
|
let Some(input) = args.url_opt else {
|
||||||
|
log::error!("thumbnailer can only handle exactly one URL");
|
||||||
|
process::exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
match thumbnail::main(&input, &output, args.size_opt) {
|
||||||
|
Ok(()) => process::exit(0),
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("failed to thumbnail '{}': {}", input, err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "redox")))]
|
#[cfg(all(unix, not(target_os = "redox")))]
|
||||||
match fork::daemon(true, true) {
|
match fork::daemon(true, true) {
|
||||||
Ok(fork::Fork::Child) => (),
|
Ok(fork::Fork::Child) => (),
|
||||||
|
|
@ -81,12 +101,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
|
||||||
|
|
||||||
localize::localize();
|
localize::localize();
|
||||||
|
|
||||||
let args = argparse::parse();
|
|
||||||
|
|
||||||
let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) {
|
let (config_handler, config) = match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) {
|
||||||
Ok(config_handler) => {
|
Ok(config_handler) => {
|
||||||
let config = match Config::get_entry(&config_handler) {
|
let config = match Config::get_entry(&config_handler) {
|
||||||
|
|
|
||||||
46
src/thumbnail.rs
Normal file
46
src/thumbnail.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use cosmic::iced_core::image::Data;
|
||||||
|
use iced_video_player::{Position, Video};
|
||||||
|
use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||||
|
use std::{error::Error, num::NonZero, path::Path, time::Duration};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub fn main(
|
||||||
|
input: &Url,
|
||||||
|
output: &Path,
|
||||||
|
size_opt: Option<(u32, u32)>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut image = {
|
||||||
|
let thumbnails = {
|
||||||
|
let mut video = Video::new(input)?;
|
||||||
|
let duration = video.duration();
|
||||||
|
//TODO: how best to decide time?
|
||||||
|
let position = if duration.as_secs_f64() < 20.0 {
|
||||||
|
// If less than 20 seconds, divide duration by 2
|
||||||
|
Position::Time(duration / 2)
|
||||||
|
} else {
|
||||||
|
// If more than 20 seconds, thumbnail at 10 seconds
|
||||||
|
Position::Time(Duration::new(10, 0))
|
||||||
|
};
|
||||||
|
video.thumbnails([position], NonZero::new(1).unwrap())?
|
||||||
|
};
|
||||||
|
//TODO: do not require clone of pixels data
|
||||||
|
match thumbnails[0].data() {
|
||||||
|
Data::Rgba {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixels,
|
||||||
|
} => RgbaImage::from_raw(*width, *height, pixels.to_vec())
|
||||||
|
.map(DynamicImage::ImageRgba8)
|
||||||
|
.ok_or_else(|| format!("failed to convert thumbnail")),
|
||||||
|
_ => Err(format!("unsupported thumbnail handle {:?}", thumbnails[0])),
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
if let Some((width, height)) = size_opt {
|
||||||
|
image = image.thumbnail(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
image.save_with_format(output, ImageFormat::Png)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue