summaryrefslogtreecommitdiff
path: root/yazi/plugins/preview-audio.yazi/main.lua
diff options
context:
space:
mode:
Diffstat (limited to 'yazi/plugins/preview-audio.yazi/main.lua')
-rw-r--r--yazi/plugins/preview-audio.yazi/main.lua149
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