summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-05-21 20:50:54 +0200
committerrabite <rabite@posteo.de>2019-05-21 20:52:04 +0200
commite8d9c6ad1f82d6b0bbf3cf9449ce53dd525cb77a (patch)
treee9615c4cf832f1a17a640a229c5dd79710ad601e /src
parent6f8a6366039282cfce272a4a430590de83f48045 (diff)
merge in media-preview stuff
Squashed commit of the following: commit 4087cee2f2a72bc2d3e44832809cbe99d25a3309 Author: rabite <rabite@posteo.de> Date: Tue May 21 20:40:27 2019 +0200 rename to preview-gen commit 275fd420a01c24393ae9cc07287204fde309130a Author: rabite <rabite@posteo.de> Date: Tue May 21 20:30:50 2019 +0200 update Cargo.toml and README commit bf4c3fec0f8e0ff4628153297ac64fe165b4e443 Author: rabite <rabite@posteo.de> Date: Tue May 21 20:30:33 2019 +0200 add config options for media commit 3bf681cd078177d634507c51d04a45bdae184f1b Author: rabite <rabite@posteo.de> Date: Tue May 21 13:46:00 2019 +0200 always use unicode icons for play/pause/mute commit ed2a0b31d1911e4d42086699ca1db636c327bacf Author: rabite <rabite@posteo.de> Date: Tue May 21 13:04:11 2019 +0200 rename to mediaview and some fixups commit bda3f8f83bec07c3f52d06cf5f8eaad7fdbd85a0 Author: rabite <rabite@posteo.de> Date: Tue May 21 11:44:52 2019 +0200 fix warnings commit 9c917903cfa958b225f268ed4fb9f55f27162be0 Author: rabite <rabite@posteo.de> Date: Tue May 21 01:10:58 2019 +0200 play audio through viedo widget commit 9fe208b213710fae889f7701bdb3bf96d599c9a8 Author: rabite <rabite@posteo.de> Date: Tue May 21 00:15:12 2019 +0200 fixed play when autoplay is off commit d4036d52864d0a4e08f7da2cf6f7419ef070c1a9 Author: rabite <rabite@posteo.de> Date: Mon May 20 13:15:44 2019 +0200 add icons for play/pause/mute commit 29f0d203cfd7501b50d80501de146da9d89388bb Author: rabite <rabite@posteo.de> Date: Mon May 20 12:26:53 2019 +0200 add basic gif support commit 8d28e4c23a91b0ef83739554c3a2f6671a9ff6f5 Author: rabite <rabite@posteo.de> Date: Mon May 20 11:43:57 2019 +0200 fixed play after pause commit 142017df78de77bcfffd8f3dd9612a06927e183b Author: rabite <rabite@posteo.de> Date: Sun May 19 14:22:16 2019 +0200 change to stale on drop commit c0026c7f56f6a841df18e41bd1c9b33543e26cfb Author: rabite <rabite@posteo.de> Date: Sat May 18 23:46:27 2019 +0200 handle errors in gstreamer closure commit ef37f872c54b9d21ed5505c2edb480c793afaef9 Author: rabite <rabite@posteo.de> Date: Sat May 18 23:40:24 2019 +0200 add image-only mode to preview-gen commit 15752464563463c6ecf892c1d8a14651bf32e1e5 Author: rabite <rabite@posteo.de> Date: Sat May 18 23:28:33 2019 +0200 add copyright note commit 4c9f08fc4de5d3bffefd42284b60aa81cfbae9f7 Author: rabite <rabite@posteo.de> Date: Sat May 18 23:27:30 2019 +0200 make image/video previews optional commit 9c2c2db2b20ecd4c30acbb1c77ad18c71f528c4c Author: rabite <rabite@posteo.de> Date: Sat May 18 22:13:25 2019 +0200 remove blank space under video commit f7056d70fbe9147b3b95d7c10950653eadbb4f48 Author: rabite <rabite@posteo.de> Date: Sat May 18 22:06:49 2019 +0200 remove dbg commit cce820657ff1258cdb78d455dd3cf04564450cea Author: rabite <rabite@posteo.de> Date: Sat May 18 21:50:25 2019 +0200 added error handling to preview-gen commit 80bbe15bacb99d1f4f97504a5d10ecf59544993f Author: rabite <rabite@posteo.de> Date: Sat May 18 21:04:31 2019 +0200 add time and visual seek bars to videoview commit 4349945cc5549334f246be64bc7c8e2db43f9150 Author: rabite <rabite@posteo.de> Date: Thu May 16 22:39:19 2019 +0200 fix sneaky process commit 373128b7096d4de29924617e549a613a3aeea5b2 Author: rabite <rabite@posteo.de> Date: Thu May 16 22:07:27 2019 +0200 fix out of controll processes commit e061e065e913ab3923ffac1079a9f0a2be0df532 Author: rabite <rabite@posteo.de> Date: Thu May 16 20:51:56 2019 +0200 add media_preview commit f68c754895a9718d1a962980de9fbf02e5cb48cd Author: rabite <rabite@posteo.de> Date: Thu May 16 19:37:04 2019 +0200 beta image/video previews commit 9a5460e553dd26ebccffaf819c64a8dc6ba74818 Author: rabite <rabite@posteo.de> Date: Tue May 14 20:36:25 2019 +0200 external img preview commit 3c4edfcb7611b36f6e537c73743e0b6bd269037b Author: rabite <rabite@posteo.de> Date: Tue May 14 20:35:12 2019 +0200 video preview
Diffstat (limited to 'src')
-rw-r--r--src/config.rs12
-rw-r--r--src/fail.rs19
-rw-r--r--src/file_browser.rs7
-rw-r--r--src/files.rs11
-rw-r--r--src/imgview.rs86
-rw-r--r--src/main.rs7
-rw-r--r--src/mediaview.rs442
-rw-r--r--src/preview-gen.rs412
-rw-r--r--src/preview.rs162
-rw-r--r--src/widget.rs7
10 files changed, 1116 insertions, 49 deletions
diff --git a/src/config.rs b/src/config.rs
index 496ce9b..8938025 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,7 +7,9 @@ pub struct Config {
pub show_hidden: bool,
pub select_cmd: String,
pub cd_cmd: String,
- pub icons: bool
+ pub icons: bool,
+ pub media_autoplay: bool,
+ pub media_mute: bool,
}
@@ -18,7 +20,9 @@ impl Config {
show_hidden: false,
select_cmd: "find -type f | fzf -m".to_string(),
cd_cmd: "find -type d | fzf".to_string(),
- icons: false
+ icons: false,
+ media_autoplay: false,
+ media_mute: false
}
}
@@ -47,6 +51,10 @@ impl Config {
let cmd = cmd.to_string();
config.cd_cmd = cmd;
}
+ Ok(("media_autoplay", "on")) => { config.media_autoplay = true; },
+ Ok(("media_autoplay", "off")) => { config.media_autoplay = false; },
+ Ok(("media_mute", "on")) => { config.media_mute = true; },
+ Ok(("media_mute", "off")) => { config.media_mute = false; },
_ => { HError::config_error::<Config>(line.to_string()).log(); }
}
config
diff --git a/src/fail.rs b/src/fail.rs
index 99d98ec..0590ce5 100644
--- a/src/fail.rs
+++ b/src/fail.rs
@@ -97,6 +97,10 @@ pub enum HError {
ConfigLineError(String),
#[fail(display = "New input in Minibuffer")]
MiniBufferInputUpdated(String),
+ #[fail(display = "Failed to parse into UTF8")]
+ UTF8ParseError(std::str::Utf8Error),
+ #[fail(display = "Failed to parse integer!")]
+ ParseIntError(std::num::ParseIntError),
}
impl HError {
@@ -308,3 +312,18 @@ impl From<async_value::AError> for HError {
err
}
}
+
+impl From<std::str::Utf8Error> for HError {
+ fn from(error: std::str::Utf8Error) -> Self {
+ let err = HError::UTF8ParseError(error);
+ err
+ }
+}
+
+
+impl From<std::num::ParseIntError> for HError {
+ fn from(error: std::num::ParseIntError) -> Self {
+ let err = HError::ParseIntError(error);
+ err
+ }
+}
diff --git a/src/file_browser.rs b/src/file_browser.rs
index d76c6cb..45c99df 100644
--- a/src/file_browser.rs
+++ b/src/file_browser.rs
@@ -1280,7 +1280,12 @@ impl Widget for FileBrowser {
Key::Char('g') => self.show_log()?,
Key::Char('z') => self.run_subshell()?,
Key::Char('c') => self.toggle_colums(),
- _ => { self.main_widget_mut()?.on_key(key)?; },
+ _ => {
+ let main_widget_result = self.main_widget_mut()?.on_key(key);
+ if let Err(HError::WidgetUndefinedKeyError{..}) = main_widget_result {
+ self.preview_widget_mut()?.on_key(key)?;
+ }
+ },
}
if !self.columns.zoom_active { self.update_preview().log(); }
Ok(())
diff --git a/src/files.rs b/src/files.rs
index 17b2612..25e3393 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -18,6 +18,8 @@ use failure::Error;
use notify::DebouncedEvent;
use rayon::{ThreadPool, ThreadPoolBuilder};
use alphanumeric_sort::compare_str;
+use mime_guess;
+
use pathbuftools::PathBufTools;
use async_value::{Async, Stale};
@@ -830,9 +832,12 @@ impl File {
Ok((size, unit))
}
- // pub fn get_mime(&self) -> String {
- // tree_magic::from_filepath(&self.path)
- // }
+ pub fn get_mime(&self) -> Option<mime_guess::Mime> {
+ if let Some(ext) = self.path.extension() {
+ let mime = mime_guess::get_mime_type(&ext.to_string_lossy());
+ Some(mime)
+ } else { None }
+ }
pub fn is_text(&self) -> bool {
tree_magic::match_filepath("text/plain", &self.path)
diff --git a/src/imgview.rs b/src/imgview.rs
new file mode 100644
index 0000000..c52abee
--- /dev/null
+++ b/src/imgview.rs
@@ -0,0 +1,86 @@
+use crate::widget::{Widget, WidgetCore};
+use crate::fail::HResult;
+
+use std::path::{Path, PathBuf};
+
+impl std::cmp::PartialEq for ImgView {
+ fn eq(&self, other: &Self) -> bool {
+ self.core == other.core &&
+ self.buffer == other.buffer
+ }
+}
+
+pub struct ImgView {
+ pub core: WidgetCore,
+ pub buffer: Vec<String>,
+ pub file: PathBuf
+}
+
+impl ImgView {
+ pub fn new_from_file(core: WidgetCore, file: &Path) -> HResult<ImgView> {
+ let (xsize, ysize) = core.coordinates.size_u();
+
+ let output = std::process::Command::new("preview-gen")
+ .arg(format!("{}", (xsize)))
+ .arg(format!("{}", (ysize+1)))
+ .arg("image")
+ .arg(format!("true"))
+ .arg(format!("true"))
+ .arg(file.to_string_lossy().to_string())
+ .output()?
+ .stdout;
+
+ let output = std::str::from_utf8(&output)?;
+ let output = output.lines()
+ .map(|l| l.to_string())
+ .collect();
+
+ Ok(ImgView {
+ core: core,
+ buffer: output,
+ file: file.to_path_buf()
+ })
+ }
+
+ pub fn set_image_data(&mut self, img_data: Vec<String>) {
+ self.buffer = img_data;
+ }
+
+ pub fn lines(&self) -> usize {
+ self.buffer.len()
+ }
+}
+
+
+impl Widget for ImgView {
+ fn get_core(&self) -> HResult<&WidgetCore> {
+ Ok(&self.core)
+ }
+
+ fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
+ Ok(&mut self.core)
+ }
+
+ fn refresh(&mut self) -> HResult<()> {
+
+ Ok(())
+ }
+
+ fn get_drawlist(&self) -> HResult<String> {
+ let (xpos, ypos) = self.core.coordinates.position_u();
+
+ let mut draw = self.buffer
+ .iter()
+ .enumerate()
+ .fold(String::new(), |mut draw, (pos, line)| {
+ draw += &format!("{}", crate::term::goto_xy_u(xpos+1,
+ ypos + pos));
+ draw += line;
+ draw
+ });
+
+ draw += &format!("{}", termion::style::Reset);
+
+ Ok(draw)
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 4df39ab..bd94046 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,6 +21,8 @@ extern crate parse_ansi;
extern crate signal_notify;
extern crate tree_magic;
extern crate systemstat;
+extern crate mime_guess;
+
extern crate osstrtools;
extern crate pathbuftools;
extern crate async_value;
@@ -52,6 +54,11 @@ mod config;
mod stats;
mod icon;
+#[cfg(feature = "img")]
+mod imgview;
+#[cfg(feature = "video")]
+mod mediaview;
+
diff --git a/src/mediaview.rs b/src/mediaview.rs
new file mode 100644
index 0000000..ee84d69
--- /dev/null
+++ b/src/mediaview.rs
@@ -0,0 +1,442 @@
+use lazy_static;
+use termion::event::{Event, Key};
+
+use crate::widget::{Widget, WidgetCore};
+use crate::async_value::Stale;
+use crate::fail::{HResult, HError, ErrorLog};
+use crate::imgview::ImgView;
+
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, Mutex, RwLock,
+ mpsc::{channel, Sender}};
+
+use std::io::{BufRead, BufReader, Write};
+use std::process::Child;
+
+impl std::cmp::PartialEq for MediaView {
+ fn eq(&self, other: &Self) -> bool {
+ self.core == other.core
+ }
+}
+
+lazy_static! {
+ static ref MUTE: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
+ static ref AUTOPLAY: Arc<RwLock<bool>> = Arc::new(RwLock::new(true));
+}
+
+pub struct MediaView {
+ core: WidgetCore,
+ imgview: Arc<Mutex<ImgView>>,
+ file: PathBuf,
+ controller: Sender<String>,
+ paused: bool,
+ media_type: MediaType,
+ position: Arc<Mutex<usize>>,
+ duration: Arc<Mutex<usize>>,
+ stale: Stale,
+ process: Arc<Mutex<Option<Child>>>,
+ preview_runner: Option<Box<dyn FnOnce(bool,
+ bool,
+ Arc<Mutex<usize>>,
+ Arc<Mutex<usize>>)
+ -> HResult<()> + Send + 'static>>
+}
+
+#[derive(Clone,Debug)]
+pub enum MediaType {
+ Video,
+ Audio
+}
+
+impl MediaType {
+ pub fn to_str(&self) -> &str {
+ match self {
+ MediaType::Video => "video",
+ MediaType::Audio => "audio"
+ }
+ }
+}
+
+impl MediaView {
+ pub fn new_from_file(core: WidgetCore,
+ file: &Path,
+ media_type: MediaType) -> MediaView {
+ let (xsize, ysize) = core.coordinates.size_u();
+ let (tx_cmd, rx_cmd) = channel();
+
+ let imgview = ImgView {
+ core: core.clone(),
+ buffer: vec![],
+ file: file.to_path_buf()
+ };
+
+ let imgview = Arc::new(Mutex::new(imgview));
+ let thread_imgview = imgview.clone();
+
+ let path = file.to_string_lossy().to_string();
+ let sender = core.get_sender();
+ let stale = Stale::new();
+ let tstale = stale.clone();
+ let rx_cmd = Arc::new(Mutex::new(rx_cmd));
+ let process = Arc::new(Mutex::new(None));
+ let cprocess = process.clone();
+ let ctype = media_type.clone();
+
+
+ let run_preview = Box::new(move | auto,
+ mute,
+ position: Arc<Mutex<usize>>,
+ duration: Arc<Mutex<usize>>| -> HResult<()> {
+ loop {
+ if tstale.is_stale()? {
+ return Ok(());
+ }
+
+ let mut previewer = std::process::Command::new("preview-gen")
+ .arg(format!("{}", (xsize)))
+ // Leave space for position/seek bar
+ .arg(format!("{}", (ysize-3)))
+ .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()?;
+
+ let mut stdout = BufReader::new(previewer.stdout.take()?);
+ let mut stdin = previewer.stdin.take()?;
+
+ cprocess.lock().map(|mut p| *p = Some(previewer))?;
+
+ let mut frame = vec![];
+ let newline = String::from("\n");
+ let mut line_buf = String::new();
+ let rx_cmd = rx_cmd.clone();
+
+ std::thread::spawn(move || -> HResult<()> {
+ for cmd in rx_cmd.lock()?.iter() {
+ write!(stdin, "{}", cmd)?;
+ write!(stdin, "\n")?;
+ stdin.flush()?;
+ }
+ Ok(())
+ });
+
+ loop {
+ // Check if preview-gen finished and break out of loop to restart
+ if let Ok(Some(code)) = cprocess.lock()?
+ .as_mut()?
+ .try_wait() {
+ if code.success() {
+ break;
+ } else { return Ok(()); }
+ }
+
+
+ stdout.read_line(&mut line_buf)?;
+
+
+ // Newline means frame is complete
+ if line_buf == newline {
+ line_buf.clear();
+ stdout.read_line(&mut line_buf)?;
+ let pos = &line_buf.trim();
+ *position.lock().unwrap() = pos
+ .parse::<usize>()?;
+
+
+ line_buf.clear();
+ stdout.read_line(&mut line_buf)?;
+ let dur = &line_buf.trim();
+ *duration.lock().unwrap() = dur
+ .parse::<usize>()?;
+
+
+ if let Ok(mut imgview) = thread_imgview.lock() {
+ imgview.set_image_data(frame);
+ sender.send(crate::widget::Events::WidgetReady)
+ .map_err(|e| HError::from(e))
+ .log();;
+ }
+
+ line_buf.clear();
+ frame = vec![];
+ continue;
+ } else {
+ frame.push(line_buf);
+ line_buf = String::new();
+ }
+ }
+ }
+ });
+
+
+ MediaView {
+ core: core.clone(),
+ imgview: imgview,
+ file: file.to_path_buf(),
+ media_type: media_type,
+ controller: tx_cmd,
+ paused: false,
+ position: Arc::new(Mutex::new(0)),
+ duration: Arc::new(Mutex::new(0)),
+ stale: stale,
+ process: process,
+ preview_runner: Some(run_preview)
+ }
+ }
+
+ pub fn start_video(&mut self) -> HResult<()> {
+ let runner = self.preview_runner.take();
+ let stale = self.stale.clone();
+ let autoplay = self.autoplay();
+ let mute = self.mute();
+ let position = self.position.clone();
+ let duration = self.duration.clone();
+
+ if runner.is_some() {
+ self.clear().log();
+ std::thread::spawn(move || -> HResult<()> {
+ let sleeptime = std::time::Duration::from_millis(50);
+ std::thread::sleep(sleeptime);
+
+ if !stale.is_stale()? {
+ runner.map(|runner| runner(autoplay,
+ mute,
+ position,
+ duration));
+ }
+ Ok(())
+ });
+ }
+ Ok(())
+ }
+
+ pub fn play(&self) -> HResult<()> {
+ Ok(self.controller.send(String::from("p"))?)
+ }
+
+ pub fn pause(&self) -> HResult<()> {
+ Ok(self.controller.send(String::from ("a"))?)
+ }
+
+ pub fn progress_bar(&self) -> HResult<String> {
+ let xsize = self.core.coordinates.xsize_u();
+
+ let position = self.position.lock()?.clone();
+ let duration = self.duration.lock()?.clone();
+
+ if duration == 0 || position == 0 {
+ Ok(format!("{:elements$}", "|", elements=xsize))
+ } else {
+ let element_percent = 100 as f32 / xsize as f32;
+ let progress_percent = position as f32 / duration as f32 * 100 as f32;
+ let element_count = progress_percent as f32 / element_percent as f32;
+
+ Ok(format!("{:|>elements$}|{: >empty$}",
+ "",
+ "",
+ empty=xsize - (element_count as usize + 1),
+ elements=element_count as usize))
+ }
+ }
+
+ pub fn progress_string(&self) -> HResult<String> {
+ let position = self.position.lock()?.clone();
+ let duration = self.duration.lock()?.clone();
+
+ let fposition = self.format_secs(position);
+ let fduration = self.format_secs(duration);
+
+ Ok(format!("{} / {}", fposition, fduration))
+ }
+
+ pub fn get_icons(&self, lines: usize) -> HResult<String> {
+ let (xpos, ypos) = self.core.coordinates.position_u();
+ let (xsize, _) = self.core.coordinates.size_u();
+
+ let mute_char = "🔇";
+ let pause_char = "⏸";
+ let play_char = "⏴";
+
+ let mut icons = String::new();
+
+ if *MUTE.read()? == true {
+ icons += &crate::term::goto_xy_u(xpos+xsize-2, ypos+lines);
+ icons += mute_char;
+ } else {
+ // Clear the mute symbol, or it doesn't go away
+ icons += &crate::term::goto_xy_u(xpos+xsize-2, ypos+lines);
+ icons += " ";
+ }
+
+ if *AUTOPLAY.read()? == true {
+ icons += &crate::term::goto_xy_u(xpos+xsize-4, ypos+lines);
+ icons += play_char;
+ } else {
+ icons += &crate::term::goto_xy_u(xpos+xsize-4, ypos+lines);
+ icons += pause_char;
+ }
+
+ Ok(icons)
+ }
+
+ pub fn format_secs(&self, secs: usize) -> String {
+ let hours = if secs >= 60*60 { (secs / 60) / 60 } else { 0 };
+ let mins = if secs >= 60 { (secs / 60) %60 } else { 0 };
+
+
+ format!("{:02}:{:02}:{:02}", hours, mins, secs % 60)
+ }
+
+ pub fn toggle_pause(&mut self) -> HResult<()> {
+ let auto = AUTOPLAY.read()?.clone();
+ let pos = self.position.lock()?.clone();
+
+ // This combination means only first frame show, since
+ // self.paused will be false, even with autoplay off
+ if pos == 0 && auto == false && self.paused == false {
+ self.toggle_autoplay();
+
+ // 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());
+
+ // Insert buffer to prevent flicker
+ let buffer = self.imgview.lock()?.buffer.clone();
+ view.imgview.lock()?.buffer = buffer;
+
+ view.start_video()?;
+ view.paused = false;
+ view.play()?;
+ std::mem::swap(self, &mut &mut view);
+
+
+ return Ok(())
+ }
+ if self.paused {
+ self.toggle_autoplay();
+ self.play()?;
+ self.paused = false;
+ } else {
+ self.pause()?;
+ self.toggle_autoplay();
+ self.paused = true;
+ }
+ Ok(())
+ }
+
+ pub fn quit(&self) -> HResult<()> {
+ Ok(self.controller.send(String::from("q"))?)
+ }
+
+ pub fn seek_forward(&self) -> HResult<()> {
+ Ok(self.controller.send(String::from(">"))?)
+ }
+
+ pub fn seek_backward(&self) -> HResult<()> {
+ Ok(self.controller.send(String::from("<"))?)
+ }
+
+ pub fn autoplay(&self) -> bool {
+ if let Ok(autoplay) = AUTOPLAY.read() {
+ return *autoplay;
+ }
+ return true;
+ }
+
+ pub fn mute(&self) -> bool {
+ if let Ok(mute) = MUTE.read() {
+ return *mute;
+ }
+ return false;
+ }
+
+ pub fn toggle_autoplay(&self) {
+ if let Ok(mut autoplay) = AUTOPLAY.write() {
+ *autoplay = !*autoplay;
+ }
+ }
+
+ pub fn toggle_mute(&self) {
+ if let Ok(mut mute) = MUTE.write() {
+ *mute = !*mute;
+ if *mute {
+ self.controller.send(String::from("m")).ok();
+ } else {
+ self.controller.send(String::from("u")).ok();
+ }
+ }
+ }
+
+ pub fn kill(&mut self) -> HResult<()> {
+ let proc = self.process.clone();
+ std::thread::spawn(move || -> HResult<()> {
+ proc.lock()?
+ .as_mut()
+ .map(|p| {
+ p.kill().map_err(|e| HError::from(e)).log();
+ p.wait().map_err(|e| HError::from(e)).log();
+ });
+ Ok(())
+ });
+ Ok(())
+ }
+}
+
+impl Widget for MediaView {
+ fn get_core(&self) -> HResult<&WidgetCore> {
+ Ok(&self.core)
+ }
+
+ fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
+ Ok(&mut self.core)
+ }
+
+ fn refresh(&mut self) -> HResult<()> {
+ self.start_video().log();
+ Ok(())
+ }
+
+ fn get_drawlist(&self) -> HResult<String> {
+ let (xpos, ypos) = self.core.coordinates.position_u();
+ let progress_str = self.progress_string()?;
+ let progress_bar = self.progress_bar()?;
+
+ let (frame, lines) = self.imgview
+ .lock()
+ .map(|img| (img.get_drawlist(), img.lines()))?;
+
+ let mut frame = frame?;
+
+ frame += &crate::term::goto_xy_u(xpos+1, ypos+lines);
+ frame += &progress_str;
+ frame += &self.get_icons(lines)?;
+ frame += &crate::term::goto_xy_u(xpos+1, ypos+lines+1);
+ frame += &progress_bar;
+
+ Ok(frame)
+ }
+
+ fn on_key(&mut self, key: Key) -> HResult<()> {
+ match key {
+ Key::Alt('>') => self.seek_forward(),
+ Key::Alt('<') => self.seek_backward(),
+ Key::Alt('m') => self.toggle_pause(),
+ Key::Alt('M') => Ok(self.toggle_mute()),
+ _ => self.bad(Event::Key(key))
+ }
+ }
+}
+
+impl Drop for MediaView {
+ fn drop(&mut self) {
+ self.stale.set_stale().ok();
+ self.kill().log();
+
+ self.clear().log();
+ }
+}
diff --git a/src/preview-gen.rs b/src/preview-gen.rs
new file mode 100644
index 0000000..6abbf80
--- /dev/null
+++ b/src/preview-gen.rs
@@ -0,0 +1,412 @@
+// Based on https://github.com/jD91mZM2/termplay
+// MIT License
+
+use image::{Pixel, FilterType, DynamicImage, GenericImageView};
+
+use termion::{color::{Bg, Fg, Rgb},
+ input::TermRead,
+ event::Key};
+
+#[cfg(feature = "video")]
+use gstreamer::{self, prelude::*};
+#[cfg(feature = "video")]
+use gstreamer_app;
+
+use failure::{Error, format_err};
+
+use rayon::prelude::*;
+
+use std::io::Write;
+
+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 preview_type = args.get(3)
+ .expect("Provide preview type")
+ .parse::<String>()
+ .unwrap();
+ let autoplay = args.get(4)
+ .expect("Autoplay?")
+ .parse::<bool>()
+ .unwrap();
+ let mute = args.get(5)
+ .expect("Muted?")
+ .parse::<bool>()
+ .unwrap();
+ let path = args.get(6).expect("Provide path");
+
+ #[cfg(feature = "video")]
+ let result =
+ match preview_type.as_ref() {
+ "video" => video_preview(path, xsize, ysize, autoplay, mute),
+ "image" => image_preview(path, xsize, ysize),
+ "audio" => audio_preview(path, autoplay, mute),
+ _ => { panic!("Available types: video/image/audio") }
+ };
+
+
+
+ #[cfg(not(feature = "video"))]
+ let result = image_preview(path, xsize, ysize);
+
+ 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 {
+ xsize,
+ ysize
+ };
+
+ renderer.send_image(img)?;
+ Ok(())
+}
+
+fn video_preview(path: &String,
+ xsize: usize,
+ ysize: usize,
+ autoplay: bool,
+ mute: bool)
+ -> MResult<()> {
+ let (player, appsink) = make_gstreamer()?;
+
+ let uri = format!("file://{}", &path);
+
+ player.set_property("uri", &uri)?;
+
+
+ let renderer = Renderer {
+ xsize,
+ ysize
+ };
+
+ 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);
+
+ match renderer.send_frame(&*sample,
+ position,
+ duration) {
+ 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
+ }
+ }
+
+ }
+ })
+ .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)?;
+
+ Ok(())
+}
+
+pub fn read_keys(player: gstreamer::Element) -> MResult<()> {
+ let seek_time = gstreamer::ClockTime::from_seconds(5);
+ for key in std::io::stdin().keys() {
+ match key {
+ Ok(Key::Char('q')) => std::process::exit(0),
+ Ok(Key::Char('>')) => {
+ 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)
+ )?;
+ }
+ },
+ Ok(Key::Char('<')) => {
+ 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)
+ )?;
+ }
+ }
+ Ok(Key::Char('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)
+ )?;
+ }
+ }
+ Ok(Key::Char('a')) => {
+ player.set_state(gstreamer::State::Paused).into_result()?;
+ }
+ Ok(Key::Char('m')) => {
+ player.set_property("volume", &0.0)?;
+ }
+ Ok(Key::Char('u')) => {
+ player.set_property("volume", &1.0)?;
+ }
+
+
+ _ => {}
+