diff options
Diffstat (limited to 'yazi/plugins/preview-audio.yazi/main.lua')
| -rw-r--r-- | yazi/plugins/preview-audio.yazi/main.lua | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/yazi/plugins/preview-audio.yazi/main.lua b/yazi/plugins/preview-audio.yazi/main.lua new file mode 100644 index 0000000..5703e0a --- /dev/null +++ b/yazi/plugins/preview-audio.yazi/main.lua @@ -0,0 +1,149 @@ +--- @since 25.12.29 + +local M = {} + +local audio_ffprobe = function(file) + -- stylua: ignore + local cmd = Command('ffprobe'):arg { + '-v', 'quiet', + '-show_entries', 'stream_tags:format:stream', + '-of', 'json=c=1', + file.name + } + + local output, err = cmd:output() + if not output then + return {}, Err('Failed to start `ffprobe`, error: %s', err) + end + + local json = ya.json_decode(output.stdout) + if not json then + return nil, Err('Failed to decode `ffprobe` output: %s', output.stdout) + elseif type(json) ~= 'table' then + return nil, Err('Invalid `ffprobe` output: %s', output.stdout) + end + ya.dbg(json) + + local audio_stream = json.streams[1] + local tags = json.format.tags or audio_stream.tags or audio_stream + local duration = json.format.duration + if duration then + duration = tonumber(duration) + duration = string.format('%d:%02d', math.floor(duration / 60), math.floor(duration % 60)) + end + + local data = {} + local title, album, aar, ar = + tags.TITLE or tags.title or '', + tags.ALBUM or tags.album or '', + tags.ALBUM_ARTIST or tags.album_artist or '', + tags.ARTIST or tags.artist or '' + + if title .. album .. ar .. aar ~= '' then + local img_stream = json.streams[2] + local date = tags.DATE or tags.date or '' + local c = '' + local artist = ar + + if tags.ORIGINALDATE and tags.ORIGINALDATE ~= '' then + date = date .. ' / ' .. tags.ORIGINALDATE + end + if (aar ~= '') and (aar ~= ar) then + artist = artist .. ' / ' .. aar + end + if img_stream then + c = img_stream.codec_name .. ' ' .. img_stream.width .. 'x' .. img_stream.height + end + + data = { + ui.Line(string.format('%s - %s', artist, title)), + ui.Line(string.format('%s: %s', 'Duration', duration)), + ui.Line(string.format('%s: %s', 'Album', album)), + ui.Line(string.format('%s: %s', 'Genre', tags.GENRE or tags.genre or 'No genre')), + ui.Line(string.format('%s: %s', 'Date', date)), + c ~= '' and ui.Line(string.format('%s: %s', 'Cover art', c)) or nil, + } + end + + local bd = audio_stream.bits_per_raw_sample or '1' + local sr = audio_stream.sample_rate + if sr then + sr = string.format('%.1fkhz', sr / 1000) + end + local br = tonumber((audio_stream.bit_rate or json.format.bit_rate or 0) // 1000) .. ' kb/s' + ya.dbg(bd, sr) + for _, item in ipairs({ + '', + '# Specs', + string.format('%s: %s', 'Format', json.format.format_name), + string.format('%s: %sbit / %s', 'Quality', bd, sr), + string.format('%s: %s', 'BitRate', br), + string.format('%s: %s', 'Channels', tostring(audio_stream.channels or '?')), + }) do + data[#data + 1] = ui.Line(item) + end + + return data +end + +function M:peek(job) + local start, cache = os.clock(), ya.file_cache(job) + local ok, err = self:preload(job) + + local img_area + if cache and fs.cha(cache) then + ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock())) + img_area, err = ya.image_show(cache, job.area) + end + + ya.preview_widget(job, { + ui.Text(audio_ffprobe(job.file)):area(ui.Rect { + x = job.area.x, + y = job.area.y + (img_area and img_area.h or 0), + w = job.area.w, + h = job.area.h, + }), + }) +end + +function M:seek(job) end + +function M:preload(job) + local cache = ya.file_cache(job) + if not cache then + return true + end + + local cha = fs.cha(cache) + if cha and cha.len > 0 then + return true + end + + -- stylua: ignore + local output, err = Command('ffmpeg') + :arg({ + '-hide_banner', + '-loglevel', 'warning', + '-i', tostring(job.file.url), + '-an', + -- '-vcodec', 'copy', + string.format('%s.jpg', cache), + }) + :stderr(Command.PIPED) + :output() + + if not output then + return true, Err('Failed to start `ffmpeg`, error: %s', err) + elseif not output.status.success then + return true, Err('Failed to get image, stderr: %s', output.stderr) + end + + local ok, err = os.rename(string.format('%s.jpg', cache), tostring(cache)) + if ok then + return true + else + return true, Err('Failed to rename: %s', err) + end +end + +return M |
