--- @since 26.1.22 local M = {} local const = require(".const") local utils = require(".utils") function M:peek(job) local preload_status, preload_err = self:preload(job) -- Stop if preload failed if not preload_status then return end local cache_img_url = ya.file_cache({ skip = 0, args = job.args, file = job.file, area = job.area, }) local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 }) local hide_metadata = utils.get_state(const.STATE_KEY.hide_metadata) local mediainfo_height = 0 local lines = {} local limit = job.area.h local last_line = 0 local EOF_mediainfo = true local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES if not hide_metadata then local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix local output = utils.read_mediainfo_cached_file(cache_mediainfo_path) if output then local max_width = math.max(1, job.area.w) if output:match("^Error:") then job.args.force_reload_mediainfo = true preload_status, preload_err = self:preload(job) if not preload_status or preload_err then return end output = utils.read_mediainfo_cached_file(cache_mediainfo_path) end output = output:gsub("\n+$", "") local iter = output:gmatch("[^\n]*") local str = iter() while str ~= nil do local next_str = iter() local label, value = str:match("(.*[^ ]) +: (.*)") local line if label then if not const.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 = ui.height and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap }) or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1)) if next_str == nil and line_height == 1 then EOF_mediainfo = true end if (last_line + line_height) > job.skip then table.insert(lines, line) end if (last_line + line_height) >= job.skip + limit then last_line = job.skip + limit EOF_mediainfo = false break end last_line = last_line + line_height end str = next_str end end mediainfo_height = math.min(limit, last_line) end if not hide_metadata and EOF_mediainfo and #lines == 0 and job.skip > 0 then ya.emit("peek", { math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))), only_if = job.file.url, upper_bound = true, }) return end utils.force_render() -- NOTE: Hacky way to prevent image overlap with old metadata area if utils.get_state(const.STATE_KEY.prev_metadata_area) then ya.preview_widget(job, { ui.Clear(ui.Rect(utils.get_state(const.STATE_KEY.prev_metadata_area))), }) end local rendered_img_rect = cache_img_url and fs.cha(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 = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h, }) ) or nil local image_height = rendered_img_rect and rendered_img_rect.h or 0 -- Handle image preload error if preload_err then table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red"))) end 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), }) -- NOTE: Hacky way to prevent image overlap with old metadata area utils.set_state(const.STATE_KEY.prev_metadata_area, not hide_metadata and { x = job.area.x, y = job.area.y + image_height, w = job.area.w, h = job.area.h - image_height, } or nil) end function M:preload(job) local cmd = "mediainfo" local err_msg = "" local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url)) -- NOTE: Preload image local mime = job.mime:match(".*/(.*)$") local is_svg = mime == "svg+xml" local is_magick = const.magick_image_mimes[mime] local no_skip_job = { skip = 0, file = job.file, args = job.args, area = job.area } local cache_img_url = ya.file_cache(no_skip_job) local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url) -- NOTE: Only generate preview image when cache image is not exist if not cache_img_url_cha or cache_img_url_cha.len <= 0 then local cache_img_status, image_preload_err if not is_valid_utf8_path then -- NOTE: Case not valid utf8 path, use trick to generate preview image if is_svg then local cache_img_url_tmp = Url(cache_img_url .. ".tmp") if fs.cha(cache_img_url_tmp) then fs.remove("file", cache_img_url_tmp) end local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp) or fs.unique_name(cache_img_url_tmp) -- svg under invalid utf8 path cache_img_status, image_preload_err = require("magick") .with_limit() :arg({ "-background", "none", tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url), "-auto-orient", "-strip", string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height), "-quality", rt.preview.image_quality, string.format("PNG32:%s", tostring(tmp_file_path)), }) :status() if cache_img_status then os.rename(tostring(tmp_file_path), tostring(cache_img_url)) end end else -- NOTE: Case valid utf8 path, use image, svg, or magick module local image_module = is_svg and "svg" or (is_magick and "magick" or "image") cache_img_status, image_preload_err = require(image_module):preload(no_skip_job) end if not cache_img_status and image_preload_err then ya.dbg("mediainfo", image_preload_err) err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "") end end -- NOTE: Get mediainfo and save to cache folder local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix) local cache_mediainfo_cha = fs.cha(cache_mediainfo_url) -- Case peek function called preload to refetch mediainfo if cache_mediainfo_cha and not job.args.force_reload_mediainfo then return true, err_msg ~= "" and ("Error: " .. err_msg) or nil end local output, err if is_valid_utf8_path then output, err = Command(cmd) :arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) }) :output() else cmd = "cd " .. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent)) .. " && " .. cmd .. " " .. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name)) output, err = Command(const.SHELL):arg({ "-c", cmd }):output() end if err then ya.dbg("mediainfo", tostring(err)) err_msg = err_msg .. string.format("Failed to start `%s`. \n 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