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,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
|