Skip to content

Commit

Permalink
Use ffmpeg to grab video thumbnails.
Browse files Browse the repository at this point in the history
  • Loading branch information
pkulak committed Apr 28, 2024
1 parent 4439798 commit 2e28f48
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 40 deletions.
1 change: 0 additions & 1 deletion .envrc

This file was deleted.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ key_bindings:
Once that is setup, Matui will open Neovim (if that's your default editor)
with keys mapped such that Shift+Enter inserts a new line.

Another nice option is to map `jx` to `<Esc>:x<CR>` in insert mode.

## File Viewing

You will probably want to view attachements and should make sure xdg-open works
Expand All @@ -119,7 +121,8 @@ with all the files you care about. I recommend [mpv](https://mpv.io/) and

## File Uploading

KDialog and/or Zenity is required to show the file picker.
KDialog and/or Zenity is required to show the file picker. FFMpeg is also
required to create thumbnails if you upload videos.

# Configuration Example

Expand Down
6 changes: 3 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
outputs = { nixpkgs, rust-overlay, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
Expand Down Expand Up @@ -37,8 +37,8 @@
# won't be found by `openssl-sys` if it's in `nativeBuildInputs`
buildInputs = with pkgs;
[ openssl ];
nativeBuildInputs = with pkgs;
sharedDeps ++ [ ];
nativeBuildInputs = with pkgs;
sharedDeps ++ [ ffmpeg ];
};
default = packages.matui;
};
Expand Down
9 changes: 4 additions & 5 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
let
# put this on stable once Cargo 1.7 hits
pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
buildInputs = with pkgs; [ cargo rustc rust-analyzer bacon clippy openssl pkg-config ];
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
buildInputs = with pkgs; [ cargo ffmpeg rustc rust-analyzer bacon clippy openssl pkg-config ];
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod settings;

/// Using external apps to do our bidding
pub mod spawn;
pub mod video;

pub fn limit_list<T>(iter: T, limit: usize, total: usize, prefix: Option<&str>) -> Vec<String>
where
Expand Down
24 changes: 22 additions & 2 deletions src/matrix/matrix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::matrix::matrix::MessageType::File;
use crate::video::get_video_thumbnail;
use std::{fs, thread};

use std::path::{Path, PathBuf};
Expand All @@ -9,7 +10,7 @@ use std::time::Duration;
use anyhow::{bail, Context};
use futures::stream::StreamExt;
use log::{error, info};
use matrix_sdk::attachment::AttachmentConfig;
use matrix_sdk::attachment::{AttachmentConfig, Thumbnail};
use matrix_sdk::config::SyncSettings;
use matrix_sdk::encryption::verification::{Emoji, SasState, SasVerification, Verification};
use matrix_sdk::media::{MediaFormat, MediaRequest};
Expand All @@ -26,6 +27,7 @@ use matrix_sdk::ruma::events::room::message::{MessageType, OriginalSyncRoomMessa
use matrix_sdk::ruma::exports::serde_json;
use matrix_sdk::ruma::UserId;
use matrix_sdk::{Client, LoopCtrl, ServerName, Session};
use mime::IMAGE_JPEG;
use once_cell::sync::OnceCell;
use rand::rngs::OsRng;
use rand::{distributions::Alphanumeric, Rng};
Expand Down Expand Up @@ -444,8 +446,26 @@ impl Matrix {
}
};

// try to grab a thumbnail if it's a video
let config = if content_type.type_() == "video" {
match get_video_thumbnail(&path) {
Ok(data) => {
let thumb = Thumbnail {
data,
content_type: IMAGE_JPEG,
info: None,
};

AttachmentConfig::with_thumbnail(thumb)
}
_ => AttachmentConfig::new(),
}
} else {
AttachmentConfig::new()
};

if let Err(err) = room
.send_attachment(&name, &content_type, data, AttachmentConfig::new())
.send_attachment(&name, &content_type, data, config)
.await
{
Matrix::send(Error(err.to_string()));
Expand Down
43 changes: 43 additions & 0 deletions src/video.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::{fs, path::Path, process::Command};

pub fn get_video_duration(path: &Path) -> anyhow::Result<f32> {
let mut command = Command::new("ffprobe");

command.args([
"-loglevel",
"error",
"-of",
"csv=p=0",
"-show_entries",
"format=duration",
]);

command.arg(path);

let output = command.output()?;
let output = String::from_utf8(output.stdout)?;

Ok(output.trim().parse()?)
}

pub fn get_video_thumbnail(path: &Path) -> anyhow::Result<Vec<u8>> {
let duration = get_video_duration(path)?;
let tmpfile = tempfile::Builder::new().suffix(".jpg").tempfile()?;

let mut command = Command::new("ffmpeg");

command.arg("-y");
command.args(["-loglevel", "error"]);
command.arg("-ss");
command.arg((duration / 2.0).to_string());
command.arg("-i");
command.arg(path);
command.args(["-frames:v", "1", "-update", "true"]);
command.arg(tmpfile.path());

if !command.status()?.success() {
anyhow::bail!("could not create thumbnail");
}

Ok(fs::read(tmpfile.path())?)
}
67 changes: 39 additions & 28 deletions src/widgets/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crossterm::event::KeyEvent;
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, BorderType, Borders, Widget, Table, Row};
use ratatui::widgets::{Block, BorderType, Borders, Row, Table, Widget};

use crate::widgets::get_margin;

Expand All @@ -12,10 +12,6 @@ use super::EventResult;
pub struct Help;

impl Help {
pub fn new() -> Self {
Self
}

pub fn widget(&self) -> HelpWidget {
HelpWidget
}
Expand All @@ -26,6 +22,12 @@ impl Help {
}
}

impl Default for Help {
fn default() -> Self {
Self
}
}

pub struct HelpWidget;

impl Widget for HelpWidget {
Expand Down Expand Up @@ -62,28 +64,37 @@ impl Widget for HelpWidget {
.split(splits[0])[0];

Table::new(vec![
Row::new(vec!["Space", "Show the room switcher"]),
Row::new(vec!["j*", "Select one line down."]),
Row::new(vec!["k*", "Select one line up."]),
Row::new(vec!["i", "Create a new message using the external editor."]),
Row::new(vec!["Enter", "Open the selected message (images, videos, urls, etc)."]),
Row::new(vec!["s", "Save the selected message (images and videos)."]),
Row::new(vec!["c", "Edit the selected message in the external editor."]),
Row::new(vec!["r", "React to the selected message."]),
Row::new(vec!["R", "Reply to the selected message."]),
Row::new(vec!["v", "View the selected message in the external editor."]),
Row::new(vec!["V", "View the current room in the external editor."]),
Row::new(vec!["u", "Upload a file."]),
Row::new(vec!["?", "Show this helper."]),
Row::new(vec!["", "* arrow keys are fine too."]),
])
.header(
Row::new(vec!["Key", "Description"])
.style(Style::default().fg(Color::Green))
.bottom_margin(1)
)
.widths(&[Constraint::Length(6), Constraint::Percentage(90)])
.column_spacing(1)
.render(area, buf)
Row::new(vec!["Space", "Show the room switcher"]),
Row::new(vec!["j*", "Select one line down."]),
Row::new(vec!["k*", "Select one line up."]),
Row::new(vec!["i", "Create a new message using the external editor."]),
Row::new(vec![
"Enter",
"Open the selected message (images, videos, urls, etc).",
]),
Row::new(vec!["s", "Save the selected message (images and videos)."]),
Row::new(vec![
"c",
"Edit the selected message in the external editor.",
]),
Row::new(vec!["r", "React to the selected message."]),
Row::new(vec!["R", "Reply to the selected message."]),
Row::new(vec![
"v",
"View the selected message in the external editor.",
]),
Row::new(vec!["V", "View the current room in the external editor."]),
Row::new(vec!["u", "Upload a file."]),
Row::new(vec!["?", "Show this helper."]),
Row::new(vec!["", "* arrow keys are fine too."]),
])
.header(
Row::new(vec!["Key", "Description"])
.style(Style::default().fg(Color::Green))
.bottom_margin(1),
)
.widths(&[Constraint::Length(6), Constraint::Percentage(90)])
.column_spacing(1)
.render(area, buf)
}
}

0 comments on commit 2e28f48

Please sign in to comment.