modularize media preview generator into own workspace

This commit is contained in:
rabite 2019-06-29 23:33:25 +02:00
parent cd01a21f68
commit 11f5bd081b
14 changed files with 737 additions and 64 deletions

14
Cargo.lock generated
View File

@ -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"

View File

@ -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]

1
hunter-media/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

6
hunter-media/Cargo.lock generated Normal file
View File

@ -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"

25
hunter-media/Cargo.toml Normal file
View File

@ -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"]

587
hunter-media/src/main.rs Normal file
View File

@ -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)
}
}

View File

@ -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

View File

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

View File

@ -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))?;
},
_ => {},
}

View File

@ -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())

View File

@ -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;

View File

@ -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 {
});
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();

View File

@ -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<Files>),
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)
}
}

View File

@ -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<Config> {
let conf = self.config
.read()?
.get()?
.clone();
Ok(conf)
}
}