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.
Files changed (67) hide show
  1. package/.github/workflows/check-formatting.yml +32 -0
  2. package/README.md +31 -5
  3. package/package.json +1 -1
  4. package/src/cli.js +104 -9
  5. package/tests/liq/audio.liq +460 -0
  6. package/tests/liq/autocue.liq +1081 -0
  7. package/tests/liq/clock.liq +14 -0
  8. package/tests/liq/cron.liq +74 -0
  9. package/tests/liq/error.liq +48 -0
  10. package/tests/liq/extra/audio.liq +677 -0
  11. package/tests/liq/extra/audioscrobbler.liq +482 -0
  12. package/tests/liq/extra/deprecations.liq +976 -0
  13. package/tests/liq/extra/externals.liq +196 -0
  14. package/tests/liq/extra/fades.liq +260 -0
  15. package/tests/liq/extra/file.liq +66 -0
  16. package/tests/liq/extra/http.liq +160 -0
  17. package/tests/liq/extra/interactive.liq +917 -0
  18. package/tests/liq/extra/metadata.liq +75 -0
  19. package/tests/liq/extra/native.liq +201 -0
  20. package/tests/liq/extra/openai.liq +150 -0
  21. package/tests/liq/extra/server.liq +177 -0
  22. package/tests/liq/extra/source.liq +476 -0
  23. package/tests/liq/extra/spinitron.liq +272 -0
  24. package/tests/liq/extra/telnet.liq +266 -0
  25. package/tests/liq/extra/video.liq +59 -0
  26. package/tests/liq/extra/visualization.liq +68 -0
  27. package/tests/liq/fades.liq +941 -0
  28. package/tests/liq/ffmpeg.liq +605 -0
  29. package/tests/liq/file.liq +387 -0
  30. package/tests/liq/getter.liq +74 -0
  31. package/tests/liq/hls.liq +329 -0
  32. package/tests/liq/http.liq +1048 -0
  33. package/tests/liq/http_codes.liq +447 -0
  34. package/tests/liq/icecast.liq +58 -0
  35. package/tests/liq/io.liq +106 -0
  36. package/tests/liq/liquidsoap.liq +31 -0
  37. package/tests/liq/list.liq +440 -0
  38. package/tests/liq/log.liq +47 -0
  39. package/tests/liq/lufs.liq +295 -0
  40. package/tests/liq/math.liq +23 -0
  41. package/tests/liq/medialib.liq +752 -0
  42. package/tests/liq/metadata.liq +253 -0
  43. package/tests/liq/nfo.liq +258 -0
  44. package/tests/liq/null.liq +71 -0
  45. package/tests/liq/playlist.liq +1347 -0
  46. package/tests/liq/predicate.liq +106 -0
  47. package/tests/liq/process.liq +93 -0
  48. package/tests/liq/profiler.liq +5 -0
  49. package/tests/liq/protocols.liq +1139 -0
  50. package/tests/liq/ref.liq +28 -0
  51. package/tests/liq/replaygain.liq +135 -0
  52. package/tests/liq/request.liq +467 -0
  53. package/tests/liq/resolvers.liq +33 -0
  54. package/tests/liq/runtime.liq +70 -0
  55. package/tests/liq/server.liq +99 -0
  56. package/tests/liq/settings.liq +41 -0
  57. package/tests/liq/socket.liq +33 -0
  58. package/tests/liq/source.liq +362 -0
  59. package/tests/liq/sqlite.liq +161 -0
  60. package/tests/liq/stdlib.liq +172 -0
  61. package/tests/liq/string.liq +476 -0
  62. package/tests/liq/switches.liq +197 -0
  63. package/tests/liq/testing.liq +37 -0
  64. package/tests/liq/thread.liq +161 -0
  65. package/tests/liq/tracks.liq +100 -0
  66. package/tests/liq/utils.liq +81 -0
  67. 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