From 11f5bd081bf303e96e79a9c3bd2c87855f1584e0 Mon Sep 17 00:00:00 2001 From: rabite Date: Sat, 29 Jun 2019 23:33:25 +0200 Subject: [PATCH] modularize media preview generator into own workspace --- Cargo.lock | 14 +- Cargo.toml | 29 +- hunter-media/.gitignore | 1 + hunter-media/Cargo.lock | 6 + hunter-media/Cargo.toml | 25 ++ hunter-media/src/main.rs | 587 +++++++++++++++++++++++++++++++++++++++ src/config.rs | 8 +- src/fail.rs | 3 + src/files.rs | 6 +- src/imgview.rs | 22 +- src/main.rs | 3 - src/mediaview.rs | 60 +++- src/preview.rs | 24 +- src/widget.rs | 13 +- 14 files changed, 737 insertions(+), 64 deletions(-) create mode 100644 hunter-media/.gitignore create mode 100644 hunter-media/Cargo.lock create mode 100644 hunter-media/Cargo.toml create mode 100644 hunter-media/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 9ef7577..040fb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,9 +453,6 @@ dependencies = [ "dirs-2 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-app 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", - "image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "lscolors 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -474,6 +471,17 @@ dependencies = [ "users 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hunter-media" +version = "0.1.0" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-app 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "image" version = "0.21.2" diff --git a/Cargo.toml b/Cargo.toml index fe35709..4a146b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["default-run"] + [package] name = "hunter" version = "1.2.1" @@ -10,6 +12,8 @@ readme = "README.md" license = "WTFPL" keywords = ["cli", "terminal", "file"] categories = ["command-line-utilities"] +default-run = "hunter" + [dependencies] termion = "1.5.1" @@ -36,29 +40,16 @@ pathbuftools = "0.1" clap = "2.33" mime = "0.3.13" - - -image = { version = "0.21.1", optional = true } -gstreamer = { version = "0.11.2", optional = true } -gstreamer-app = { version = "0.11.2", optional = true } - - - - [features] -default = ["img", "video"] -img = ["image"] -video = ["img", "gstreamer", "gstreamer-app"] +default = ["video"] +video = [] -[[bin]] -name = "hunter" -path = "src/main.rs" -[[bin]] -name = "preview-gen" -path = "src/preview-gen.rs" -required-features = ["img"] + +[workspace] +members = [".", "hunter-media"] +default-members = [".", "hunter-media"] [patch.crates-io] diff --git a/hunter-media/.gitignore b/hunter-media/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/hunter-media/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/hunter-media/Cargo.lock b/hunter-media/Cargo.lock new file mode 100644 index 0000000..c2c6cce --- /dev/null +++ b/hunter-media/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "hunter-preview" +version = "0.1.0" + diff --git a/hunter-media/Cargo.toml b/hunter-media/Cargo.toml new file mode 100644 index 0000000..eb0df6a --- /dev/null +++ b/hunter-media/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "hunter-media" +version = "0.1.0" +authors = ["rabite0"] +edition = "2018" +description = "hunter's preview generator for image/video/audio files" +homepage = "https://github.com/rabite0/hunter" +repository = "https://github.com/rabite0/hunter" +readme = "../README.md" +license = "WTFPL" +keywords = ["cli", "terminal", "file"] +categories = ["command-line-utilities"] + + +[dependencies] +termion = "1.5.1" +failure = "0.1.5" +image = "0.21.1" +gstreamer = { version = "0.11.2", optional = true } +gstreamer-app = { version = "0.11.2", optional = true } + + +[features] +default = ["video"] +video = ["gstreamer", "gstreamer-app"] diff --git a/hunter-media/src/main.rs b/hunter-media/src/main.rs new file mode 100644 index 0000000..41f48a1 --- /dev/null +++ b/hunter-media/src/main.rs @@ -0,0 +1,587 @@ +// Based on https://github.com/jD91mZM2/termplay +// MIT License + +use image::{Pixel, FilterType, DynamicImage, GenericImageView}; + +use termion::color::{Bg, Fg, Rgb}; +#[cfg(feature = "video")] +use termion::input::TermRead; + + +#[cfg(feature = "video")] +use gstreamer::{self, prelude::*}; +#[cfg(feature = "video")] +use gstreamer_app; + +use failure::Error; +#[cfg(feature = "video")] +use failure::format_err; + + +use std::io::Write; +#[cfg(feature = "video")] +use std::sync::{Arc, RwLock}; + +pub type MResult = Result; + +fn main() -> MResult<()> { + let args = std::env::args().collect::>(); + let xsize: usize = args.get(1) + .expect("Provide xsize") + .parse::() + .unwrap(); + let ysize = args.get(2) + .expect("provide ysize") + .parse() + .unwrap(); + let xpos = args.get(3) + .expect("provide xpos") + .parse() + .unwrap(); + let ypos = args.get(4) + .expect("provide ypos") + .parse() + .unwrap(); + let preview_type = args.get(5) + .expect("Provide preview type") + .parse::() + .unwrap(); + let autoplay = args.get(6) + .expect("Autoplay?") + .parse::() + .unwrap(); + let mute = args.get(7) + .expect("Muted?") + .parse::() + .unwrap(); + let path = args.get(8).expect("Provide path"); + + + let result = + match preview_type.as_ref() { + #[cfg(feature = "video")] + "video" => video_preview(path, + xsize, + ysize, + 0, + 0, + autoplay, + mute, + false), + + "image" => image_preview(path, + xsize, + ysize), + + #[cfg(feature = "video")] + "audio" => audio_preview(path, + autoplay, + mute), + + #[cfg(feature = "video")] + "video-raw" => video_preview(path, + xsize, + ysize, + xpos, + ypos, + autoplay, + mute, + true), + #[cfg(feature = "video")] + _ => { panic!("Available types: video(-raw)/image/audio") } + + #[cfg(not(feature = "video"))] + _ => { panic!("Available type: image") } + }; + + if result.is_err() { + println!("{:?}", &result); + result + } else { + Ok(()) + } +} + +fn image_preview(path: &str, + xsize: usize, + ysize: usize) -> MResult<()> { + let img = image::open(&path)?; + + let renderer = Renderer::new(xsize, + ysize, + 0, + 0); + + renderer.send_image(&img)?; + + Ok(()) +} + +// #[cfg(feature = "video")] +fn video_preview(path: &String, + xsize: usize, + ysize: usize, + xpos: usize, + ypos: usize, + autoplay: bool, + mute: bool, + raw: bool) + -> MResult<()> { + + let (player, appsink) = make_gstreamer()?; + + let uri = format!("file://{}", &path); + + player.set_property("uri", &uri)?; + + + let renderer = Renderer::new(xsize, ysize, xpos, ypos); + let renderer = Arc::new(RwLock::new(renderer)); + let crenderer = renderer.clone(); + + + + + let p = player.clone(); + + appsink.set_callbacks( + gstreamer_app::AppSinkCallbacks::new() + .new_sample({ + move |sink| { + let sample = match sink.pull_sample() { + Some(sample) => sample, + None => return gstreamer::FlowReturn::Eos, + }; + + let position = p.query_position::() + .map(|p| p.seconds().unwrap_or(0)) + .unwrap_or(0); + + let duration = p.query_duration::() + .map(|d| d.seconds().unwrap_or(0)) + .unwrap_or(0); + + if let Ok(mut renderer) = crenderer.write() { + match renderer.send_frame(&*sample, + position, + duration, + raw) { + Ok(()) => { + if autoplay == false { + // Just render first frame to get a static image + match p.set_state(gstreamer::State::Paused) + .into_result() { + Ok(_) => gstreamer::FlowReturn::Eos, + Err(_) => gstreamer::FlowReturn::Error + } + } else { + gstreamer::FlowReturn::Ok + } + } + Err(err) => { + println!("{:?}", err); + gstreamer::FlowReturn::Error + } + } + } else { gstreamer::FlowReturn::Error } + + } + }) + .eos({ + move |_| { + std::process::exit(0); + } + }) + .build() + ); + + if mute == true || autoplay == false { + player.set_property("volume", &0.0)?; + } + player.set_state(gstreamer::State::Playing).into_result()?; + + + + + + read_keys(player, Some(renderer))?; + + Ok(()) +} + +// #[cfg(feature = "video")] +fn read_keys(player: gstreamer::Element, + renderer: Option>>) -> MResult<()> { + let seek_time = gstreamer::ClockTime::from_seconds(5); + + let stdin = std::io::stdin(); + let mut stdin = stdin.lock(); + + loop { + let input = stdin + .read_line()? + .unwrap_or_else(|| String::from("q")); + + + match input.as_str() { + "q" => std::process::exit(0), + ">" => { + if let Some(mut time) = player + .query_position::() { + time += seek_time; + + player.seek_simple( + gstreamer::SeekFlags::FLUSH, + gstreamer::format::GenericFormattedValue::from_time(time) + )?; + } + }, + "<" => { + if let Some(mut time) = player + .query_position::() { + if time >= seek_time { + time -= seek_time; + } else { + time = gstreamer::ClockTime(Some(0)); + } + + player.seek_simple( + gstreamer::SeekFlags::FLUSH, + gstreamer::format::GenericFormattedValue::from_time(time) + )?; + } + } + "p" => { + player.set_state(gstreamer::State::Playing).into_result()?; + + // To actually start playing again + if let Some(time) = player + .query_position::() { + player.seek_simple( + gstreamer::SeekFlags::FLUSH, + gstreamer::format::GenericFormattedValue::from_time(time) + )?; + } + } + "a" => { + player.set_state(gstreamer::State::Paused).into_result()?; + } + "m" => { + player.set_property("volume", &0.0)?; + } + "u" => { + player.set_property("volume", &1.0)?; + } + "xy" => { + if let Some(ref renderer) = renderer { + let xsize = stdin.read_line()?; + let ysize = stdin.read_line()?; + + let xsize = xsize.unwrap_or(String::from("0")).parse::()?; + let ysize = ysize.unwrap_or(String::from("0")).parse::()?; + + let mut renderer = renderer + .write() + .map_err(|_| format_err!("Renderer RwLock failed!"))?; + + renderer.set_size(xsize, ysize)?; + } + } + _ => {} + } + } +} + +// #[cfg(feature = "video")] +pub fn audio_preview(path: &String, + autoplay: bool, + mute: bool) + -> MResult<()> { + let (player, _) = make_gstreamer()?; + + let uri = format!("file://{}", &path); + + player.set_property("uri", &uri)?; + let p = player.clone(); + + // Since events don't work with audio files... + std::thread::spawn(move || -> MResult<()> { + let mut last_pos = None; + let sleep_duration = std::time::Duration::from_millis(50); + let mut stdout = std::io::stdout(); + loop { + std::thread::sleep(sleep_duration); + + let position = p.query_position::() + .map(|p| p.seconds().unwrap_or(0)) + .unwrap_or(0); + + let duration = p.query_duration::() + .map(|d| d.seconds().unwrap_or(0)) + .unwrap_or(0); + + // Just redo loop until position changes + if last_pos == Some(position) { + continue + } + + last_pos = Some(position); + + // MediaView needs empty line as separator + writeln!(stdout, "")?; + // Send position and duration + writeln!(stdout, "{}", position)?; + writeln!(stdout, "{}", duration)?; + stdout.flush()?; + } + + }); + + if mute == true || autoplay == false{ + player.set_property("volume", &0.0)?; + } else { + player.set_state(gstreamer::State::Playing).into_result()?; + } + + read_keys(player, None)?; + + Ok(()) +} + +// #[cfg(feature = "video")] +pub fn make_gstreamer() -> MResult<(gstreamer::Element, + gstreamer_app::AppSink)> { + gstreamer::init()?; + + let player = gstreamer::ElementFactory::make("playbin", None) + .ok_or(format_err!("Can't create playbin"))?; + + let videorate = gstreamer::ElementFactory::make("videorate", None) + .ok_or(format_err!("Can't create videorate element"))?; + + let pnmenc = gstreamer::ElementFactory::make("pnmenc", None) + .ok_or(format_err!("Can't create PNM-encoder"))?; + + let sink = gstreamer::ElementFactory::make("appsink", None) + .ok_or(format_err!("Can't create appsink"))?; + + let appsink = sink.clone() + .downcast::() + .unwrap(); + + + videorate.set_property("max-rate", &30)?; + + let elems = &[&videorate, &pnmenc, &sink]; + + let bin = gstreamer::Bin::new(None); + bin.add_many(elems)?; + gstreamer::Element::link_many(elems)?; + + // make input for bin point to first element + let sink = elems[0].get_static_pad("sink").unwrap(); + let ghost = gstreamer::GhostPad::new("sink", &sink) + .ok_or(format_err!("Can't create GhostPad"))?; + + ghost.set_active(true)?; + bin.add_pad(&ghost)?; + + player.set_property("video-sink", &bin.upcast::())?; + + Ok((player, appsink)) +} + + +struct Renderer { + xsize: usize, + ysize: usize, + #[cfg(feature = "video")] + xpos: usize, + #[cfg(feature = "video")] + ypos: usize, + last_frame: Option, + #[cfg(feature = "video")] + position: Option, + #[cfg(feature = "video")] + duration: Option +} + +impl Renderer { + fn new(xsize: usize, + ysize: usize, + xpos: usize, + ypos: usize) -> Renderer { + Renderer { + xsize, + ysize, + #[cfg(feature = "video")] + xpos, + #[cfg(feature = "video")] + ypos, + #[cfg(feature = "video")] + last_frame: None, + #[cfg(feature = "video")] + position: None, + #[cfg(feature = "video")] + duration: None + } + } + + + // #[cfg(feature = "video")] + fn set_size(&mut self, xsize: usize, ysize: usize) -> MResult<()> { + self.xsize = xsize; + self.ysize = ysize; + + if let Some(ref frame) = self.last_frame { + let pos = self.position.unwrap_or(0); + let dur = self.duration.unwrap_or(0); + + // Use send_image, because send_frame takes SampleRef + self.send_image(frame)?; + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + writeln!(stdout, "")?; + writeln!(stdout, "{}", pos)?; + writeln!(stdout, "{}", dur)?; + } + Ok(()) + } + + fn send_image(&self, image: &DynamicImage) -> MResult<()> { + let rendered_img = self.render_image(image); + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + for line in rendered_img { + writeln!(stdout, "{}", line)?; + } + + Ok(()) + } + + // #[cfg(feature = "video")] + fn send_frame(&mut self, + frame: &gstreamer::sample::SampleRef, + position: u64, + duration: u64, + raw: bool) + -> MResult<()> { + let buffer = frame.get_buffer() + .ok_or(format_err!("Couldn't get buffer from frame!"))?; + let map = buffer.map_readable() + .ok_or(format_err!("Couldn't get buffer from frame!"))?; + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + + if !raw { + let img = image::load_from_memory_with_format(&map, + image::ImageFormat::PNM)?; + let rendered_img = self.render_image(&img); + + self.last_frame = Some(img); + self.position = Some(position as usize); + self.duration = Some(duration as usize); + + for line in rendered_img { + writeln!(stdout, "{}", line)?; + } + } else { + stdout.write_all(map.as_slice())?; + + // Add newline after frame data + write!(stdout, "\n")?; + } + + // Empty line means end of frame + writeln!(stdout, "")?; + + // Send position and duration + writeln!(stdout, "{}", position)?; + writeln!(stdout, "{}", duration)?; + + #[cfg(feature = "video")] + match raw { + true => { + writeln!(stdout, "{}", self.xpos)?; + writeln!(stdout, "{}", self.ypos)?; + } + _ => {} + } + + Ok(()) + } + + pub fn render_image(&self, image: &DynamicImage) -> Vec { + let (xsize, ysize) = self.max_size(&image); + + let img = image.resize_exact(xsize as u32, + ysize as u32, + FilterType::Nearest).to_rgba(); + + + let rows = img.pixels() + .collect::>() + .chunks(xsize as usize) + .map(|line| line.to_vec()) + .collect::>>(); + + rows.chunks(2) + .map(|rows| { + rows[0] + .iter() + .zip(rows[1].iter()) + .map(|(upper, lower)| { + let upper_color = upper.to_rgb(); + let lower_color = lower.to_rgb(); + + format!("{}{}▀{}", + Fg(Rgb(upper_color[0], upper_color[1], upper_color[2])), + Bg(Rgb(lower_color[0], lower_color[1], lower_color[2])), + termion::style::Reset + ) + }).collect() + }).collect() + } + + pub fn max_size(&self, image: &DynamicImage) -> (usize, usize) + { + let xsize = self.xsize; + let ysize = self.ysize; + let img_xsize = image.width(); + let img_ysize = image.height(); + let img_ratio = img_xsize as f32 / img_ysize as f32; + + let mut new_x = xsize; + let mut new_y; + + new_y = if img_ratio < 1 as f32 { + (xsize as f32 * img_ratio) as usize + } else { + (xsize as f32 / img_ratio) as usize + }; + + // Multiply by two because of half-block + if new_y > ysize*2 { + new_y = self.ysize * 2; + + new_x = if img_ratio < 1 as f32 { + (ysize as f32 / img_ratio) as usize * 2 + } else { + (ysize as f32 * img_ratio) as usize * 2 + }; + } + + // To make half-block encoding easier, y should be divisible by 2 + if new_y as u32 % 2 == 1 { + new_y += 1; + } + + + (new_x as usize, new_y as usize) + } +} diff --git a/src/config.rs b/src/config.rs index 24bc995..e969f1a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -77,6 +77,7 @@ pub struct Config { pub icons: bool, pub media_autoplay: bool, pub media_mute: bool, + pub media_previewer: String } @@ -95,7 +96,8 @@ impl Config { cd_cmd: "find -type d | fzf".to_string(), icons: false, media_autoplay: false, - media_mute: false + media_mute: false, + media_previewer: "hunter-media".to_string() } } @@ -128,6 +130,10 @@ impl Config { Ok(("media_autoplay", "off")) => { config.media_autoplay = false; }, Ok(("media_mute", "on")) => { config.media_mute = true; }, Ok(("media_mute", "off")) => { config.media_mute = false; }, + Ok(("media_previewer", cmd)) => { + let cmd = cmd.to_string(); + config.select_cmd = cmd; + } _ => { HError::config_error::(line.to_string()).log(); } } config diff --git a/src/fail.rs b/src/fail.rs index 0102fa0..b3351ba 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use crate::foldview::LogEntry; +use crate::mediaview::MediaError; pub type HResult = Result; @@ -101,6 +102,8 @@ pub enum HError { UTF8ParseError(std::str::Utf8Error), #[fail(display = "Failed to parse integer!")] ParseIntError(std::num::ParseIntError), + #[fail(display = "{}", _0)] + Media(MediaError) } impl HError { diff --git a/src/files.rs b/src/files.rs index 44f5470..a4bbbcc 100644 --- a/src/files.rs +++ b/src/files.rs @@ -424,9 +424,9 @@ impl Files { file.path = new_path.into(); file.reload_meta()?; }, - DebouncedEvent::Error(err, path) => { - dbg!(err); - dbg!(path); + DebouncedEvent::Error(err, _path) => { + // Never seen this happen. Should reload affected dirs + HError::log::<()>(&format!("{}", err))?; }, _ => {}, } diff --git a/src/imgview.rs b/src/imgview.rs index d84fc03..93e1832 100644 --- a/src/imgview.rs +++ b/src/imgview.rs @@ -4,6 +4,8 @@ use crate::fail::HResult; use std::path::{Path, PathBuf}; +use crate::mediaview::MediaError; + impl std::cmp::PartialEq for ImgView { fn eq(&self, other: &Self) -> bool { self.core == other.core && @@ -31,18 +33,34 @@ impl ImgView { pub fn encode_file(&mut self) -> HResult<()> { let (xsize, ysize) = self.core.coordinates.size_u(); + let (xpos, ypos) = self.core.coordinates.position_u(); let file = &self.file; + let media_previewer = self.core.config().media_previewer; - let output = std::process::Command::new("preview-gen") + let output = std::process::Command::new(&media_previewer) .arg(format!("{}", (xsize))) .arg(format!("{}", (ysize+1))) + .arg(format!("{}", xpos)) + .arg(format!("{}", ypos)) .arg("image") .arg(format!("true")) .arg(format!("true")) .arg(file.to_string_lossy().to_string()) - .output()? + .output() + .map_err(|e| { + let msg = format!("Couldn't run {}{}{}! Error: {:?}", + crate::term::color_red(), + media_previewer, + crate::term::normal_color(), + &e.kind()); + + self.core.show_status(&msg).ok(); + + MediaError::NoPreviewer(msg) + })? .stdout; + let output = std::str::from_utf8(&output)?; let output = output.lines() .map(|l| l.to_string()) diff --git a/src/main.rs b/src/main.rs index bc949fe..57d2dbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,10 +59,7 @@ mod icon; mod quick_actions; mod trait_ext; mod config_installer; - -#[cfg(feature = "img")] mod imgview; -#[cfg(feature = "video")] mod mediaview; diff --git a/src/mediaview.rs b/src/mediaview.rs index f502591..2f9153c 100644 --- a/src/mediaview.rs +++ b/src/mediaview.rs @@ -1,5 +1,6 @@ use lazy_static; use termion::event::Key; +use failure::{self, Fail}; use crate::widget::{Widget, WidgetCore}; use crate::coordinates::Coordinates; @@ -14,6 +15,18 @@ use std::sync::{Arc, Mutex, RwLock, use std::io::{BufRead, BufReader, Write}; use std::process::Child; +#[derive(Fail, Debug, Clone)] +pub enum MediaError { + #[fail(display = "{}", _0)] + NoPreviewer(String) +} + +impl From for HError { + fn from(e: MediaError) -> HError { + HError::Media(e) + } +} + impl std::cmp::PartialEq for MediaView { fn eq(&self, other: &Self) -> bool { self.core == other.core @@ -61,8 +74,23 @@ impl MediaType { impl MediaView { pub fn new_from_file(core: WidgetCore, file: &Path, - media_type: MediaType) -> MediaView { + media_type: MediaType) -> HResult { + // Check if previewer is present, or bail out to show message + let media_previewer = core.config().media_previewer; + if crate::minibuffer::find_bins(&media_previewer).is_err() { + let msg = format!("Couldn't find previewer: {}{}{}!", + crate::term::color_red(), + media_previewer, + crate::term::normal_color()); + + + core.show_status(&msg).log(); + + return Err(MediaError::NoPreviewer(msg))?; + } + let (xsize, ysize) = core.coordinates.size_u(); + let (xpos, ypos) = core.coordinates.position_u(); let (tx_cmd, rx_cmd) = channel(); let imgview = ImgView { @@ -71,6 +99,7 @@ impl MediaView { file: file.to_path_buf() }; + // Stuff that gets moved into the closure let imgview = Arc::new(Mutex::new(imgview)); let thread_imgview = imgview.clone(); @@ -82,7 +111,8 @@ impl MediaView { let process = Arc::new(Mutex::new(None)); let cprocess = process.clone(); let ctype = media_type.clone(); - + let ccore = core.clone(); + let media_previewer = core.config().media_previewer; let run_preview = Box::new(move | auto, mute, @@ -93,18 +123,32 @@ impl MediaView { return Ok(()); } - let mut previewer = std::process::Command::new("preview-gen") + + let mut previewer = std::process::Command::new(&media_previewer) .arg(format!("{}", (xsize))) // Leave space for position/seek bar .arg(format!("{}", (ysize-1))) + .arg(format!("{}", xpos)) + .arg(format!("{}", ypos)) .arg(format!("{}", ctype.to_str())) .arg(format!("{}", auto)) .arg(format!("{}", mute)) .arg(&path) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::inherit()) - .spawn()?; + .stderr(std::process::Stdio::piped()) + .spawn() + .map_err(|e| { + let msg = format!("Couldn't run {}{}{}! Error: {:?}", + crate::term::color_red(), + media_previewer, + crate::term::normal_color(), + &e.kind()); + + ccore.show_status(&msg).log(); + + MediaError::NoPreviewer(msg) + })?; let mut stdout = BufReader::new(previewer.stdout.take()?); let mut stdin = previewer.stdin.take()?; @@ -174,7 +218,7 @@ impl MediaView { }); - MediaView { + Ok(MediaView { core: core.clone(), imgview: imgview, file: file.to_path_buf(), @@ -186,7 +230,7 @@ impl MediaView { stale: stale, process: process, preview_runner: Some(run_preview) - } + }) } pub fn start_video(&mut self) -> HResult<()> { @@ -308,7 +352,7 @@ impl MediaView { // Since GStreamer sucks, just create a new instace let mut view = MediaView::new_from_file(self.core.clone(), &self.file.clone(), - self.media_type.clone()); + self.media_type.clone())?; // Insert buffer to prevent flicker let buffer = self.imgview.lock()?.buffer.clone(); diff --git a/src/preview.rs b/src/preview.rs index 3d7a022..ac5ee60 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -12,10 +12,7 @@ use crate::widget::{Widget, WidgetCore}; use crate::coordinates::Coordinates; use crate::fail::{HResult, HError, ErrorLog}; use crate::dirty::Dirtyable; - -#[cfg(feature = "img")] use crate::imgview::ImgView; -#[cfg(feature = "video")] use crate::mediaview::MediaView; @@ -201,9 +198,7 @@ impl PartialEq for Previewer { enum PreviewWidget { FileList(ListView), TextView(TextView), - #[cfg(feature = "img")] ImgView(ImgView), - #[cfg(feature = "video")] MediaView(MediaView) } @@ -370,26 +365,23 @@ impl Previewer { let is_gif = mime.subtype() == "gif"; match mime_type { - #[cfg(feature = "video")] _ if mime_type == "video" || is_gif => { let media_type = crate::mediaview::MediaType::Video; let mediaview = MediaView::new_from_file(core.clone(), &file.path, - media_type); + media_type)?; return Ok(PreviewWidget::MediaView(mediaview)); } - #[cfg(feature = "img")] "image" => { let imgview = ImgView::new_from_file(core.clone(), &file.path())?; return Ok(PreviewWidget::ImgView(imgview)); } - #[cfg(feature = "video")] "audio" => { let media_type = crate::mediaview::MediaType::Audio; let mediaview = MediaView::new_from_file(core.clone(), &file.path, - media_type); + media_type)?; return Ok(PreviewWidget::MediaView(mediaview)); } "text" if mime.subtype() == "plain" => { @@ -578,9 +570,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.get_core(), PreviewWidget::TextView(widget) => widget.get_core(), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.get_core(), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.get_core() } } @@ -588,9 +578,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.get_core_mut(), PreviewWidget::TextView(widget) => widget.get_core_mut(), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.get_core_mut(), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.get_core_mut() } } @@ -598,9 +586,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.set_coordinates(coordinates), PreviewWidget::TextView(widget) => widget.set_coordinates(coordinates), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.set_coordinates(coordinates), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.set_coordinates(coordinates), } } @@ -608,9 +594,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.refresh(), PreviewWidget::TextView(widget) => widget.refresh(), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.refresh(), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.refresh() } } @@ -618,9 +602,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.get_drawlist(), PreviewWidget::TextView(widget) => widget.get_drawlist(), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.get_drawlist(), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.get_drawlist() } } @@ -629,9 +611,7 @@ impl Widget for PreviewWidget { match self { PreviewWidget::FileList(widget) => widget.on_key(key), PreviewWidget::TextView(widget) => widget.on_key(key), - #[cfg(feature = "img")] PreviewWidget::ImgView(widget) => widget.on_key(key), - #[cfg(feature = "video")] PreviewWidget::MediaView(widget) => widget.on_key(key) } } diff --git a/src/widget.rs b/src/widget.rs index 6cb19eb..8e2019d 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -192,9 +192,16 @@ impl WidgetCore { } pub fn config(&self) -> Config { - self.config.read().unwrap().get() - .map(|config| config.clone()) - .unwrap_or(Config::new()) + self.get_conf() + .unwrap_or_else(|_| Config::new()) + } + + fn get_conf(&self) -> HResult { + let conf = self.config + .read()? + .get()? + .clone(); + Ok(conf) } }