1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
|