Skip to content

Commit

Permalink
feat!: video spotter & using ffmpeg instead of ffmpegthumbnailer
Browse files Browse the repository at this point in the history
…as previewer backend (#1928)
  • Loading branch information
sxyazi committed Nov 22, 2024
1 parent 22e7d57 commit 34ca3c4
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 96 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo"],"language":"en","version":"0.2"}
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo","ffprobe","vframes"],"version":"0.2"}
29 changes: 15 additions & 14 deletions yazi-boot/src/actions/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,21 @@ impl Actions {

writeln!(s, "\nDependencies")?;
#[rustfmt::skip]
writeln!(s, " file : {}", Self::process_output(env::var_os("YAZI_FILE_ONE").unwrap_or("file".into()), "--version"))?;
writeln!(s, " ueberzugpp : {}", Self::process_output("ueberzugpp", "--version"))?;
writeln!(s, " ffmpegthumbnailer: {}", Self::process_output("ffmpegthumbnailer", "-v"))?;
writeln!(s, " pdftoppm : {}", Self::process_output("pdftoppm", "--help"))?;
writeln!(s, " magick : {}", Self::process_output("magick", "--version"))?;
writeln!(s, " fzf : {}", Self::process_output("fzf", "--version"))?;
writeln!(s, " fd : {}", Self::process_output("fd", "--version"))?;
writeln!(s, " fdfind : {}", Self::process_output("fdfind", "--version"))?;
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
writeln!(s, " 7z : {}", Self::process_output("7z", "i"))?;
writeln!(s, " 7zz : {}", Self::process_output("7zz", "i"))?;
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
writeln!(s, " file : {}", Self::process_output(env::var_os("YAZI_FILE_ONE").unwrap_or("file".into()), "--version"))?;
writeln!(s, " ueberzugpp : {}", Self::process_output("ueberzugpp", "--version"))?;
#[rustfmt::skip]
writeln!(s, " ffmpeg/ffprobe: {} / {}", Self::process_output("ffmpeg", "-version"), Self::process_output("ffprobe", "-version"))?;
writeln!(s, " pdftoppm : {}", Self::process_output("pdftoppm", "--help"))?;
writeln!(s, " magick : {}", Self::process_output("magick", "--version"))?;
writeln!(s, " fzf : {}", Self::process_output("fzf", "--version"))?;
#[rustfmt::skip]
writeln!(s, " fd/fdfind : {} / {}", Self::process_output("fd", "--version"), Self::process_output("fdfind", "--version"))?;
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
#[rustfmt::skip]
writeln!(s, " 7z/7zz : {} / {}", Self::process_output("7z", "i"), Self::process_output("7zz", "i"))?;
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;

writeln!(s, "\nClipboard")?;
#[rustfmt::skip]
Expand Down
1 change: 1 addition & 0 deletions yazi-plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ md-5 = { workspace = true }
mlua = { workspace = true }
parking_lot = { workspace = true }
ratatui = { workspace = true }
serde_json = { workspace = true }
shell-words = { workspace = true }
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { workspace = true }
Expand Down
77 changes: 30 additions & 47 deletions yazi-plugin/preset/plugins/file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,10 @@ end

function M:seek() end

-- TODO: remove this
local hovered_mime = ya.sync(function()
local h = cx.active.current.hovered
if not h then
return nil
elseif h.cha.is_dir then
return "inode/directory"
else
return h:mime()
end
end)

function M:spot(job)
local mime = hovered_mime()
if not mime then
return
end

local file = job.file
local spotter = PLUGIN.spotter(file.url, mime)
local previewer = PLUGIN.previewer(file.url, mime)
local fetchers = PLUGIN.fetchers(file.url, mime)
local preloaders = PLUGIN.preloaders(file.url, mime)

for i, v in ipairs(fetchers) do
fetchers[i] = v.cmd
end
for i, v in ipairs(preloaders) do
preloaders[i] = v.cmd
end

local rows = {}
local row = function(key, value)
local h = type(value) == "table" and #value or 1
rows[#rows + 1] = ui.Row({ key, value }):height(h)
end

rows[#rows + 1] = ui.Row({ "Metadata", "" }):style(ui.Style():fg("red"))
row(" Created:", file.cha.btime and os.date("%y/%m/%d %H:%M", math.floor(file.cha.btime)) or "-")
row(" Modified:", file.cha.mtime and os.date("%y/%m/%d %H:%M", math.floor(file.cha.mtime)) or "-")
row(" Mimetype:", mime)
rows[#rows + 1] = ui.Row({ { "", "Plugins" }, "" }):height(2):style(ui.Style():fg("red"))
row(" Spotter:", spotter and spotter.cmd or "-")
row(" Previewer:", previewer and previewer.cmd or "-")
row(" Fetchers:", #fetchers ~= 0 and fetchers or "-")
row(" Preloaders:", #preloaders ~= 0 and preloaders or "-")

ya.spot_table(
job,
ui.Table(rows)
ui.Table(self:spot_base(job))
:area(ui.Pos { "center", w = 60, h = 20 })
:row(1)
:col(1)
Expand All @@ -75,4 +29,33 @@ function M:spot(job)
)
end

function M:spot_base(job)
local url, cha = job.file.url, job.file.cha
local spotter = PLUGIN.spotter(url, job.mime)
local previewer = PLUGIN.previewer(url, job.mime)
local fetchers = PLUGIN.fetchers(url, job.mime)
local preloaders = PLUGIN.preloaders(url, job.mime)

for i, v in ipairs(fetchers) do
fetchers[i] = v.cmd
end
for i, v in ipairs(preloaders) do
preloaders[i] = v.cmd
end

return {
ui.Row({ "Base" }):style(ui.Style():fg("green")),
ui.Row { " Created:", cha.btime and os.date("%y/%m/%d %H:%M", math.floor(cha.btime)) or "-" },
ui.Row { " Modified:", cha.mtime and os.date("%y/%m/%d %H:%M", math.floor(cha.mtime)) or "-" },
ui.Row { " Mimetype:", job.mime },
ui.Row {},

ui.Row({ "Plugins" }):style(ui.Style():fg("green")),
ui.Row { " Spotter:", spotter and spotter.cmd or "-" },
ui.Row { " Previewer:", previewer and previewer.cmd or "-" },
ui.Row { " Fetchers:", #fetchers ~= 0 and fetchers or "-" },
ui.Row { " Preloaders:", #preloaders ~= 0 and preloaders or "-" },
}
end

return M
33 changes: 19 additions & 14 deletions yazi-plugin/preset/plugins/image.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,34 @@ function M:preload()
end

function M:spot(job)
local info = ya.image_info(job.file.url)

local rows = {}
local row = function(key, value)
local h = type(value) == "table" and #value or 1
rows[#rows + 1] = ui.Row({ key, value }):height(h)
end

row("Format:", tostring(info.format))
row("Width:", string.format("%dpx", info.w))
row("Height:", string.format("%dpx", info.h))
row("Color:", tostring(info.color))
local rows = self:spot_base(job)
rows[#rows + 1] = ui.Row {}

ya.spot_table(
job,
ui.Table(rows)
ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
:area(ui.Pos { "center", w = 60, h = 20 })
:row(job.skip)
:row(1)
:col(1)
:col_style(ui.Style():fg("blue"))
:cell_style(ui.Style():fg("yellow"):reverse())
:widths { ui.Constraint.Length(12), ui.Constraint.Fill(1) }
:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
)
end

function M:spot_base(job)
local info = ya.image_info(job.file.url)
if not info then
return {}
end

return {
ui.Row({ "Image" }):style(ui.Style():fg("green")),
ui.Row { " Format:", tostring(info.format) },
ui.Row { " Size:", string.format("%dx%d", info.w, info.h) },
ui.Row { " Color:", tostring(info.color) },
}
end

return M
96 changes: 81 additions & 15 deletions yazi-plugin/preset/plugins/video.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,96 @@ function M:preload()
return 1
end

local child, code = Command("ffmpegthumbnailer"):args({
"-q",
"6",
"-c",
"jpeg",
"-i",
tostring(self.file.url),
"-o",
tostring(cache),
"-t",
tostring(percent),
"-s",
tostring(PREVIEW.max_width),
local meta, err = self.list_meta(self.file.url, "format=duration")
if not meta then
ya.err(tostring(err))
return 0
end

local ss = math.floor(meta.format.duration * percent / 100)
local qv = 31 - math.floor(PREVIEW.image_quality * 0.3)
-- stylua: ignore
local child, code = Command("ffmpeg"):args({
"-v", "quiet", "-hwaccel", "auto",
"-skip_frame", "nokey", "-ss", ss,
"-an", "-sn", "-dn",
"-i", tostring(self.file.url),
"-vframes", 1,
"-q:v", qv,
"-vf", string.format("scale=%d:-2:flags=fast_bilinear", PREVIEW.max_width),
"-f", "image2",
"-y", tostring(cache),
}):spawn()

if not child then
ya.err("spawn `ffmpegthumbnailer` command returns " .. tostring(code))
ya.err("Spawn `ffmpeg` process returns " .. tostring(code))
return 0
end

local status = child:wait()
return status and status.success and 1 or 2
end

function M:spot(job) require("file"):spot(job) end
function M:spot(job)
local rows = self:spot_base(job)
rows[#rows + 1] = ui.Row {}

ya.spot_table(
job,
ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
:area(ui.Pos { "center", w = 60, h = 20 })
:row(1)
:col(1)
:col_style(ui.Style():fg("blue"))
:cell_style(ui.Style():fg("yellow"):reverse())
:widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) }
)
end

function M:spot_base(job)
local meta, err = self.list_meta(job.file.url, "format=duration:stream=codec_name,codec_type,width,height")
if not meta then
ya.err(tostring(err))
return {}
end

local dur = meta.format.duration
local rows = {
ui.Row({ "Video" }):style(ui.Style():fg("green")),
ui.Row { " Codec:", meta.streams[1].codec_name },
ui.Row { " Duration:", string.format("%d:%02d", math.floor(dur / 60), math.floor(dur % 60)) },
}

for i, s in ipairs(meta.streams) do
if s.codec_type == "video" then
rows[#rows + 1] = ui.Row { string.format(" Stream %d:", i), "video" }
rows[#rows + 1] = ui.Row { " Codec:", s.codec_name }
rows[#rows + 1] = ui.Row { " Size:", string.format("%dx%d", s.width, s.height) }
elseif s.codec_type == "audio" then
rows[#rows + 1] = ui.Row { string.format(" Stream %d:", i), "audio" }
rows[#rows + 1] = ui.Row { " Codec:", s.codec_name }
end
end
return rows
end

function M.list_meta(url, entries)
local output, err =
Command("ffprobe"):args({ "-v", "quiet", "-show_entries", entries, "-of", "json=c=1", tostring(url) }):output()
if not output then
return nil, Err("Spawn `ffprobe` process returns %s", err)
end

local t = ya.json_decode(output.stdout)
if not t then
return nil, Err("Failed to decode `ffprobe` output: %s", output.stdout)
elseif type(t) ~= "table" then
return nil, Err("Invalid `ffprobe` output: %s", output.stdout)
end

t.format = t.format or {}
t.streams = t.streams or {}
return t
end

return M
1 change: 0 additions & 1 deletion yazi-plugin/preset/setup.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
os.setlocale("")
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path

require("dds"):setup()
require("extract"):setup()
1 change: 1 addition & 0 deletions yazi-plugin/preset/ya.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
table.unpack = table.unpack or unpack
function Err(s, ...) return Error.custom(string.format(s, ...)) end

function ya.clamp(min, x, max)
if x < min then
Expand Down
25 changes: 25 additions & 0 deletions yazi-plugin/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use mlua::{Lua, MetaMethod, UserData, UserDataMethods};

pub enum Error {
Serde(serde_json::Error),
Custom(String),
}

impl Error {
pub fn install(lua: &Lua) -> mlua::Result<()> {
let new = lua.create_function(|_, msg: String| Ok(Error::Custom(msg)))?;

lua.globals().raw_set("Error", lua.create_table_from([("custom", new)])?)
}
}

impl UserData for Error {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::ToString, |_, me, ()| {
Ok(match me {
Error::Serde(e) => e.to_string(),
Error::Custom(s) => s.clone(),
})
});
}
}
1 change: 1 addition & 0 deletions yazi-plugin/src/isolate/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
crate::file::pour(&lua)?;
crate::url::pour(&lua)?;

crate::Error::install(&lua)?;
crate::loader::install_isolate(&lua)?;
crate::process::install(&lua)?;

Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/src/isolate/peek.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn peek(

if let Err(e) = result {
if !e.to_string().contains("Peek task cancelled") {
error!("{e:?}");
error!("{e}");
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/src/isolate/spot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub fn spot(

if let Err(e) = result {
if !e.to_string().contains("Spot task cancelled") {
error!("{e:?}");
error!("{e}");
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ yazi_macro::mod_pub!(
bindings, elements, external, file, fs, isolate, loader, process, pubsub, url, utils
);

yazi_macro::mod_flat!(clipboard config lua runtime);
yazi_macro::mod_flat!(clipboard config error lua runtime);

pub fn init() -> anyhow::Result<()> {
CLIPBOARD.with(<_>::default);
Expand Down
1 change: 1 addition & 0 deletions yazi-plugin/src/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> {
globals.raw_set("ya", crate::utils::compose(lua, false)?)?;
globals.raw_set("ps", crate::pubsub::compose(lua)?)?;

crate::Error::install(lua)?;
crate::bindings::Cha::install(lua)?;
crate::loader::install(lua)?;
crate::file::pour(lua)?;
Expand Down
Loading

0 comments on commit 34ca3c4

Please sign in to comment.