liquidsoap-prettier 1.8.1 → 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/dist/liquidsoap.cjs +4155 -3090
- 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,329 @@
|
|
|
1
|
+
let hls = {playlist = ()}
|
|
2
|
+
|
|
3
|
+
# Generate a main HLS playlist
|
|
4
|
+
# @category String
|
|
5
|
+
def hls.playlist.main(~extra_tags=[], ~prefix="", ~version=7, streams) =
|
|
6
|
+
prefix = prefix == "" or r/\/$/.test(prefix) ? prefix : "#{prefix}/"
|
|
7
|
+
|
|
8
|
+
streams =
|
|
9
|
+
list.fold(
|
|
10
|
+
fun (streams, s) ->
|
|
11
|
+
begin
|
|
12
|
+
let ({bandwidth, codecs, video_size?, ...s} :
|
|
13
|
+
string.{ bandwidth: int, codecs: string, video_size?: (int * int) }
|
|
14
|
+
) = s
|
|
15
|
+
resolution =
|
|
16
|
+
if
|
|
17
|
+
null.defined(video_size)
|
|
18
|
+
then
|
|
19
|
+
let (w, h) = null.get(video_size)
|
|
20
|
+
",RESOLUTION=#{w}x#{h}"
|
|
21
|
+
else
|
|
22
|
+
""
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[
|
|
26
|
+
...streams,
|
|
27
|
+
"#EXT-X-STREAM-INF:BANDWIDTH=#{bandwidth},CODECS=#{
|
|
28
|
+
string.quote(codecs)
|
|
29
|
+
}#{resolution}",
|
|
30
|
+
"#{prefix}#{s}.m3u8"
|
|
31
|
+
]
|
|
32
|
+
end,
|
|
33
|
+
[],
|
|
34
|
+
streams
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
string.concat(
|
|
38
|
+
separator="\r\n",
|
|
39
|
+
["#EXTM3U", "#EXT-X-VERSION:#{version}", ...extra_tags, ...streams, ""]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @docof output.file.hls
|
|
44
|
+
def replaces output.file.hls(
|
|
45
|
+
%argsof(output.file.hls[!main_playlist_writer]),
|
|
46
|
+
~main_playlist_writer=null(
|
|
47
|
+
fun (~extra_tags, ~prefix, ~version, streams) ->
|
|
48
|
+
null(
|
|
49
|
+
hls.playlist.main(
|
|
50
|
+
extra_tags=extra_tags,
|
|
51
|
+
prefix=prefix,
|
|
52
|
+
version=version,
|
|
53
|
+
streams
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
),
|
|
57
|
+
dir,
|
|
58
|
+
streams,
|
|
59
|
+
s
|
|
60
|
+
) =
|
|
61
|
+
output.file.hls(
|
|
62
|
+
%argsof(output.file.hls[!main_playlist_writer]),
|
|
63
|
+
main_playlist_writer=main_playlist_writer,
|
|
64
|
+
dir,
|
|
65
|
+
streams,
|
|
66
|
+
s
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
let input.hls = ()
|
|
71
|
+
|
|
72
|
+
# Play an HLS stream.
|
|
73
|
+
# @category Source / Input / Active
|
|
74
|
+
# @param ~id Force the value of the source ID.
|
|
75
|
+
# @param ~reload How often (in seconds) the playlist should be reloaded.
|
|
76
|
+
# @param uri Playlist URI.
|
|
77
|
+
# @flag experimental
|
|
78
|
+
def input.hls.native(~id=null, ~reload=10., uri) =
|
|
79
|
+
playlistr = ref([])
|
|
80
|
+
sequence = ref(0)
|
|
81
|
+
playlist_uri = ref(uri)
|
|
82
|
+
id = string.id.default(default="input.hls.native", id)
|
|
83
|
+
|
|
84
|
+
def load_playlist() =
|
|
85
|
+
pl = request.create(playlist_uri())
|
|
86
|
+
if
|
|
87
|
+
request.resolve(pl)
|
|
88
|
+
then
|
|
89
|
+
pl = request.filename(pl)
|
|
90
|
+
m = r/#EXT-X-MEDIA-SEQUENCE:(\d+)/.exec(file.contents(pl))
|
|
91
|
+
pl_sequence = m[1]
|
|
92
|
+
log.info(
|
|
93
|
+
label=id,
|
|
94
|
+
"Sequence: " ^
|
|
95
|
+
pl_sequence
|
|
96
|
+
)
|
|
97
|
+
pl_sequence = int_of_string(default=0, pl_sequence)
|
|
98
|
+
files = playlist.parse(path=path.dirname(playlist_uri()) ^ "/", pl)
|
|
99
|
+
|
|
100
|
+
def file_request(idx, el) =
|
|
101
|
+
let (meta, file) = el
|
|
102
|
+
|
|
103
|
+
def escape(s) =
|
|
104
|
+
string.escape(encoding="ascii", s)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
s =
|
|
108
|
+
list.fold(
|
|
109
|
+
fun (cur, el) -> "#{cur},#{fst(el)}=#{escape(snd(el))}",
|
|
110
|
+
"",
|
|
111
|
+
meta
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
s = if s == "" then file else "annotate:#{s}:#{file}" end
|
|
115
|
+
(pl_sequence + idx, s)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
files = list.mapi(file_request, files)
|
|
119
|
+
let (first_idx, _) = list.hd(default=(-1, ""), playlistr())
|
|
120
|
+
|
|
121
|
+
def add_file(playlist, file) =
|
|
122
|
+
let (idx, _) = file
|
|
123
|
+
if
|
|
124
|
+
first_idx < idx and not list.assoc.mem(idx, playlist)
|
|
125
|
+
then
|
|
126
|
+
list.append(playlist, [file])
|
|
127
|
+
else
|
|
128
|
+
playlist
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
playlistr := list.fold(add_file, playlistr(), files)
|
|
133
|
+
else
|
|
134
|
+
log.severe(
|
|
135
|
+
label=id,
|
|
136
|
+
"Couldn't read playlist: request resolution failed."
|
|
137
|
+
)
|
|
138
|
+
playlistr := []
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
request.destroy(pl)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def rec next() =
|
|
145
|
+
if
|
|
146
|
+
list.length(playlistr()) > 0
|
|
147
|
+
then
|
|
148
|
+
let (_, ret) = list.hd(default=(1, ""), playlistr())
|
|
149
|
+
playlistr := list.tl(playlistr())
|
|
150
|
+
sequence := sequence() + 1
|
|
151
|
+
request.create(ret)
|
|
152
|
+
else
|
|
153
|
+
null
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def find_stream() =
|
|
158
|
+
pl = request.create(playlist_uri())
|
|
159
|
+
if
|
|
160
|
+
request.resolve(pl)
|
|
161
|
+
then
|
|
162
|
+
plfile = request.filename(pl)
|
|
163
|
+
m =
|
|
164
|
+
r/#EXT-X-STREAM-INF[^\n]*\n([^\r\n]*)\r?\n/.exec(file.contents(plfile))
|
|
165
|
+
|
|
166
|
+
playlist_uri := list.assoc(default=playlist_uri(), 1, m)
|
|
167
|
+
if
|
|
168
|
+
not (string.contains(substring="/", playlist_uri()))
|
|
169
|
+
then
|
|
170
|
+
playlist_uri := path.dirname(request.uri(pl)) ^ "/" ^ playlist_uri()
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
log(
|
|
174
|
+
label=id,
|
|
175
|
+
"Playlist: " ^
|
|
176
|
+
playlist_uri()
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
find_stream()
|
|
182
|
+
s = request.dynamic(id=id, prefetch=10, next)
|
|
183
|
+
let {track_marks = _, ...tracks} = source.tracks(s)
|
|
184
|
+
s = source(tracks)
|
|
185
|
+
thread.run(every=reload, load_playlist)
|
|
186
|
+
s
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
let replaces input.hls = input.hls.native
|
|
190
|
+
%ifdef input.ffmpeg
|
|
191
|
+
let replaces input.hls = input.ffmpeg
|
|
192
|
+
%endif
|
|
193
|
+
|
|
194
|
+
# Play an HLS stream.
|
|
195
|
+
# @category Source / Input / Active
|
|
196
|
+
# @param ~id Force the value of the source ID.
|
|
197
|
+
# @param uri Playlist URI.
|
|
198
|
+
def input.hls(~id=null, uri) =
|
|
199
|
+
input.hls(id=id, uri)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
let output.harbor.hls = ()
|
|
203
|
+
|
|
204
|
+
# @flag hidden
|
|
205
|
+
def output.harbor.hls.base(
|
|
206
|
+
%argsof(output.file.hls[!segment_name]),
|
|
207
|
+
~segment_name,
|
|
208
|
+
~tmpdir,
|
|
209
|
+
~port,
|
|
210
|
+
~path,
|
|
211
|
+
serve,
|
|
212
|
+
formats,
|
|
213
|
+
s
|
|
214
|
+
) =
|
|
215
|
+
tmpdir = tmpdir ?? file.temp_dir("hls", "")
|
|
216
|
+
|
|
217
|
+
def content_type(fname) =
|
|
218
|
+
ext = file.extension(fname)
|
|
219
|
+
if
|
|
220
|
+
ext == ".m3u8"
|
|
221
|
+
then
|
|
222
|
+
"application/x-mpegURL"
|
|
223
|
+
else
|
|
224
|
+
def f(cur, el) =
|
|
225
|
+
format = snd(el)
|
|
226
|
+
if
|
|
227
|
+
ext == ".#{encoder.extension(format)}"
|
|
228
|
+
then
|
|
229
|
+
encoder.content_type(format)
|
|
230
|
+
else
|
|
231
|
+
cur
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
list.fold(f, "", formats)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
serve(port=port, path=path, content_type=content_type, tmpdir)
|
|
240
|
+
output.file.hls(%argsof(output.file.hls), tmpdir, formats, s)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Output the source stream to an HTTP live stream served from the harbor HTTP server.
|
|
244
|
+
# @category Source / Output
|
|
245
|
+
# @argsof output.file.hls
|
|
246
|
+
# @param ~headers Default response headers.
|
|
247
|
+
# @param ~port Port for incoming harbor (http) connections.
|
|
248
|
+
# @param ~path Base path for hls URIs.
|
|
249
|
+
# @param ~transport Http transport. Use `http.transport.ssl` or `http.transport.secure_transport`, when available, to enable HTTPS output
|
|
250
|
+
# @param ~tmpdir Directory for generated files.
|
|
251
|
+
# @param formats List of specifications for each stream: (name, format).
|
|
252
|
+
def replaces output.harbor.hls(
|
|
253
|
+
%argsof(output.file.hls[!segment_name]),
|
|
254
|
+
~segment_name=(
|
|
255
|
+
fun (metadata) ->
|
|
256
|
+
"#{metadata.stream_name}_#{metadata.position}.#{metadata.extname}"
|
|
257
|
+
),
|
|
258
|
+
~headers=[("Access-Control-Allow-Origin", "*")],
|
|
259
|
+
~port=8000,
|
|
260
|
+
~path="/",
|
|
261
|
+
~tmpdir=null,
|
|
262
|
+
~transport=http.transport.unix,
|
|
263
|
+
formats,
|
|
264
|
+
s
|
|
265
|
+
) =
|
|
266
|
+
def serve(~port, ~path, ~content_type, dir) =
|
|
267
|
+
harbor.http.static(
|
|
268
|
+
port=port,
|
|
269
|
+
path=path,
|
|
270
|
+
content_type=content_type,
|
|
271
|
+
headers=headers,
|
|
272
|
+
transport=transport,
|
|
273
|
+
dir
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
output.harbor.hls.base(
|
|
278
|
+
%argsof(output.file.hls),
|
|
279
|
+
path=path,
|
|
280
|
+
port=port,
|
|
281
|
+
tmpdir=tmpdir,
|
|
282
|
+
serve,
|
|
283
|
+
formats,
|
|
284
|
+
s
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
%ifdef harbor.https.static
|
|
289
|
+
# Output the source stream to an HTTP live stream served from the harbor HTTPS server.
|
|
290
|
+
# @category Source / Output
|
|
291
|
+
# @param ~headers Default response headers.
|
|
292
|
+
# @param ~port Port for incoming harbor (http) connections.
|
|
293
|
+
# @param ~path Base path for hls URIs.
|
|
294
|
+
# @param ~tmpdir Directory for generated files.
|
|
295
|
+
# @param formats List of specifications for each stream: (name, format).
|
|
296
|
+
def output.harbor.hls.https(
|
|
297
|
+
%argsof(output.file.hls[!segment_name]),
|
|
298
|
+
~segment_name=(
|
|
299
|
+
fun (metadata) ->
|
|
300
|
+
"#{metadata.stream_name}_#{metadata.position}.#{metadata.extname}"
|
|
301
|
+
),
|
|
302
|
+
~headers=[("Access-Control-Allow-Origin", "*")],
|
|
303
|
+
~port=8000,
|
|
304
|
+
~path="/",
|
|
305
|
+
~tmpdir=null,
|
|
306
|
+
formats,
|
|
307
|
+
s
|
|
308
|
+
) =
|
|
309
|
+
def serve(~port, ~path, ~content_type, dir) =
|
|
310
|
+
harbor.https.static(
|
|
311
|
+
port=port,
|
|
312
|
+
path=path,
|
|
313
|
+
content_type=content_type,
|
|
314
|
+
headers=headers,
|
|
315
|
+
dir
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
output.harbor.hls.base(
|
|
320
|
+
%argsof(output.file.hls),
|
|
321
|
+
path=path,
|
|
322
|
+
port=port,
|
|
323
|
+
tmpdir=tmpdir,
|
|
324
|
+
serve,
|
|
325
|
+
formats,
|
|
326
|
+
s
|
|
327
|
+
)
|
|
328
|
+
end
|
|
329
|
+
%endif
|