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,70 @@
1
+ # @docof runtime.memory
2
+ def replaces runtime.memory() =
3
+ x =
4
+ {
5
+ ...runtime.memory(),
6
+ process_managed_memory =
7
+ runtime.gc.quick_stat().heap_words * runtime.sys.word_size / 8
8
+ }
9
+
10
+ let x.pretty =
11
+ {
12
+ process_managed_memory =
13
+ runtime.memory.prettify_bytes(x.process_managed_memory),
14
+ total_virtual_memory =
15
+ runtime.memory.prettify_bytes(x.total_virtual_memory),
16
+ total_physical_memory =
17
+ runtime.memory.prettify_bytes(x.total_physical_memory),
18
+ total_used_virtual_memory =
19
+ runtime.memory.prettify_bytes(x.total_used_virtual_memory),
20
+ total_used_physical_memory =
21
+ runtime.memory.prettify_bytes(x.total_used_physical_memory),
22
+ process_virtual_memory =
23
+ runtime.memory.prettify_bytes(x.process_virtual_memory),
24
+ process_physical_memory =
25
+ runtime.memory.prettify_bytes(x.process_physical_memory),
26
+ process_private_memory =
27
+ runtime.memory.prettify_bytes(x.process_private_memory),
28
+ process_swapped_memory =
29
+ runtime.memory.prettify_bytes(x.process_swapped_memory)
30
+ }
31
+
32
+ x
33
+ end
34
+
35
+ let runtime.cpu = ()
36
+ %ifdef process.time
37
+ # Create a function returning CPU usage (in `float` percent so
38
+ # `0.2` means `20%`) since the last time it was called.
39
+ # @category Liquidsoap
40
+ def runtime.cpu.usage_getter() =
41
+ t = ref(time())
42
+ let {user, system} = process.time()
43
+ u = ref(user)
44
+ s = ref(system)
45
+
46
+ def f() =
47
+ t' = time()
48
+ delta = t' - t()
49
+ let {user, system} = process.time()
50
+ u' = user - u()
51
+ s' = system - s()
52
+ t := t'
53
+ u := user
54
+ s := system
55
+ {user = u' / delta, system = s' / delta, total = (u' + s') / delta}
56
+ end
57
+
58
+ f
59
+ end
60
+ %endif
61
+
62
+ # Set the current time zone. This is
63
+ # equivalent to setting the `TZ` environment
64
+ # variable.
65
+ # @category Time
66
+ def time.zone.set(tz) =
67
+ environment.set("TZ", tz)
68
+ end
69
+
70
+ runtime.gc.set(runtime.gc.get().{space_overhead = 80})
@@ -0,0 +1,99 @@
1
+ # Enable telnet server.
2
+ # @category Interaction
3
+ # @param ~port Port on which we should listen.
4
+ def server.telnet(~port=1234) =
5
+ settings.server.telnet.port := port
6
+ settings.server.telnet := true
7
+ end
8
+
9
+ server.register(
10
+ namespace="runtime.gc",
11
+ description="Run a full memory collection",
12
+ "full_major",
13
+ fun (_) ->
14
+ begin
15
+ runtime.gc.full_major()
16
+ "Done!"
17
+ end
18
+ )
19
+
20
+ server.register(
21
+ namespace="runtime.gc",
22
+ description="Compact OCaml memory",
23
+ "compact",
24
+ fun (_) ->
25
+ begin
26
+ runtime.gc.compact()
27
+ "Done!"
28
+ end
29
+ )
30
+
31
+ server.register(
32
+ namespace="runtime",
33
+ description="Return a description of the memory used by the process",
34
+ "memory",
35
+ fun (_) ->
36
+ begin
37
+ let {
38
+ process_managed_memory,
39
+ process_physical_memory,
40
+ process_private_memory,
41
+ process_swapped_memory
42
+ } = runtime.memory().pretty
43
+ "Physical memory: #{process_physical_memory}\nPrivate memory: #{
44
+ process_private_memory
45
+ }\nManaged memory: #{process_managed_memory}\nSwapped memory: #{
46
+ process_swapped_memory
47
+ }"
48
+ end
49
+ )
50
+
51
+ server.register(
52
+ namespace="clock",
53
+ description="Dump a description of the current clocks and sources",
54
+ "dump",
55
+ fun (_) -> clock.dump()
56
+ )
57
+
58
+ server.register(
59
+ namespace="clock",
60
+ description="Dump the source graph for all active clocks",
61
+ "dump_all_sources",
62
+ fun (_) -> clock.dump_all_sources()
63
+ )
64
+
65
+ on_start(
66
+ fun () ->
67
+ begin
68
+ if
69
+ settings.request.deprecated_on_air_metadata()
70
+ then
71
+ server.register(
72
+ namespace="request",
73
+ description="Return all the requests currently on_air (DEPRECATED)!",
74
+ "on_air",
75
+ fun (_) ->
76
+ string.concat(
77
+ separator=" ",
78
+ list.map(
79
+ string,
80
+ list.sort(
81
+ fun (a, b) -> a - b,
82
+ list.filter_map(
83
+ fun (r) ->
84
+ if
85
+ list.assoc.mem("on_air", request.metadata(r))
86
+ then
87
+ request.id(r)
88
+ else
89
+ null
90
+ end,
91
+ request.all()
92
+ )
93
+ )
94
+ )
95
+ )
96
+ )
97
+ end
98
+ end
99
+ )
@@ -0,0 +1,41 @@
1
+ # Instantiate a new setting.
2
+ # @category Settings
3
+ # @flag hidden
4
+ def settings.make(~comments="", ~(description:string), v) =
5
+ current_value = ref(v)
6
+ current_value.{description = description, comments = comments}
7
+ end
8
+
9
+ # Instantiate a new empty setting.
10
+ # @category Settings
11
+ # @flag hidden
12
+ def settings.make.void(~comments="", (description:string)) =
13
+ {description = description, comments = comments}
14
+ end
15
+
16
+ let frame = ()
17
+
18
+ # Duration of a frame.
19
+ # @category Settings
20
+ def frame.duration =
21
+ settings.frame.duration
22
+ end
23
+
24
+ let settings.init.compact_before_start =
25
+ settings.make(
26
+ description="Run the OCaml memory compaction algorithm before starting your \
27
+ script. This is useful when script caching is not possible but initial \
28
+ memory consumption is a concern. This will result in a large chunk of \
29
+ memory being freed right before starting the script. This also increases \
30
+ the script's initial startup time.",
31
+ true
32
+ )
33
+
34
+ # Top-level init module for convenience.
35
+ # @category Settings
36
+ # @flag hidden
37
+ init = settings.init
38
+
39
+ on_start(
40
+ {if settings.init.compact_before_start() then runtime.gc.compact() end}
41
+ )
@@ -0,0 +1,33 @@
1
+ # Open a named UNIX socket and connect as a client.
2
+ # @param ~non_blocking Open in non-blocking mode.
3
+ # @category File
4
+ def socket.unix.client(~non_blocking=false, path) =
5
+ s = socket.unix(domain=socket.domain.unix)
6
+ s.non_blocking(non_blocking)
7
+ s.connect(socket.address.unix(path))
8
+ (s : socket).{read = s.read, write = s.write, type = s.type, close = s.close}
9
+ end
10
+
11
+ # Open a named socket and wait for a client to connect
12
+ # @param ~non_blocking Open in non-blocking mode.
13
+ # @category File
14
+ def socket.unix.listen(~non_blocking=false, path) =
15
+ s = socket.unix(domain=socket.domain.unix)
16
+ s.non_blocking(non_blocking)
17
+ s.bind(socket.address.unix(path))
18
+ s.listen(1)
19
+ let (s', _) = s.accept()
20
+ close =
21
+ fun () ->
22
+ begin
23
+ s'.close()
24
+ s.close()
25
+ end
26
+
27
+ (s' : socket).{
28
+ read = s'.read,
29
+ write = s'.write,
30
+ type = s'.type,
31
+ close = close
32
+ }
33
+ end
@@ -0,0 +1,362 @@
1
+ # Create an audio source from the given track, with
2
+ # metadata and track marks from that same track.
3
+ # @category Source / Track processing
4
+ def source.audio(~id=null("source.audio"), audio) =
5
+ source(
6
+ id=id,
7
+ {
8
+ audio = audio,
9
+ metadata = track.metadata(audio),
10
+ track_marks = track.track_marks(audio)
11
+ }
12
+ )
13
+ end
14
+
15
+ # Create a video source from the given track, with
16
+ # metadata and track marks from that same track.
17
+ # @category Source / Track processing
18
+ def source.video(~id=null("source.video"), video) =
19
+ source(
20
+ id=id,
21
+ {
22
+ video = video,
23
+ metadata = track.metadata(video),
24
+ track_marks = track.track_marks(video)
25
+ }
26
+ )
27
+ end
28
+
29
+ # Remove duplicate metadata in a track.
30
+ # @param ~using Labels to use to compare the metadata. Defaults to all of them \
31
+ # when `null`.
32
+ # @category Metadata
33
+ def track.metadata.deduplicate(
34
+ ~id=null("track.metadata.deduplicate"),
35
+ ~using=null,
36
+ t
37
+ ) =
38
+ last_meta = ref([])
39
+
40
+ def f(m) =
41
+ m =
42
+ if
43
+ null.defined(using)
44
+ then
45
+ using = null.get(using)
46
+ list.filter(fun (x) -> list.mem(fst(x), using), m)
47
+ else
48
+ m
49
+ end
50
+
51
+ if
52
+ m == last_meta()
53
+ then
54
+ []
55
+ else
56
+ last_meta := m
57
+ m
58
+ end
59
+ end
60
+
61
+ track.metadata.map(
62
+ id=id,
63
+ insert_missing=false,
64
+ update=false,
65
+ strip=true,
66
+ f,
67
+ t
68
+ )
69
+ end
70
+
71
+ # Remove duplicate metadata in a source.
72
+ # @category Metadata
73
+ # @param ~using Labels to use to compare the metadata. Defaults to all of them \
74
+ # when `null`.
75
+ # @param ~id Source id
76
+ # @param s source
77
+ def metadata.deduplicate(~id=null("metadata.deduplicate"), ~using=null, s) =
78
+ tracks = source.tracks(s)
79
+ source(
80
+ id=id,
81
+ tracks.{metadata = track.metadata.deduplicate(using=using, tracks.metadata)}
82
+ )
83
+ end
84
+
85
+ # Rewrite metadata on the fly using a function.
86
+ # @category Source / Track processing
87
+ # @argsof track.metadata.map
88
+ def metadata.map(
89
+ ~id=null("metadata.map"),
90
+ %argsof(track.metadata.map[!id]),
91
+ f,
92
+ (s:source)
93
+ ) =
94
+ tracks = source.tracks(s)
95
+ source(
96
+ id=id,
97
+ tracks.{
98
+ metadata =
99
+ track.metadata.map(%argsof(track.metadata.map), f, tracks.metadata)
100
+ }
101
+ )
102
+ end
103
+
104
+ # Turn a source into an infaillible source by adding blank when the source is
105
+ # not available.
106
+ # @param s the source to turn infaillible
107
+ # @category Source / Track processing
108
+ def mksafe(~id="mksafe", s) =
109
+ fallback(
110
+ id=id,
111
+ [(s : source).{track_sensitive = false}, blank(id="safe_blank")]
112
+ )
113
+ end
114
+
115
+ # Creates a source that plays only one track of the input source.
116
+ # @category Source / Track processing
117
+ # @param s The input source.
118
+ def once(~id=null("once"), s) =
119
+ sequence(id=id, [(s : source), source.fail()])
120
+ end
121
+
122
+ # Skip track when detecting a blank.
123
+ # @category Source / Track processing
124
+ # @param ~id Force the value of the source ID.
125
+ # @param ~threshold Power in decibels under which the stream is considered silent.
126
+ # @param ~max_blank Maximum silence length allowed, in seconds.
127
+ # @param ~min_noise Minimum duration of noise required to end silence, in seconds.
128
+ # @param ~track_sensitive Reset blank counter at each track.
129
+ def blank.skip(
130
+ ~id=null("blank.skip"),
131
+ ~threshold=-40.,
132
+ ~max_blank=20.,
133
+ ~min_noise=0.,
134
+ ~track_sensitive=true,
135
+ s
136
+ ) =
137
+ s' =
138
+ blank.detect(
139
+ id=id,
140
+ threshold=threshold,
141
+ max_blank=max_blank,
142
+ min_noise=min_noise,
143
+ track_sensitive=track_sensitive,
144
+ s
145
+ )
146
+ s'.on_blank(synchronous=true, source.methods(s).skip)
147
+ s'
148
+ end
149
+
150
+ # Run a function regularly. This is similar to `thread.run` but based on a
151
+ # source internal time instead of the computer's time.
152
+ # @category Source / Track processing
153
+ # @param s Source whose time is taken as reference.
154
+ # @param ~delay Time to wait before the first run (in seconds).
155
+ # @param ~every How often to run the function (in seconds). The function is run once if `null`.
156
+ # @param f Function to run.
157
+ def source.run(s, ~delay=0., ~every=null, f) =
158
+ next = ref(delay)
159
+
160
+ def check() =
161
+ if
162
+ source.time(s) >= next()
163
+ then
164
+ null.case(
165
+ every,
166
+ {next := infinity},
167
+ fun (every) -> next := next() + every
168
+ )
169
+
170
+ f()
171
+ end
172
+ end
173
+
174
+ source.methods(s).on_frame(synchronous=true, check)
175
+ s
176
+ end
177
+
178
+ # Append an extra track to every track. Set the metadata ‘liq_append’ to
179
+ # `false` to inhibit appending on one track.
180
+ # @category Source / Track processing
181
+ # @param f Given the metadata, build the source producing the track to append. The function can return `null` to prevent appending a new track.
182
+ # @param ~insert_missing Treat track beginnings without metadata as having empty one.
183
+ # @param ~merge Merge the track with its appended track.
184
+ # @flag extra
185
+ def append(~id=null("append"), ~insert_missing=true, ~merge=false, s, f) =
186
+ last_meta = ref(null)
187
+ pending = ref(null)
188
+
189
+ def next() =
190
+ p = pending()
191
+ pending := null
192
+ last_meta := null
193
+
194
+ if null.defined(p) then null.get(p) else (s : source) end
195
+ end
196
+
197
+ d =
198
+ source.dynamic(
199
+ track_sensitive=true,
200
+ merge={merge and null.defined(pending)},
201
+ next
202
+ )
203
+
204
+ def f(m) =
205
+ if
206
+ d.current_source() == (s : source)
207
+ then
208
+ if
209
+ m["liq_append"] == "false"
210
+ then
211
+ last_meta := null
212
+ pending := null
213
+ elsif
214
+ last_meta() != m
215
+ then
216
+ last_meta := m
217
+ s = f(m)
218
+ s = d.prepare(s)
219
+ pending := s
220
+ end
221
+ end
222
+ end
223
+
224
+ if insert_missing then d.on_track(synchronous=true, f) end
225
+ d.on_metadata(synchronous=true, f)
226
+
227
+ s = fallback(id=id, [d, s])
228
+ s.{
229
+ pending =
230
+ # Return the pending source
231
+ fun () -> pending(),
232
+ set_pending =
233
+ # Set the pending source
234
+ fun (s) -> pending.set(s),
235
+ cancel_pending =
236
+ # Cancel any pending appended source.
237
+ fun () -> pending.set(null),
238
+ skip =
239
+ # Skip the current track. Pending appended source are cancelled by default. Pass `cancel_pending=false` to keep it.
240
+ fun (~cancel_pending=true) ->
241
+ begin
242
+ if cancel_pending then pending.set(null) end
243
+ s.skip()
244
+ end
245
+ }
246
+ end
247
+
248
+ # Prepend an extra track before every track. Set the metadata `liq_prepend` to
249
+ # `false` to inhibit prepending on one track.
250
+ # @category Source / Track processing
251
+ # @param f Given the metadata, build the source producing the track to append. The function can return `null` to prevent any track prepend.
252
+ # @param ~merge Merge the track with its appended track.
253
+ # @flag extra
254
+ def prepend(~id=null("prepend"), ~merge=false, s, f) =
255
+ last_meta = ref(null)
256
+
257
+ def on_meta(m) =
258
+ if
259
+ m["liq_prepend"] == "false"
260
+ then
261
+ last_meta := null
262
+ elsif
263
+ last_meta() != m
264
+ then
265
+ last_meta := m
266
+ end
267
+ end
268
+
269
+ s.on_track(
270
+ synchronous=true,
271
+ fun (m) -> m == [] ? last_meta := null : on_meta(m)
272
+ )
273
+ s.on_metadata(synchronous=true, on_meta)
274
+
275
+ def next() =
276
+ if
277
+ null.defined(last_meta)
278
+ then
279
+ m = last_meta()
280
+ last_meta := null
281
+ if
282
+ null.defined(m)
283
+ then
284
+ m = null.get(m)
285
+ p = (f(m) : source?)
286
+ if
287
+ null.defined(p)
288
+ then
289
+ sequence(merge=merge, [null.get(p), s])
290
+ else
291
+ s
292
+ end
293
+ else
294
+ s
295
+ end
296
+ else
297
+ s
298
+ end
299
+ end
300
+
301
+ d = source.dynamic(track_sensitive=true, next)
302
+ fallback(id=id, [d, s])
303
+ end
304
+
305
+ # Call a callback on each subtitle in the source.
306
+ # @category Source / Track processing
307
+ # @param f Callback function called on each subtitle.
308
+ # @argsof track.on_subtitle
309
+ def on_subtitle(
310
+ ~id=null("on_subtitle"),
311
+ %argsof(track.on_subtitle[!id]),
312
+ f,
313
+ s
314
+ ) =
315
+ let {subtitles, ...tracks} = source.tracks(s)
316
+ source(
317
+ id=id,
318
+ tracks.{
319
+ subtitles =
320
+ track.on_subtitle(%argsof(track.on_subtitle[!id]), f, subtitles)
321
+ }
322
+ )
323
+ end
324
+
325
+ # Map a function over each subtitle in the source.
326
+ # @category Source / Track processing
327
+ # @param f Callback function called on each subtitle. Returns transformed subtitle or null to filter out.
328
+ # @argsof track.subtitles.map
329
+ def subtitles.map(
330
+ ~id=null("subtitles.map"),
331
+ %argsof(track.subtitles.map[!id]),
332
+ f,
333
+ s
334
+ ) =
335
+ let {subtitles, ...tracks} = source.tracks(s)
336
+ source(
337
+ id=id,
338
+ tracks.{
339
+ subtitles =
340
+ track.subtitles.map(%argsof(track.subtitles.map[!id]), f, subtitles)
341
+ }
342
+ )
343
+ end
344
+
345
+ # Allow inserting subtitles into the source.
346
+ # subtitles track is created is the source does
347
+ # not have one.
348
+ # @category Source / Track processing
349
+ # @argsof track.subtitles.insert
350
+ def subtitles.insert(
351
+ ~id=null("subtitles.insert"),
352
+ %argsof(track.subtitles.insert[!id]),
353
+ s
354
+ ) =
355
+ tracks = source.tracks(s)
356
+ subtitles = tracks?.subtitles
357
+ let subtitles =
358
+ track.subtitles.insert(%argsof(track.subtitles.insert[!id]), subtitles)
359
+ source(id=id, tracks.{subtitles = subtitles}).{
360
+ insert_subtitle = subtitles.insert_subtitle
361
+ }
362
+ end