solorice/config/yazi/plugins/mediainfo.yazi/main.lua
Kristofers Solo 1a19d1112b
Update 2025-06-30
Update 2025-06-06

Update 2025-06-08

Update 2025-06-11

Update 2025-06-11

Update 2025-06-18

Update 2025-06-24
2025-07-16 15:34:39 +03:00

234 lines
6.5 KiB
Lua

--- @since 25.5.31
local skip_labels = {
["Complete name"] = true,
["CompleteName_Last"] = true,
["Unique ID"] = true,
["File size"] = true,
["Format/Info"] = true,
["Codec ID/Info"] = true,
["MD5 of the unencoded content"] = true,
}
local magick_image_mimes = {
avif = true,
hei = true,
heic = true,
heif = true,
["heif-sequence"] = true,
["heic-sequence"] = true,
jxl = true,
xml = true,
["svg+xml"] = true,
}
local M = {}
local suffix = "_mediainfo"
local function read_mediainfo_cached_file(file_path)
-- Open the file in read mode
local file = io.open(file_path, "r")
if file then
-- Read the entire file content
local content = file:read("*all")
file:close()
return content
end
end
function M:peek(job)
local start = os.clock()
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
if not job.mime then
return
end
local is_video = string.find(job.mime, "^video/")
local is_audio = string.find(job.mime, "^audio/")
local is_image = string.find(job.mime, "^image/")
local cache_img_url = (is_audio or is_image) and cache_img_url_no_skip
if is_video then
cache_img_url = ya.file_cache({ file = job.file, skip = math.min(90, job.skip) })
end
local ok, err = self:preload(job)
if not ok or err then
return
end
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. suffix
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
local output = read_mediainfo_cached_file(cache_mediainfo_path)
local lines = {}
local max_lines = math.floor(job.area.h / 2)
local last_line = 0
local is_wrap = rt.preview.wrap == "yes"
if output then
local max_width = math.max(1, job.area.w)
if output:match("^Error:") then
job.args.force_reload_mediainfo = true
local _ok, _err = self:preload(job)
if not _ok or _err then
return
end
output = read_mediainfo_cached_file(cache_mediainfo_path)
end
for str in output:gsub("\n+$", ""):gmatch("[^\n]*") do
local label, value = str:match("(.*[^ ]) +: (.*)")
local line
if label then
if not skip_labels[label] then
line = ui.Line({
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
})
end
elseif str ~= "General" then
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
end
if line then
local line_height = math.max(1, is_wrap and math.ceil(line:width() / max_width) or 1)
if (last_line + line_height) > job.skip then
table.insert(lines, line)
end
if (last_line + line_height) >= job.skip + max_lines then
last_line = job.skip + max_lines
break
end
last_line = last_line + line_height
end
end
end
local mediainfo_height = math.min(max_lines, last_line)
if (job.skip > 0 and #lines == 0) and (not is_video or (is_video and job.skip >= 90)) then
ya.emit("peek", { math.max(0, job.skip - max_lines), only_if = job.file.url, upper_bound = false })
return
end
local rendered_img_rect = cache_img_url
and ya.image_show(
cache_img_url,
ui.Rect({
x = job.area.x,
y = job.area.y,
w = job.area.w,
h = job.area.h - mediainfo_height,
})
)
or nil
local image_height = rendered_img_rect and rendered_img_rect.h or 0
ya.preview_widget(job, {
ui.Text(lines)
:area(ui.Rect({
x = job.area.x,
y = job.area.y + image_height,
w = job.area.w,
h = job.area.h - image_height,
}))
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
})
end
function M:seek(job)
local h = cx.active.current.hovered
if h and h.url == job.file.url then
ya.emit("peek", {
math.max(0, cx.active.preview.skip + job.units),
only_if = job.file.url,
})
end
end
function M:preload(job)
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
local cache_img_url_no_skip_cha = cache_img_url_no_skip and fs.cha(cache_img_url_no_skip)
if not cache_img_url_no_skip then
return true
end
local cache_mediainfo_url = Url(tostring(cache_img_url_no_skip) .. suffix)
local err_msg = ""
-- seekable mimetype
if job.mime and string.find(job.mime, "^video/") then
local video = require("video")
local cache_img_status, video_preload_err = video:preload({ file = job.file, skip = math.min(90, job.skip) })
if not cache_img_status and video_preload_err then
err_msg = err_msg
.. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg")
end
end
-- none-seekable mimetype
if cache_img_url_no_skip and (not cache_img_url_no_skip_cha or cache_img_url_no_skip_cha.len <= 0) then
-- audio
if job.mime and string.find(job.mime, "^audio/") then
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
local status, _ = Command("ffmpeg"):arg({
"-v",
"quiet",
"-threads",
1,
"-hwaccel",
"auto",
"-skip_frame",
"nokey",
"-an",
"-sn",
"-dn",
"-i",
tostring(job.file.url),
"-vframes",
1,
"-q:v",
qv,
"-vf",
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
"-f",
"image2",
"-y",
tostring(cache_img_url_no_skip),
}):status()
-- NOTE: Ignore this err msg because some audio types doesn't have cover image
--
-- if not status or not status.success then
-- err_msg = err_msg
-- .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg")
-- end
-- image
elseif job.mime and string.find(job.mime, "^image/") then
local svg_plugin_ok, svg_plugin = pcall(require, "svg")
local mime = job.mime:match(".*/(.*)$")
local image = magick_image_mimes[mime]
and ((mime == "svg+xml" and svg_plugin_ok) and svg_plugin or require("magick"))
or require("image")
local no_skip_job = { skip = 0, file = job.file }
-- image = ya.dict_merge(image, no_skip_job)
local cache_img_status, image_preload_err = image:preload(no_skip_job)
if not cache_img_status and image_preload_err then
err_msg = err_msg .. "Failed to cache image , check cache folder permissions\n"
end
end
end
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
return true
end
local cmd = "mediainfo"
local output, err = Command(cmd):arg({ tostring(job.file.url) }):stdout(Command.PIPED):output()
if err then
err_msg = err_msg .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", cmd, cmd)
end
return fs.write(
cache_mediainfo_url,
(err_msg ~= "" and "Error: " .. err_msg or "") .. (output and output.stdout or "")
)
end
return M