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,161 @@
|
|
|
1
|
+
# Run a function in a separate thread.
|
|
2
|
+
# @category Programming
|
|
3
|
+
# @param ~fast Whether the thread is supposed to return quickly or not. Typically, blocking tasks (e.g. fetching data over the internet) should not be considered to be fast. When set to `false` its priority will be lowered below that of request resolutions and fast timeouts. This is only effective if you set a dedicated queue for fast tasks, see the "scheduler" settings for more details.
|
|
4
|
+
# @param ~delay Delay (in seconds) after which the thread should be launched.
|
|
5
|
+
# @param ~every How often (in seconds) the thread should be run. If negative or `null`, run once.
|
|
6
|
+
# @param ~on_error Error callback executed when an error occurred while running the given function. When passed, \
|
|
7
|
+
# all raised errors are silenced unless re-raised by the callback.
|
|
8
|
+
# @param f Function to execute.
|
|
9
|
+
def replaces thread.run(~fast=true, ~delay=0., ~on_error=null, ~every=null, f) =
|
|
10
|
+
every = every ?? getter(-1.)
|
|
11
|
+
|
|
12
|
+
def f() =
|
|
13
|
+
ignore(f() == ())
|
|
14
|
+
getter.get(every)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
on_error =
|
|
18
|
+
null.map(
|
|
19
|
+
fun (fn) ->
|
|
20
|
+
fun (err) ->
|
|
21
|
+
begin
|
|
22
|
+
(fn(err) : unit)
|
|
23
|
+
(-1.)
|
|
24
|
+
end,
|
|
25
|
+
on_error
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
thread.run.recurrent(fast=fast, delay=delay, on_error=on_error, f)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Unless told not to, we want to batch thread.when calls when the getter is a fixed
|
|
32
|
+
# value. We batch them by getter value. All callbacks are expected to be fast so we
|
|
33
|
+
# do a single global callback for all batched calls. After tasks_count_warning is
|
|
34
|
+
# reached that they might need to refactor their code.
|
|
35
|
+
let thread.when =
|
|
36
|
+
{tasks_count = ref(0), tasks_count_warning = 70, batches = ref([])}
|
|
37
|
+
|
|
38
|
+
# @flag hidden
|
|
39
|
+
def thread.when.add_to_batch(~every, check) =
|
|
40
|
+
current_batches = thread.when.batches()
|
|
41
|
+
|
|
42
|
+
if
|
|
43
|
+
list.assoc.mem(every, current_batches)
|
|
44
|
+
then
|
|
45
|
+
tasks = list.assoc(every, current_batches)
|
|
46
|
+
tasks := [check, ...tasks()]
|
|
47
|
+
else
|
|
48
|
+
tasks = ref([check])
|
|
49
|
+
errors = ref([])
|
|
50
|
+
def exec_task(fn) =
|
|
51
|
+
try
|
|
52
|
+
fn()
|
|
53
|
+
catch err do
|
|
54
|
+
errors := [err, ...errors()]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
def check_tasks() =
|
|
58
|
+
current_tasks = tasks()
|
|
59
|
+
list.iter(exec_task, current_tasks)
|
|
60
|
+
current_errors = errors()
|
|
61
|
+
|
|
62
|
+
errors := []
|
|
63
|
+
|
|
64
|
+
if
|
|
65
|
+
current_errors != []
|
|
66
|
+
then
|
|
67
|
+
errors_list =
|
|
68
|
+
string.concat(
|
|
69
|
+
separator=", ",
|
|
70
|
+
list.map(string, current_errors)
|
|
71
|
+
)
|
|
72
|
+
log.critical(
|
|
73
|
+
label="thread.when",
|
|
74
|
+
"Error(s) while executing batched `thread.when`: #{errors_list}"
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
every
|
|
79
|
+
end
|
|
80
|
+
thread.run.recurrent(fast=true, delay=0., on_error=null, check_tasks)
|
|
81
|
+
thread.when.batches := [(every, tasks), ...current_batches]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Execute a callback when a predicate is `true`. The predicate
|
|
86
|
+
# is checked `every` seconds and the callback is
|
|
87
|
+
# called when the predicate returns `true` after having been
|
|
88
|
+
# `false`, following the same semantics as `predicate.activates`.
|
|
89
|
+
# @category Programming
|
|
90
|
+
# @param ~fast Whether the callback is supposed to return quickly or not.
|
|
91
|
+
# @param ~init Detect at beginning.
|
|
92
|
+
# @param ~every How often (in sec.) to check for the predicate.
|
|
93
|
+
# @param ~once Execute the function only once.
|
|
94
|
+
# @param ~changed Execute the function only if the predicate was false when last checked.
|
|
95
|
+
# @param ~batched When `fast` is `true`, `every` is a fixed value, `on_error` is `null` and `once` is `false`, \
|
|
96
|
+
# we batch callbacks to make it more efficient. Set this argument to `false` to disable that.
|
|
97
|
+
# @param ~on_error Error callback executed when an error occurred while running the given function. When passed, \
|
|
98
|
+
# all raised errors are silenced unless re-raised by the callback.
|
|
99
|
+
# @param p Predicate indicating when to execute the function, typically a time interval such as `{10h-10h30}`.
|
|
100
|
+
# @param f Function to execute when the predicate is true.
|
|
101
|
+
def thread.when(
|
|
102
|
+
~fast=true,
|
|
103
|
+
~init=true,
|
|
104
|
+
~every=getter(0.5),
|
|
105
|
+
~once=false,
|
|
106
|
+
~changed=true,
|
|
107
|
+
~on_error=null,
|
|
108
|
+
~batched=true,
|
|
109
|
+
p,
|
|
110
|
+
f
|
|
111
|
+
) =
|
|
112
|
+
ref.incr(thread.when.tasks_count)
|
|
113
|
+
if
|
|
114
|
+
thread.when.tasks_count() == thread.when.tasks_count_warning + 1
|
|
115
|
+
then
|
|
116
|
+
log.critical(
|
|
117
|
+
label="thread.when",
|
|
118
|
+
"There are over #{thread.when.tasks_count_warning} `thread.when` tasks \
|
|
119
|
+
running! You might want to consider refactoring your script to use a \
|
|
120
|
+
single callback!"
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
p = once or not changed ? p : (predicate.activates(init=init, p))
|
|
125
|
+
|
|
126
|
+
def check() =
|
|
127
|
+
if
|
|
128
|
+
p()
|
|
129
|
+
then
|
|
130
|
+
f()
|
|
131
|
+
once ? (-1.) : (getter.get(every))
|
|
132
|
+
else
|
|
133
|
+
getter.get(every)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if
|
|
138
|
+
(
|
|
139
|
+
batched
|
|
140
|
+
and getter.is_constant(every)
|
|
141
|
+
and not null.defined(on_error)
|
|
142
|
+
and fast
|
|
143
|
+
and not once
|
|
144
|
+
)
|
|
145
|
+
then
|
|
146
|
+
thread.when.add_to_batch(every=getter.get(every), fun () -> ignore(check()))
|
|
147
|
+
else
|
|
148
|
+
thread.run.recurrent(fast=fast, delay=0., on_error=on_error, check)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @flag hidden
|
|
153
|
+
def default_error_handler(~backtrace, err) =
|
|
154
|
+
log(
|
|
155
|
+
label="runtime",
|
|
156
|
+
level=1,
|
|
157
|
+
"Uncaught error #{err}!\n#{backtrace}"
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
thread.on_error(null, default_error_handler)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
let source.mux = ()
|
|
2
|
+
let source.mux.track = ()
|
|
3
|
+
|
|
4
|
+
# Replace the audio track of a source.
|
|
5
|
+
# @category Source / Track processing
|
|
6
|
+
def source.mux.track.audio(~id=null, ~audio, s) =
|
|
7
|
+
source(id=id, source.tracks(s).{audio = audio})
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Replace the video track of a source.
|
|
11
|
+
# @category Source / Track processing
|
|
12
|
+
def source.mux.track.video(~id=null, ~video, s) =
|
|
13
|
+
source(id=id, source.tracks(s).{video = video})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Replace the audio track of a source by the one of another source.
|
|
17
|
+
# @category Source / Track processing
|
|
18
|
+
# @param ~audio Source whose audio track is to be taken.
|
|
19
|
+
def source.mux.audio(~id=null, ~(audio:source), s) =
|
|
20
|
+
source.mux.track.audio(id=id, audio=source.tracks(audio).audio, s)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Replace the video track of a source by the one of another source.
|
|
24
|
+
# @category Source / Track processing
|
|
25
|
+
# @param ~audio Source whose video track is to be taken.
|
|
26
|
+
def source.mux.video(~id=null, ~(video:source), s) =
|
|
27
|
+
source.mux.track.video(id=id, video=source.tracks(video).video, s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Replace the midi track of a source by the one of another source.
|
|
31
|
+
# @category Source / Track processing
|
|
32
|
+
# @param ~midi Source whose midi track is to be taken.
|
|
33
|
+
def source.mux.midi(~id=null, ~(midi:source), s) =
|
|
34
|
+
source(id=id, source.tracks(s).{midi = source.tracks(midi).midi})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
let source.drop = ()
|
|
38
|
+
|
|
39
|
+
# Remove the audio track of a source.
|
|
40
|
+
# @category Source / Track processing
|
|
41
|
+
def source.drop.audio(~id=null, s) =
|
|
42
|
+
let {audio = _, ...tracks} = source.tracks(s)
|
|
43
|
+
source(id=id, tracks)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Remove the video track of a source.
|
|
47
|
+
# @category Source / Track processing
|
|
48
|
+
def source.drop.video(~id=null, s) =
|
|
49
|
+
let {video = _, ...tracks} = source.tracks(s)
|
|
50
|
+
source(id=id, tracks)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Remove the midi track of a source.
|
|
54
|
+
# @category Source / Track processing
|
|
55
|
+
def source.drop.midi(~id=null, s) =
|
|
56
|
+
let {midi = _, ...tracks} = source.tracks(s)
|
|
57
|
+
source(id=id, tracks)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Remove the metadata track of a source.
|
|
61
|
+
# @category Source / Track processing
|
|
62
|
+
def source.drop.metadata(~id=null, s) =
|
|
63
|
+
let {metadata = _, ...tracks} = source.tracks(s)
|
|
64
|
+
source(id=id, tracks)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Remove the track marks of a source.
|
|
68
|
+
# @category Source / Track processing
|
|
69
|
+
def source.drop.track_marks(~id=null, s) =
|
|
70
|
+
let {track_marks = _, ...tracks} = source.tracks(s)
|
|
71
|
+
source(id=id, tracks)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Remove the metadata and track marks of a source.
|
|
75
|
+
# @category Source / Track processing
|
|
76
|
+
def source.drop.metadata_track_marks(~id=null, s) =
|
|
77
|
+
let {metadata = _, track_marks = _, ...tracks} = source.tracks(s)
|
|
78
|
+
source(id=id, tracks)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
let settings.amplify =
|
|
82
|
+
settings.make.void(
|
|
83
|
+
"Settings for the amplify operator"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
let settings.amplify.override =
|
|
87
|
+
settings.make(
|
|
88
|
+
description="Default metadata used to override amplification.",
|
|
89
|
+
"liq_amplify"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# @docof track.audio.amplify
|
|
93
|
+
def track.audio.amplify(
|
|
94
|
+
%argsof(track.audio.amplify[!override]),
|
|
95
|
+
~override=getter({(settings.amplify.override() : string?)}),
|
|
96
|
+
v,
|
|
97
|
+
t
|
|
98
|
+
) =
|
|
99
|
+
track.audio.amplify(%argsof(track.audio.amplify), v, t)
|
|
100
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
%ifdef environment
|
|
2
|
+
# Get the value of an environment variable.
|
|
3
|
+
# Returns `default` if the variable is not set.
|
|
4
|
+
# @category System
|
|
5
|
+
def environment.get(~default="", s) =
|
|
6
|
+
env = environment()
|
|
7
|
+
if
|
|
8
|
+
not list.assoc.mem(s, env)
|
|
9
|
+
then
|
|
10
|
+
default
|
|
11
|
+
else
|
|
12
|
+
list.assoc(default=default, s, env)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
%endif
|
|
16
|
+
|
|
17
|
+
# Split the arguments of an url of the form `arg=bar&arg2=bar2` into
|
|
18
|
+
# `[("arg","bar"),("arg2","bar2")]`. The returned strings are decoded (see
|
|
19
|
+
# `url.decode`).
|
|
20
|
+
# @category String
|
|
21
|
+
# @param args Argument string to split.
|
|
22
|
+
def url.split_args(args) =
|
|
23
|
+
def f(args, x) =
|
|
24
|
+
if
|
|
25
|
+
x == ""
|
|
26
|
+
then
|
|
27
|
+
args
|
|
28
|
+
else
|
|
29
|
+
ret = r/=/.split(x)
|
|
30
|
+
arg = url.decode(list.nth(default="", ret, 0))
|
|
31
|
+
val = url.decode(list.nth(default="", ret, 1))
|
|
32
|
+
[...args, (arg, val)]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
l = r/&/.split(args)
|
|
37
|
+
list.fold(f, [], l)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Split an url of the form `foo?arg=bar&arg2=bar2` into
|
|
41
|
+
# `("foo",[("arg","bar"),("arg2","bar2")])`. The returned strings are decoded
|
|
42
|
+
# (see `url.decode`).
|
|
43
|
+
# @category String
|
|
44
|
+
# @param uri Url to split.
|
|
45
|
+
def url.split(uri) =
|
|
46
|
+
ret = r/(?<uri>[^\?]*)\?(?<args>.*)/.exec(uri).groups
|
|
47
|
+
args = ret["args"]
|
|
48
|
+
if
|
|
49
|
+
args != ""
|
|
50
|
+
then
|
|
51
|
+
(url.decode(ret["uri"]), url.split_args(args))
|
|
52
|
+
else
|
|
53
|
+
(url.decode(uri), [])
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Memoize the result of a function, making sure it is only executed once.
|
|
58
|
+
# @category Programming
|
|
59
|
+
def memoize(fn) =
|
|
60
|
+
cached_result = ref([])
|
|
61
|
+
fun () ->
|
|
62
|
+
begin
|
|
63
|
+
if
|
|
64
|
+
cached_result() != []
|
|
65
|
+
then
|
|
66
|
+
list.hd(cached_result())
|
|
67
|
+
else
|
|
68
|
+
result = fn()
|
|
69
|
+
cached_result := [result]
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
let duration = ()
|
|
76
|
+
|
|
77
|
+
# Convert a duration in seconds to hour/minutes/seconds
|
|
78
|
+
# @category Time
|
|
79
|
+
def duration.split(d) =
|
|
80
|
+
{hours = d / 3600, minutes = (d / 60) mod 60, seconds = (d mod 60)}
|
|
81
|
+
end
|