liquidsoap-prettier 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/check-formatting.yml +32 -0
- package/README.md +31 -5
- package/package.json +1 -1
- package/src/cli.js +104 -9
- package/tests/liq/audio.liq +460 -0
- package/tests/liq/autocue.liq +1081 -0
- package/tests/liq/clock.liq +14 -0
- package/tests/liq/cron.liq +74 -0
- package/tests/liq/error.liq +48 -0
- package/tests/liq/extra/audio.liq +677 -0
- package/tests/liq/extra/audioscrobbler.liq +482 -0
- package/tests/liq/extra/deprecations.liq +976 -0
- package/tests/liq/extra/externals.liq +196 -0
- package/tests/liq/extra/fades.liq +260 -0
- package/tests/liq/extra/file.liq +66 -0
- package/tests/liq/extra/http.liq +160 -0
- package/tests/liq/extra/interactive.liq +917 -0
- package/tests/liq/extra/metadata.liq +75 -0
- package/tests/liq/extra/native.liq +201 -0
- package/tests/liq/extra/openai.liq +150 -0
- package/tests/liq/extra/server.liq +177 -0
- package/tests/liq/extra/source.liq +476 -0
- package/tests/liq/extra/spinitron.liq +272 -0
- package/tests/liq/extra/telnet.liq +266 -0
- package/tests/liq/extra/video.liq +59 -0
- package/tests/liq/extra/visualization.liq +68 -0
- package/tests/liq/fades.liq +941 -0
- package/tests/liq/ffmpeg.liq +605 -0
- package/tests/liq/file.liq +387 -0
- package/tests/liq/getter.liq +74 -0
- package/tests/liq/hls.liq +329 -0
- package/tests/liq/http.liq +1048 -0
- package/tests/liq/http_codes.liq +447 -0
- package/tests/liq/icecast.liq +58 -0
- package/tests/liq/io.liq +106 -0
- package/tests/liq/liquidsoap.liq +31 -0
- package/tests/liq/list.liq +440 -0
- package/tests/liq/log.liq +47 -0
- package/tests/liq/lufs.liq +295 -0
- package/tests/liq/math.liq +23 -0
- package/tests/liq/medialib.liq +752 -0
- package/tests/liq/metadata.liq +253 -0
- package/tests/liq/nfo.liq +258 -0
- package/tests/liq/null.liq +71 -0
- package/tests/liq/playlist.liq +1347 -0
- package/tests/liq/predicate.liq +106 -0
- package/tests/liq/process.liq +93 -0
- package/tests/liq/profiler.liq +5 -0
- package/tests/liq/protocols.liq +1139 -0
- package/tests/liq/ref.liq +28 -0
- package/tests/liq/replaygain.liq +135 -0
- package/tests/liq/request.liq +467 -0
- package/tests/liq/resolvers.liq +33 -0
- package/tests/liq/runtime.liq +70 -0
- package/tests/liq/server.liq +99 -0
- package/tests/liq/settings.liq +41 -0
- package/tests/liq/socket.liq +33 -0
- package/tests/liq/source.liq +362 -0
- package/tests/liq/sqlite.liq +161 -0
- package/tests/liq/stdlib.liq +172 -0
- package/tests/liq/string.liq +476 -0
- package/tests/liq/switches.liq +197 -0
- package/tests/liq/testing.liq +37 -0
- package/tests/liq/thread.liq +161 -0
- package/tests/liq/tracks.liq +100 -0
- package/tests/liq/utils.liq +81 -0
- package/tests/liq/video.liq +918 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
let settings.lufs = ()
|
|
2
|
+
|
|
3
|
+
let settings.lufs.track_gain_target =
|
|
4
|
+
settings.make(
|
|
5
|
+
description="Target LUFS All available autocue implementations",
|
|
6
|
+
-16.
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
let settings.lufs.integrated_metadata =
|
|
10
|
+
settings.make(
|
|
11
|
+
description="Metadata used to store integrated LUFS",
|
|
12
|
+
"liq_integrated_lufs"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
let settings.lufs.decoding_ratio =
|
|
16
|
+
settings.make(
|
|
17
|
+
description="Decoding ratio used when decoding integrated LUFS from files",
|
|
18
|
+
50.
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
let settings.lufs.true_peak_max =
|
|
22
|
+
settings.make(
|
|
23
|
+
description="Maximum allowed true peak (dBTP) after LUFS normalization. \
|
|
24
|
+
Tracks whose true peak would exceed this after gain application will have \
|
|
25
|
+
their gain reduced accordingly. EBU R128 mandates -1.0 dBTP.",
|
|
26
|
+
-1.0
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
let file.lufs = ()
|
|
30
|
+
|
|
31
|
+
# Compute the LUFS of a file (in dB).
|
|
32
|
+
# @category File
|
|
33
|
+
# @param ~id Force the value of the source ID.
|
|
34
|
+
# @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing lufs tags. Defaults to `settings.lufs.decoding_ratio` when `null`
|
|
35
|
+
# @param file_name File name.
|
|
36
|
+
# @flag hidden
|
|
37
|
+
def file.lufs.compute(~ratio=null, file_name) =
|
|
38
|
+
ratio = ratio ?? settings.lufs.decoding_ratio()
|
|
39
|
+
_request = request.create(resolve_metadata=false, file_name)
|
|
40
|
+
if
|
|
41
|
+
request.resolve(_request)
|
|
42
|
+
then
|
|
43
|
+
get_lufs = ref(fun () -> null)
|
|
44
|
+
def process(s) =
|
|
45
|
+
s = lufs(s)
|
|
46
|
+
get_lufs := {s.lufs_integrated()}
|
|
47
|
+
s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
request.process(ratio=ratio, process=process, _request)
|
|
51
|
+
|
|
52
|
+
fn = get_lufs()
|
|
53
|
+
fn()
|
|
54
|
+
else
|
|
55
|
+
null
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Compute the integrated LUFS and true peak of a file in a single decoding pass.
|
|
60
|
+
# Returns a record `{lufs, true_peak}` (both in dB / dBTP), or `null` on failure.
|
|
61
|
+
# @category File
|
|
62
|
+
# @param ~ratio Decoding ratio. Defaults to `settings.lufs.decoding_ratio` when `null`.
|
|
63
|
+
# @param file_name File name.
|
|
64
|
+
# @flag hidden
|
|
65
|
+
def file.lufs.compute_with_peak(~ratio=null, file_name) =
|
|
66
|
+
ratio = ratio ?? settings.lufs.decoding_ratio()
|
|
67
|
+
_request = request.create(resolve_metadata=false, file_name)
|
|
68
|
+
if
|
|
69
|
+
request.resolve(_request)
|
|
70
|
+
then
|
|
71
|
+
get_lufs = ref(fun () -> null)
|
|
72
|
+
get_tp = ref(fun () -> null)
|
|
73
|
+
def process(s) =
|
|
74
|
+
s = lufs(s)
|
|
75
|
+
get_lufs := {s.lufs_integrated()}
|
|
76
|
+
get_tp := {s.true_peak()}
|
|
77
|
+
s
|
|
78
|
+
end
|
|
79
|
+
request.process(ratio=ratio, process=process, _request)
|
|
80
|
+
lufs_fn = get_lufs()
|
|
81
|
+
tp_fn = get_tp()
|
|
82
|
+
lufs_val = lufs_fn()
|
|
83
|
+
tp_val = tp_fn()
|
|
84
|
+
if
|
|
85
|
+
null.defined(lufs_val) and null.defined(tp_val)
|
|
86
|
+
then
|
|
87
|
+
{lufs = null.get(lufs_val), true_peak = null.get(tp_val)}
|
|
88
|
+
else
|
|
89
|
+
null
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
null
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Extract the LUFS from the metadata (in dB).
|
|
97
|
+
# @category Metadata
|
|
98
|
+
# @param _metadata Metadata from which the LUFS should be extracted.
|
|
99
|
+
def metadata.lufs(_metadata) =
|
|
100
|
+
k = settings.lufs.integrated_metadata()
|
|
101
|
+
if
|
|
102
|
+
list.assoc.mem(k, _metadata)
|
|
103
|
+
then
|
|
104
|
+
lufs_metadata = _metadata[k]
|
|
105
|
+
match = r/([+-]?\d*\.?\d*)/.exec(lufs_metadata)
|
|
106
|
+
try
|
|
107
|
+
float_of_string(list.assoc(1, match))
|
|
108
|
+
catch _ do
|
|
109
|
+
null
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
null
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get the LUFS for a file (in dB).
|
|
117
|
+
# @category File
|
|
118
|
+
# @param ~id Force the value of the source ID.
|
|
119
|
+
# @param ~compute Compute LUFS if metadata tag is empty.
|
|
120
|
+
# @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing lufs tags. Defaults to `settings.lufs.decoding_ratio` when `null`.
|
|
121
|
+
# @param file_name File name.
|
|
122
|
+
def replaces file.lufs(~id=null, ~compute=true, ~ratio=null, file_name) =
|
|
123
|
+
id = string.id.default(default="file.lufs", id)
|
|
124
|
+
file_name_quoted = string.quote(file_name)
|
|
125
|
+
ratio = ratio ?? settings.lufs.decoding_ratio()
|
|
126
|
+
|
|
127
|
+
_metadata = file.metadata(exclude=decoder.metadata.reentrant(), file_name)
|
|
128
|
+
gain = metadata.lufs(_metadata)
|
|
129
|
+
|
|
130
|
+
if
|
|
131
|
+
gain != null
|
|
132
|
+
then
|
|
133
|
+
log.info(
|
|
134
|
+
label=id,
|
|
135
|
+
"Detected track lufs #{gain} dB for #{file_name_quoted}."
|
|
136
|
+
)
|
|
137
|
+
gain
|
|
138
|
+
elsif
|
|
139
|
+
compute
|
|
140
|
+
then
|
|
141
|
+
log.info(
|
|
142
|
+
label=id,
|
|
143
|
+
"Computing integrated LUFS for #{file_name_quoted}."
|
|
144
|
+
)
|
|
145
|
+
start_time = time()
|
|
146
|
+
gain = file.lufs.compute(ratio=ratio, file_name)
|
|
147
|
+
elapsed_time = time() - start_time
|
|
148
|
+
if
|
|
149
|
+
gain != null
|
|
150
|
+
then
|
|
151
|
+
log.info(
|
|
152
|
+
label=id,
|
|
153
|
+
"Computed integrated LUFS of #{gain} dB for #{file_name_quoted} (time: #{
|
|
154
|
+
elapsed_time
|
|
155
|
+
} s)."
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
gain
|
|
159
|
+
else
|
|
160
|
+
null
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Enable LUFS metadata resolver. This resolver will process any file
|
|
165
|
+
# decoded by Liquidsoap and add a `liq_normalize_track_gain` metadata when this
|
|
166
|
+
# value could be computed (key is configurable via `settings.normalize_track_gain_metadata`).
|
|
167
|
+
# For a finer-grained replay gain processing, use the `lufs_track_gain:` protocol.
|
|
168
|
+
#
|
|
169
|
+
# When `true_peak=true`, both integrated LUFS and true peak are measured
|
|
170
|
+
# in a single pass. The gain written to `liq_normalize_track_gain` is then
|
|
171
|
+
# adjusted so the true peak after amplification does not exceed
|
|
172
|
+
# `settings.lufs.true_peak_max` (default -1.0 dBTP, per EBU R128):
|
|
173
|
+
#
|
|
174
|
+
# - If true peak after LUFS gain <= tp_max: gain is used as-is.
|
|
175
|
+
# - If true peak after LUFS gain is in (tp_max, tp_max+0.5]: gain is reduced
|
|
176
|
+
# by `tp_after_gain - tp_max` so that true peak lands exactly at tp_max.
|
|
177
|
+
# Maximum attenuation in this range is 0.5 dB.
|
|
178
|
+
# - If true peak after LUFS gain > tp_max+0.5: same correction is applied, but
|
|
179
|
+
# a warning is logged. A dynamic limiter may be needed for these tracks.
|
|
180
|
+
#
|
|
181
|
+
# When `true_peak=false` (default), only integrated LUFS is measured and the
|
|
182
|
+
# gain is set purely from LUFS — no true peak correction is applied. This
|
|
183
|
+
# preserves the original behaviour.
|
|
184
|
+
#
|
|
185
|
+
# When `compute=false`, the existing LUFS metadata is used without any
|
|
186
|
+
# computation (same behaviour as before).
|
|
187
|
+
#
|
|
188
|
+
# @param ~compute Compute LUFS if metadata tag is empty.
|
|
189
|
+
# @param ~true_peak Also measure true peak and apply EBU R128 TP correction to the gain.
|
|
190
|
+
# @param ~ratio Decoding ratio. A value of `50.` means try to decode the file `50x` faster than real time, if possible. Defaults to `settings.lufs.decoding_ratio` when `null`.
|
|
191
|
+
# @category Liquidsoap
|
|
192
|
+
def enable_lufs_track_gain_metadata(
|
|
193
|
+
~compute=true,
|
|
194
|
+
~true_peak=true,
|
|
195
|
+
~ratio=null
|
|
196
|
+
) =
|
|
197
|
+
ratio = ratio ?? settings.lufs.decoding_ratio()
|
|
198
|
+
def lufs_metadata(~metadata:_, file_name) =
|
|
199
|
+
if
|
|
200
|
+
compute and true_peak
|
|
201
|
+
then
|
|
202
|
+
# Single-pass scan: measure both LUFS and true peak, apply TP correction.
|
|
203
|
+
result = file.lufs.compute_with_peak(ratio=ratio, file_name)
|
|
204
|
+
if
|
|
205
|
+
result != null
|
|
206
|
+
then
|
|
207
|
+
let {lufs = measured_lufs, true_peak = measured_tp} = null.get(result)
|
|
208
|
+
target = settings.lufs.track_gain_target()
|
|
209
|
+
tp_max = settings.lufs.true_peak_max()
|
|
210
|
+
gain_lufs = target - measured_lufs
|
|
211
|
+
tp_after = measured_tp + gain_lufs
|
|
212
|
+
|
|
213
|
+
# x is the correction: negative (attenuation) when tp_after > tp_max.
|
|
214
|
+
x = tp_max - tp_after
|
|
215
|
+
|
|
216
|
+
final_gain =
|
|
217
|
+
if
|
|
218
|
+
tp_after <= tp_max
|
|
219
|
+
then
|
|
220
|
+
# Case 1: true peak within limit, LUFS target is met exactly.
|
|
221
|
+
gain_lufs
|
|
222
|
+
elsif
|
|
223
|
+
tp_after <= tp_max + 0.5
|
|
224
|
+
then
|
|
225
|
+
# Case 2: mild overshoot (up to 0.5 dB), correct by reducing gain.
|
|
226
|
+
# Resulting LUFS-I will be slightly below target (max 0.5 LU deviation),
|
|
227
|
+
# but within allowed variance of ± 1 dB.
|
|
228
|
+
gain_lufs + x
|
|
229
|
+
else
|
|
230
|
+
# Case 3: overshoot exceeds 0.5 dB. Capping gain reduction at 0.5 dB
|
|
231
|
+
# to keep LUFS-I no lower than target - 0.5 LU (EBU R128 floor).
|
|
232
|
+
# Remaining TP excess must be handled by a limiter.
|
|
233
|
+
log.important(
|
|
234
|
+
label="lufs",
|
|
235
|
+
"#{string.quote(file_name)}: true peak after normalization would \
|
|
236
|
+
be #{tp_after} dBTP (#{tp_after - tp_max} dB over limit). Gain \
|
|
237
|
+
correction capped at 0.5 dB to preserve LUFS-I; a limiter is \
|
|
238
|
+
required for this track."
|
|
239
|
+
)
|
|
240
|
+
gain_lufs - 0.5
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
[
|
|
244
|
+
(
|
|
245
|
+
settings.normalize_track_gain_metadata(),
|
|
246
|
+
"#{final_gain} dB"
|
|
247
|
+
),
|
|
248
|
+
(
|
|
249
|
+
settings.lufs.integrated_metadata(),
|
|
250
|
+
"#{measured_lufs} LUFS"
|
|
251
|
+
),
|
|
252
|
+
("lufs_true_peak", "#{measured_tp}")
|
|
253
|
+
]
|
|
254
|
+
else
|
|
255
|
+
[]
|
|
256
|
+
end
|
|
257
|
+
elsif
|
|
258
|
+
compute
|
|
259
|
+
then
|
|
260
|
+
# LUFS-only scan: no true peak measurement or correction (default behaviour).
|
|
261
|
+
# Uses file.lufs() which checks for existing metadata before computing,
|
|
262
|
+
# matching the original behaviour exactly.
|
|
263
|
+
gain = file.lufs(compute=true, ratio=ratio, file_name)
|
|
264
|
+
if
|
|
265
|
+
gain != null
|
|
266
|
+
then
|
|
267
|
+
[
|
|
268
|
+
(
|
|
269
|
+
settings.normalize_track_gain_metadata(),
|
|
270
|
+
"#{settings.lufs.track_gain_target() - null.get(gain)} dB"
|
|
271
|
+
)
|
|
272
|
+
]
|
|
273
|
+
else
|
|
274
|
+
[]
|
|
275
|
+
end
|
|
276
|
+
else
|
|
277
|
+
# compute=false: use existing LUFS metadata, no computation at all.
|
|
278
|
+
gain = file.lufs(compute=false, ratio=ratio, file_name)
|
|
279
|
+
if
|
|
280
|
+
gain != null
|
|
281
|
+
then
|
|
282
|
+
[
|
|
283
|
+
(
|
|
284
|
+
settings.normalize_track_gain_metadata(),
|
|
285
|
+
"#{settings.lufs.track_gain_target() - null.get(gain)} dB"
|
|
286
|
+
)
|
|
287
|
+
]
|
|
288
|
+
else
|
|
289
|
+
[]
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
decoder.metadata.add(reentrant=true, "lufs_track_gain", lufs_metadata)
|
|
295
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Compute the minimum of two values.
|
|
2
|
+
# @category Math
|
|
3
|
+
def min(a, b) =
|
|
4
|
+
if a <= b then a else b end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Compute the maximum of two values.
|
|
8
|
+
# @category Math
|
|
9
|
+
def max(a, b) =
|
|
10
|
+
if a >= b then a else b end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Convert linear scale into decibels.
|
|
14
|
+
# @category Math
|
|
15
|
+
def dB_of_lin(x) =
|
|
16
|
+
20. * log10(x)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Convert decibels into linear scale.
|
|
20
|
+
# @category Math
|
|
21
|
+
def lin_of_dB(x) =
|
|
22
|
+
pow(10., x / 20.)
|
|
23
|
+
end
|