summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-06-29 23:33:25 +0200
committerrabite <rabite@posteo.de>2019-06-29 23:33:25 +0200
commit11f5bd081bf303e96e79a9c3bd2c87855f1584e0 (patch)
tree6eabc5fc758ea220852b256a451db14fb751b1d3
parentcd01a21f687a0d17984ee982e2e899634879db9a (diff)
modularize media preview generator into own workspace
-rw-r--r--Cargo.lock14
-rw-r--r--Cargo.toml29
-rw-r--r--hunter-media/.gitignore1
-rw-r--r--hunter-media/Cargo.lock6
-rw-r--r--hunter-media/Cargo.toml25
-rw-r--r--hunter-media/src/main.rs587
-rw-r--r--src/config.rs8
-rw-r--r--src/fail.rs3
-rw-r--r--src/files.rs6
-rw-r--r--src/imgview.rs22
-rw-r--r--src/main.rs3
-rw-r--r--src/mediaview.rs60
-rw-r--r--src/preview.rs24
-rw-r--r--src/widget.rs13
14 files changed, 737 insertions, 64 deletions
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)",
@@ -475,6 +472,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
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<T> = Result<T, Error>;
+
+fn main() -> MResult<()> {
+ let args = std::env::args().collect::<Vec<String>>();
+ let xsize: usize = args.get(1)
+ .expect("Provide xsize")
+ .parse::<usize>()
+ .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::<String>()
+ .unwrap();
+ let autoplay = args.get(6)
+ .expect("Autoplay?")
+ .parse::<bool>()
+ .unwrap();
+ let mute = args.get(7)
+ .expect("Muted?")
+ .parse::<bool>()
+ .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::<gstreamer::ClockTime>()
+ .map(|p| p.seconds().unwrap_or(0))
+ .unwrap_or(0);
+
+ let duration = p.query_duration::<gstreamer::ClockTime>()
+ .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<Arc<RwLock<Renderer>>>) -> 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::<gstreamer::ClockTime>() {
+ time += seek_time;
+
+ player.seek_simple(
+ gstreamer::SeekFlags::FLUSH,
+ gstreamer::format::GenericFormattedValue::from_time(time)
+ )?;
+ }
+ },
+ "<" => {
+ if let Some(mut time) = player
+ .query_position::<gstreamer::ClockTime>() {
+ 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::<gstreamer::ClockTime>() {
+ 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::<usize>()?;
+ let ysize = ysize.unwrap_or(String::from("0")).parse::<usize>()?;
+
+ 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::<gstreamer::ClockTime>()
+ .map(|p| p.seconds().unwrap_or(0))
+ .unwrap_or(0);
+
+ let duration = p.query_duration::<gstreamer::ClockTime>()
+ .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::<gstreamer_app::AppSink>()
+ .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::<gstreamer::Element>())?;
+
+ Ok((player, appsink))
+}
+
+
+struct Renderer {
+ xsize: usize,
+ ysize: usize,
+ #[cfg(feature = "video")]
+ xpos: usize,
+ #[cfg(feature = "video")]
+ ypos: usize,
+ last_frame: Option<DynamicImage>,
+ #[cfg(feature = "video")]
+ position: Option<usize>,
+ #[cfg(feature = "video")]
+ duration: Option<usize>
+}
+
+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<String> {
+ 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::<Vec<_>>()
+ .chunks(xsize as usize)
+ .map(|line| line.to_vec())
+ .collect::<Vec<Vec<_>>>();
+
+ 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::<Config>(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<T> = Result<T, HError>;
@@ -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<MediaError> 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<MediaView> {
+ // 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 {
});
-