summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorrabite <rabite@posteo.de>2019-07-17 22:56:30 +0200
committerrabite <rabite@posteo.de>2019-07-17 22:56:30 +0200
commitf38093ad1f5b890d8912aaae1ff883961d44fa01 (patch)
tree2ea72cfa39346069d6fd80bfec602be64ebd2a40 /src
parent3ba311a5bbf07a18eb52c9aee23fc069015c3dfd (diff)
big fat update to graphics mode (performance, features, etc)
Diffstat (limited to 'src')
-rw-r--r--src/config.rs53
-rw-r--r--src/coordinates.rs15
-rw-r--r--src/hunter-media.rs1009
-rw-r--r--src/imgview.rs25
-rw-r--r--src/main.rs10
-rw-r--r--src/mediaview.rs89
-rw-r--r--src/preview.rs83
-rw-r--r--src/term.rs17
-rw-r--r--src/widget.rs5
9 files changed, 838 insertions, 468 deletions
diff --git a/src/config.rs b/src/config.rs
index 1893935..864248d 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -14,7 +14,7 @@ struct ArgvConfig {
animation: Option<bool>,
show_hidden: Option<bool>,
icons: Option<bool>,
- sixel: Option<bool>,
+ graphics: Option<String>,
}
impl ArgvConfig {
@@ -23,7 +23,7 @@ impl ArgvConfig {
animation: None,
show_hidden: None,
icons: None,
- sixel: None
+ graphics: None
}
}
}
@@ -37,7 +37,6 @@ pub fn set_argv_config(args: clap::ArgMatches) -> HResult<()> {
let animation = args.is_present("animation-off");
let show_hidden = args.is_present("show-hidden");
let icons = args.is_present("icons");
- let sixel = args.is_present("sixel");
let mut config = ArgvConfig::new();
@@ -53,8 +52,12 @@ pub fn set_argv_config(args: clap::ArgMatches) -> HResult<()> {
config.icons = Some(true)
}
- if sixel == true {
- config.sixel = Some(true)
+ if let Some(mode) = args.value_of("graphics") {
+ if mode == "auto" {
+ config.graphics = Some(detect_g_mode());
+ } else {
+ config.graphics = Some(String::from(mode));
+ }
}
*ARGV_CONFIG.write()? = config;
@@ -71,7 +74,7 @@ fn infuse_argv_config(mut config: Config) -> Config {
argv_config.animation.map(|val| config.animation = val);
argv_config.show_hidden.map(|val| config.show_hidden = val);
argv_config.icons.map(|val| config.icons = val);
- argv_config.sixel.map(|val| config.sixel = val);
+ argv_config.graphics.map(|val| config.graphics = val);
config
}
@@ -88,7 +91,7 @@ pub struct Config {
pub media_mute: bool,
pub media_previewer: String,
pub ratios: Vec::<usize>,
- pub sixel: bool,
+ pub graphics: String,
}
@@ -111,7 +114,7 @@ impl Config {
media_mute: false,
media_previewer: "hunter-media".to_string(),
ratios: vec![20,30,49],
- sixel: false
+ graphics: detect_g_mode(),
}
}
@@ -126,16 +129,16 @@ impl Config {
let config = config_string.lines().fold(Config::new(), |mut config, line| {
match Config::prep_line(line) {
- Ok(("animation", "on")) => { config.animation = true; },
- Ok(("animation", "off")) => { config.animation = false; },
+ Ok(("animation", "on")) => config.animation = true,
+ Ok(("animation", "off")) => config.animation = false,
Ok(("animation_refresh_frequency", frequency)) => {
match frequency.parse::<usize>() {
Ok(parsed_freq) => config.animation_refresh_frequency = parsed_freq,
_ => HError::config_error::<Config>(line.to_string()).log()
}
}
- Ok(("show_hidden", "on")) => { config.show_hidden = true; },
- Ok(("show_hidden", "off")) => { config.show_hidden = false; },
+ Ok(("show_hidden", "on")) => config.show_hidden = true,
+ Ok(("show_hidden", "off")) => config.show_hidden = false,
Ok(("icons", "on")) => config.icons = true,
Ok(("icons", "off")) => config.icons = false,
Ok(("select_cmd", cmd)) => {
@@ -146,10 +149,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; },
+ 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,
Ok(("media_previewer", cmd)) => {
let cmd = cmd.to_string();
config.media_previewer = cmd;
@@ -167,7 +170,13 @@ impl Config {
}
}
}
- Ok(("sixel", "on")) => { config.sixel = true; }
+ #[cfg(feature = "sixel")]
+ Ok(("graphics",
+ "sixel")) => config.graphics = "sixel".to_string(),
+ Ok(("graphics",
+ "kitty")) => config.graphics = "kitty".to_string(),
+ Ok(("graphics",
+ "auto")) => config.graphics = detect_g_mode(),
_ => { HError::config_error::<Config>(line.to_string()).log(); }
}
config
@@ -196,3 +205,13 @@ impl Config {
self.show_hidden
}
}
+
+fn detect_g_mode() -> String {
+ let term = std::env::var("TERM").unwrap_or(String::new());
+ match term.as_str() {
+ "xterm-kitty" => "kitty",
+ #[cfg(feature = "sixel")]
+ "xterm" => "sixel",
+ _ => "unicode"
+ }.to_string()
+}
diff --git a/src/coordinates.rs b/src/coordinates.rs
index 0bd2ff7..ac969ea 100644
--- a/src/coordinates.rs
+++ b/src/coordinates.rs
@@ -1,3 +1,5 @@
+use crate::fail::HResult;
+
#[derive(Debug, Clone, PartialEq)]
pub struct Size(pub (u16, u16));
#[derive(Debug, Clone, PartialEq)]
@@ -110,6 +112,19 @@ impl Coordinates {
((xsize-1) as usize, (ysize-1) as usize)
}
+ pub fn size_pixels(&self) -> HResult<(usize, usize)> {
+ let (xsize, ysize) = self.size_u();
+ let (cols, rows) = crate::term::size()?;
+ let (xpix, ypix) = crate::term::size_pixels()?;
+ // Cell dimensions
+ let (xpix, ypix) = (xpix/cols, ypix/rows);
+ // Frame dimensions
+ let (xpix, ypix) = (xpix * (xsize + 1),
+ ypix * (ysize + 1));
+
+ Ok((xpix as usize, ypix as usize))
+ }
+
pub fn top(&self) -> Position {
self.position().clone()
}
diff --git a/src/hunter-media.rs b/src/hunter-media.rs
index cc1de41..585f3d8 100644
--- a/src/hunter-media.rs
+++ b/src/hunter-media.rs
@@ -1,8 +1,7 @@
// Based on https://github.com/jD91mZM2/termplay
// MIT License
-use image::{FilterType, DynamicImage, GenericImageView};
-use sixel::encoder::Encoder;
+use image::{RgbaImage, DynamicImage, GenericImageView};
use base64;
use termion::color::{Bg, Fg, Rgb};
@@ -11,7 +10,7 @@ use termion::input::TermRead;
#[cfg(feature = "video")]
-use gstreamer::{self, prelude::*};
+use gstreamer::prelude::*;
#[cfg(feature = "video")]
use gstreamer_app;
@@ -36,50 +35,65 @@ fn main() -> MResult<()> {
.expect("provide ysize")
.parse()
.unwrap();
- let xpix = args.get(3)
+ let mut xpix = args.get(3)
.expect("provide xsize in pixels")
.parse::<usize>()
.unwrap();
- let ypix = args.get(4)
+ let mut ypix = args.get(4)
.expect("provide ysize in pixels")
.parse::<usize>()
.unwrap();
- let preview_type = args.get(5)
+ let cell_ratio = args.get(5)
+ .expect("Provide cell ratio")
+ .parse::<f32>()
+ .unwrap();
+ let preview_type = args.get(6)
.expect("Provide preview type")
.parse::<String>()
.unwrap();
- // #[cfg(feature = "video")]
- let autoplay = args.get(6)
+ let autoplay = args.get(7)
.expect("Autoplay?")
.parse::<bool>()
.unwrap();
- // #[cfg(feature = "video")]
- let mute = args.get(7)
+ let mute = args.get(8)
.expect("Muted?")
.parse::<bool>()
.unwrap();
- let sixel = args.get(8)
- .expect("Use SIXEL?")
- .parse::<bool>()
+ let target = args.get(9)
+ .expect("Render target?")
+ .parse::<String>()
.unwrap();
- let path = args.get(9).expect("Provide path");
-
- let target = if sixel {
- if std::env::var("TERM") == Ok(String::from("xterm-kitty")) {
- RenderTarget::Kitty
- } else {
- RenderTarget::Sixel
+ let path = args.get(10).expect("Provide path");
+
+ let target = match target.as_str() {
+ #[cfg(feature = "sixel")]
+ "sixel" => RenderTarget::Sixel,
+ "kitty" => RenderTarget::Kitty,
+ "auto" => {
+ let term = std::env::var("TERM").unwrap_or(String::from(""));
+ match term.as_str() {
+ "kitty" => RenderTarget::Kitty,
+ #[cfg(feature = "sixel")]
+ "xterm" => RenderTarget::Sixel,
+ _ => RenderTarget::Unicode,
+ }
}
- } else {
- RenderTarget::Unicode
+ _ => RenderTarget::Unicode
};
+ if target == RenderTarget::Unicode {
+ xpix = xsize;
+ ypix = ysize * 2;
+ }
+
+
let renderer = Renderer::new(target,
xsize,
ysize,
xpix,
- ypix);
+ ypix,
+ cell_ratio);
let result =
match preview_type.as_ref() {
@@ -115,70 +129,95 @@ fn main() -> MResult<()> {
fn image_preview(path: &str,
renderer: Renderer) -> MResult<()> {
let img = image::open(&path)?;
+ let max_size = renderer.max_size_pix(&img);
+ let img = img.resize_exact(max_size.0 as u32,
+ max_size.1 as u32,
+ image::FilterType::Gaussian)
+ .to_rgba();
renderer.send_image(&img)?;
Ok(())
}
+trait ImgSize {
+ fn size(&self) -> MResult<(usize, usize)>;
+}
+
+#[cfg(feature = "video")]
+impl ImgSize for gstreamer::Sample {
+ fn size(&self) -> MResult<(usize, usize)> {
+ let size = || {
+ let caps = self.as_ref().get_caps()?;
+ let caps = caps.get_structure(0)?;
+ let width = caps.get::<i32>("width")? as usize;
+ let height = caps.get::<i32>("height")? as usize;
+ Some((width, height))
+ };
+ size().ok_or(format_err!("Can't get size from sample!"))
+ }
+}
+
+impl ImgSize for RgbaImage {
+ fn size(&self) -> MResult<(usize, usize)> {
+ let width = self.width() as usize;
+ let height = self.height() as usize;
+ Ok((width, height))
+ }
+}
+
+impl ImgSize for DynamicImage {
+ fn size(&self) -> MResult<(usize, usize)> {
+ let width = self.width() as usize;
+ let height = self.height() as usize;
+ Ok((width, height))
+ }
+}
+
+
#[cfg(feature = "video")]
fn video_preview(path: &String,
renderer: Renderer,
autoplay: bool,
mute: bool)
-> MResult<()> {
- let low_fps = renderer.target == RenderTarget::Sixel;
-
- let (player, appsink) = make_gstreamer(low_fps)?;
-
- let uri = format!("file://{}", &path);
-
- player.set_property("uri", &uri)?;
-
+ let gst = Gstreamer::new(path)?;
let renderer = Arc::new(RwLock::new(renderer));
let crenderer = renderer.clone();
+ let cgst = gst.clone();
+ gst.process_first_frame(&renderer)?;
-
-
-
- let p = player.clone();
-
- appsink.set_callbacks(
+ gst.appsink.set_callbacks(
gstreamer_app::AppSinkCallbacks::new()
.new_sample({
move |sink| {
+ let renderer = crenderer.clone();
+ let gst = cgst.clone();
+
let sample = match sink.pull_sample() {
Some(sample) => sample,
- None => return gstreamer::FlowReturn::Eos,
+ None => return Err(gstreamer::FlowError::Eos)
};
- let position = p.query_position::<gstreamer::ClockTime>()
- .map(|p| p.seconds().unwrap_or(0))
- .unwrap_or(0);
+ let pos = gst.position();
+ let dur = gst.duration();
- let duration = p.query_duration::<gstreamer::ClockTime>()
- .map(|d| d.seconds().unwrap_or(0))
- .unwrap_or(0);
-
- let renderer = crenderer.clone();
std::thread::spawn(move || {
- renderer.write()
- .map(|mut r| r.send_frame(&*sample,
- position,
- duration)).ok()
+ // This will lock make sure only one frame is being sent
+ // at a time
+ renderer.try_write()
+ .map(|mut r| r.new_frame(sample,
+ pos,
+ dur).unwrap())
+ .map_err(|_| {
+ // But if processing takes too long, reduce rate
+ let rate = gst.get_rate().unwrap();
+ gst.set_rate(rate-1)
+ }).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
- }
+ Ok(gstreamer::FlowSuccess::Ok)
}
})
.eos({
@@ -189,25 +228,24 @@ fn video_preview(path: &String,
.build()
);
- if mute == true || autoplay == false {
- player.set_property("volume", &0.0)?;
- }
- player.set_state(gstreamer::State::Playing).into_result()?;
-
-
-
+ // Flush pipeline and restart with corrent resizing
+ gst.stop()?;
+ if autoplay {
+ gst.start(mute)?;
+ } else {
+ gst.pause()?;
+ gst.send_preroll(&renderer)?;
+ }
- read_keys(player, Some(renderer))?;
+ read_keys(gst.clone(), Some(renderer))?;
Ok(())
}
#[cfg(feature = "video")]
-fn read_keys(player: gstreamer::Element,
+fn read_keys(gst: Gstreamer,
renderer: Option<Arc<RwLock<Renderer>>>) -> MResult<()> {
- let seek_time = gstreamer::ClockTime::from_seconds(5);
-
let stdin = std::io::stdin();
let mut stdin = stdin.lock();
@@ -218,68 +256,65 @@ fn read_keys(player: gstreamer::Element,
match input.as_str() {
- "q" => std::process::exit(0),
+ "q" => return gst.stop(),
">" => {
- 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)
- )?;
+ gst.seek_forward()?;
+ renderer.as_ref().map(|r| {
+ if gst.get_state() == gstreamer::State::Paused {
+ gst.send_preroll(&r).unwrap();
}
+ });
},
"<" => {
- 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)
- )?;
+ gst.seek_backward()?;
+ renderer.as_ref().map(|r| {
+ if gst.get_state() == gstreamer::State::Paused {
+ gst.send_preroll(&r).unwrap();
}
+ });
}
- "a" => {
- player.set_state(gstreamer::State::Paused).into_result()?;
- }
- "m" => {
- player.set_property("volume", &0.0)?;
- }
- "u" => {
- player.set_property("volume", &1.0)?;
- }
- // TODO add pixel size
+ "p" => gst.play()?,
+ "a" => gst.pause()?,
+ "m" => gst.mute()?,
+ "u" => gst.unmute()?,
"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 xsize = stdin.read_line()?
+ .unwrap_or(String::from("0"))
+ .parse::<usize>()?;;
+ let ysize = stdin.read_line()?
+ .unwrap_or(String::from("0"))
+ .parse::<usize>()?;;
+ let mut xpix = stdin.read_line()?
+ .unwrap_or(String::from("0"))
+ .parse::<usize>()?;;
+ let mut ypix = stdin.read_line()?
+ .unwrap_or(String::from("0"))
+ .parse::<usize>()?;;
+ let cell_ratio = stdin.read_line()?
+ .unwrap_or(String::from("0"))
+ .parse::<f32>()?;;
let mut renderer = renderer
.write()
.map_err(|_| format_err!("Renderer RwLock failed!"))?;
- renderer.set_size(xsize, ysize)?;
+ if renderer.target == RenderTarget::Unicode {
+ xpix = xsize;
+ ypix = ysize*2;
+ }
+
+
+ renderer.set_widget_size(xsize, ysize, xpix, ypix, cell_ratio)?;
+ match renderer.last_frame {
+ Some(ref sample) => {
+ let (max_x, max_y) = renderer.max_size_pix(sample);
+ gst.set_scaling(max_x, max_y)?;
+ }
+ _ => {}
+ }
+
+
}
}
_ => {}
@@ -292,12 +327,8 @@ pub fn audio_preview(path: &String,
autoplay: bool,
mute: bool)
-> MResult<()> {
- let (player, _) = make_gstreamer(false)?;
-
- let uri = format!("file://{}", &path);
-
- player.set_property("uri", &uri)?;
- let p = player.clone();
+ let gst = Gstreamer::new(path)?;
+ let tgst = gst.clone();
// Since events don't work with audio files...
std::thread::spawn(move || -> MResult<()> {
@@ -306,14 +337,10 @@ pub fn audio_preview(path: &String,
let mut stdout = std::io::stdout();
loop {
std::thread::sleep(sleep_duration);
+ let gst = tgst.clone();
- 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);
+ let position = gst.position();
+ let duration = gst.duration();
// Just redo loop until position changes
if last_pos == Some(position) {
@@ -333,353 +360,581 @@ pub fn audio_preview(path: &String,
});
- if mute == true || autoplay == false{
- player.set_property("volume", &0.0)?;
- } else {
- player.set_state(gstreamer::State::Playing).into_result()?;
+ if autoplay && !mute {
+ gst.start(mute)?;
}
- read_keys(player, None)?;
+ read_keys(gst, None)?;
Ok(())
}
#[cfg(feature = "video")]
-pub fn make_gstreamer(low_fps: bool) -> MResult<(gstreamer::Element,
- gstreamer_app::AppSink)> {
- gstreamer::init()?;
+#[derive(Clone)]
+struct Gstreamer {
+ player: gstreamer::Element,
+ appsink: gstreamer_app::AppSink,
+ videorate: gstreamer::Element,
+}
- let player = gstreamer::ElementFactory::make("playbin", None)
- .ok_or(format_err!("Can't create playbin"))?;
+#[cfg(feature = "video")]
+impl Gstreamer {
+ fn new(file: &str) -> MResult<Gstreamer> {
+ use gstreamer::{Element, ElementFactory, GhostPad, Bin};
+ gstreamer::init()?;
- let videorate = gstreamer::ElementFactory::make("videorate", None)
- .ok_or(format_err!("Can't create videorate element"))?;
+ let player = ElementFactory::make("playbin", None)
+ .ok_or(format_err!("Can't create playbin"))?;
- let pnmenc = gstreamer::ElementFactory::make("pnmenc", None)
- .ok_or(format_err!("Can't create PNM-encoder"))?;
+ let videorate = ElementFactory::make("videorate", None)
+ .ok_or(format_err!("Can't create videorate element"))?;
- let sink = gstreamer::ElementFactory::make("appsink", None)
- .ok_or(format_err!("Can't create appsink"))?;
+ let sink = ElementFactory::make("appsink", None)
+ .ok_or(format_err!("Can't create appsink"))?;
- let appsink = sink.clone()
- .downcast::<gstreamer_app::AppSink>()
- .unwrap();
+ let appsink = sink.clone()
+ .downcast::<gstreamer_app::AppSink>()
+ .unwrap();
+ let elems = &[&videorate, //&videoscale,
+ &sink];
- if low_fps {
- videorate.set_property("max-rate", &10)?;
- } else {
- videorate.set_property("max-rate", &30)?;
+ let bin = Bin::new(None);
+
+ bin.add_many(elems)?;
+ Element::link_many(elems)?;
+
+ // make input for bin point to first element
+ let sink = elems[0].get_static_pad("sink").unwrap();
+ let ghost = GhostPad::new(Some("sink"), &sink)
+ .ok_or(format_err!("Can't create GhostPad"))?;
+
+ ghost.set_active(true)?;
+ bin.add_pad(&ghost)?;
+
+ appsink.set_drop(true);
+ appsink.set_max_buffers(4);
+
+ videorate.set_property("drop-only", &true)?;
+ //videorate.set_property("max-rate", &1)?;
+
+ let uri = format!("file://{}", &file);
+
+ player.set_property("video-sink", &bin.upcast::<gstreamer::Element>())?;
+ player.set_property("uri", &uri)?;
+
+ use gstreamer::prelude::*;
+
+ Ok(Gstreamer {
+ player,
+ appsink,
+ videorate,
+ })
+ }
+
+ pub fn change_format(&self, format: gstreamer::Caps) -> MResult<()> {
+ use gstreamer::Element;
+ use gstreamer_video::prelude::*;
+
+ let state = self.get_state();
+ self.pause()?;
+
+
+ let appsink = self.appsink.clone()
+ .upcast::<Element>();
+
+ Element::unlink_many(&[&self.videorate, &appsink]);
+
+ self.appsink.set_caps(Some(&format));
+
+ Element::link_many(&[&self.videorate, &appsink])?;
+
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ self.player.set_state(state)?;
+
+
+
+ Ok(())
}
- let elems = &[&videorate, &pnmenc, &sink];
+ pub fn process_first_frame(&self,
+ renderer: &Arc<RwLock<Renderer>>) -> MResult<()> {
+ self.pause()?;
+
+ let sample = self.appsink.pull_preroll()
+ .ok_or_else(|| format_err!("Couldn't read first frame!"))?;
+
+ let (max_x, max_y) = renderer.read()
+ .map_err(|_| format_err!("Failed at locking renderer!"))?
+ .max_size_pix(&sample);
- let bin = gstreamer::Bin::new(None);
- bin.add_many(elems)?;
- gstreamer::Element::link_many(elems)?;
+ self.set_scaling(max_x, max_y)?;
- // 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"))?;
+ Ok(())
+ }
- ghost.set_active(true)?;
- bin.add_pad(&ghost)?;
- player.set_property("video-sink", &bin.upcast::<gstreamer::Element>())?;
+ pub fn send_preroll(&self,
+ renderer: &Arc<RwLock<Renderer>>) -> MResult<()> {
+ let appsink = self.appsink.downcast_ref::<gstreamer_app::AppSink>().unwrap();
+ let sample = appsink.pull_preroll().unwrap();
+ let pos = self.position();
+ let dur = self.duration();
+ renderer.write().unwrap().new_frame(sample, pos, dur)
+ }
+
+ pub fn set_scaling(&self, x: usize, y: usize) -> MResult<()> {
+ use gstreamer::Caps;
- Ok((player, appsink))
+ let caps =
+ format!("video/x-raw,format=RGBA,width={},height={}",
+ x,
+ y);
+ let caps = Caps::from_string(&caps).unwrap();
+
+ self.change_format(caps)
+ }
+
+ pub fn get_rate(&self) -> MResult<i32> {
+ let rate = self.videorate
+ .get_property("max-rate")?
+ .downcast::<i32>().unwrap()
+ .get()
+ .ok_or_else(|| format_err!("No video rate???"))?;
+
+ if rate == 2147483647 {
+ // Sane defalt fps cap if rendering is too slow
+ Ok(30)
+ } else {
+ Ok(rate)
+ }
+ }
+
+ pub fn set_rate(&self, rate: i32) -> MResult<()> {
+ self.videorate.set_property("max-rate", &rate)?;
+ Ok(())
+ }
+
+ pub fn position(&self) -> usize {
+ self.player.query_position::<gstreamer::ClockTime>()
+ .map(|p| p.seconds().unwrap_or(0))
+ .unwrap_or(0) as usize
+ }
+
+ pub fn duration(&self) -> usize {
+ self.player.query_duration::<gstreamer::ClockTime>()
+ .map(|d| d.seconds().unwrap_or(0))
+ .unwrap_or(0) as usize
+ }
+
+ pub fn set_state(&self, state: gstreamer::State) -> MResult<()> {
+ self.player.set_state(state)?;
+ // HACK: How to sync properly?
+ std::thread::sleep(std::time::Duration::from_millis(100));
+
+ Ok(())
+ }
+
+ pub fn pause(&self) -> MResult<()> {
+ self.set_state(gstreamer::State::Paused)?;
+
+ Ok(())
+ }
+
+ pub fn mute(&self) -> MResult<()> {
+ Ok(self.player.set_property("volume", &0.0)?)
+ }
+
+ pub fn unmute(&self) -> MResult<()> {
+ Ok(self.player.set_property("volume", &1.0)?)
+ }
+
+ pub fn get_state(&self) -> gstreamer::State {
+ let timeout = gstreamer::ClockTime::from_seconds(1);
+ let state = self.player.get_state(timeout);
+
+ state.1
+ }
+
+ pub fn start(&self, mute: bool) -> MResult<()> {
+ if mute {
+ self.mute()?;
+ }
+ self.play()
+ }
+
+ pub fn play(&self) -> MResult<()> {
+ self.set_state(gstreamer::State::Playing)
+ }
+
+ pub fn stop(&self) -> MResult<()> {
+ self.set_state(gstreamer::State::Ready)
+ }
+
+ pub fn seek_forward(&self) -> MResult<()> {
+ let seek_time = gstreamer::ClockTime::from_seconds(5);
+ if let Some(mut time) = self.player
+ .query_position::<gstreamer::ClockTime>() {
+ time += seek_time;
+
+ self.player.seek_simple(
+ gstreamer::SeekFlags::FLUSH,
+ gstreamer::format::GenericFormattedValue::Time(time)
+ )?;
+ }
+ Ok(())
+ }
+
+ pub fn seek_backward(&self) -> MResult<()> {
+ let seek_time = gstreamer::ClockTime::from_seconds(5);
+ if let Some(mut time) = self.player
+ .query_position::<gstreamer::ClockTime>() {
+ if time >= seek_time {
+ time -= seek_time;
+ } else {
+ time = gstreamer::ClockTime(Some(0));
+ }
+
+ self.player.seek_simple(
+ gstreamer::SeekFlags::FLUSH,
+ gstreamer::format::GenericFormattedValue::Time(time)
+ )?;
+ }
+ Ok(())
+ }
+}
+
+
+trait WithRaw {
+ fn with_raw(&self,
+ fun: impl FnOnce(&[u8]) -> MResult<()>)
+ -> MResult<()>;
+}
+
+#[cfg(feature = "video")]
+impl WithRaw for gstreamer::Sample {
+ fn with_raw(&self,
+ fun: impl FnOnce(&[u8]) -> MResult<()>)
+ -> MResult<()> {
+ let buffer = self.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!"))?;
+
+ fun(map.as_slice())
+ }
+}
+
+// Mostly for plain old images, since they come from image::open
+impl WithRaw for RgbaImage {
+ fn with_raw(&self,
+ fun: impl FnOnce(&[u8]) -> MResult<()>)
+ -> MResult<()> {
+ let bytes = self.as_flat_samples();
+
+ fun(bytes.as_slice())
+ }
}
#[derive(PartialEq)]
enum RenderTarget {
Unicode,
+ #[cfg(feature = "sixel")]
Sixel,
Kitty
}
+impl RenderTarget {
+ fn send_image(&self,
+ img: &(impl WithRaw+ImgSize),
+ context: &Renderer) -> MResult<()> {
+ match self {
+ #[cfg(feature = "sixel")]
+ RenderTarget::Sixel => self.print_sixel(img)?,
+ RenderTarget::Unicode => self.print_unicode(img)?,
+ RenderTarget::Kitty => self.print_kitty(img, context)?
+ }
+ Ok(())
+ }
+
+ fn print_unicode(&self, img: &(impl WithRaw+ImgSize)) -> MResult<()> {
+ let (xsize, _) = img.size()?;
+
+ img.with_raw(move |raw| -> MResult<()> {
+ let lines = raw.chunks(4*xsize*2).map(|two_lines_colors| {
+ let (upper_line,lower_line) = two_lines_colors.split_at(4*xsize);
+ upper_line.chunks(4)
+ .zip(lower_line.chunks(4))
+ .map(|(upper, lower)| {
+ format!("{}{}▀{}",
+ Fg(Rgb(upper[0], upper[1], upper[2])),
+ Bg(Rgb(lower[0], lower[1], lower[2])),
+ termion::style::Reset
+ )
+ }).collect::<String>()
+ }).collect::<Vec<String>>();
+
+ for line in lines {
+ println!("{}", line);
+ }
+
+ println!("");
+
+ Ok(())
+ })
+ }
+
+ fn print_kitty(&self,
+ img: &(impl WithRaw+ImgSize),
+ context: &Renderer) -> MResult<()> {
+ let (w,h) = context.max_size(img);
+ let (img_x, img_y) = img.size()?;
+
+ img.with_raw(move |raw| -> MResult<()> {
+ let mut file = std::fs::File::create("/tmp/img.raw.new")?;
+ file.write_all(raw)?;
+ file.flush()?;
+ std::fs::rename("/tmp/img.raw.new", "/tmp/img.raw")?;
+
+ let path = base64::encode("/tmp/img.raw");
+
+ print!("\x1b_Ga=d\x1b\\");