File: externals.liq

package info (click to toggle)
liquidsoap 1.3.3-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 4,504 kB
  • sloc: ml: 37,149; python: 956; makefile: 624; sh: 458; perl: 322; lisp: 124; ansic: 53; ruby: 8
file content (392 lines) | stat: -rw-r--r-- 13,634 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# Decoders, enabled when the binary is detected and the os is not Win32.

register(
  name="external decoders",
  descr="External decoders settings",
  "decoder.external",
  ())
register(
  name="ffmpeg path",
  descr="Path to ffmpeg binary",
  "decoder.external.ffmpeg.path",
  "ffmpeg#{exe_ext}")
register(
  name="ffprobe path",
  descr="Path to ffprobe binary",
  "decoder.external.ffprobe.path",
  "ffprobe#{exe_ext}")

register(
  name="Ffmpeg decoder stream mimes",
  descr="Mime types supported by the ffmpeg stream decoder",
  "decoder.mime_types.ffmpeg",
  [])

%ifdef add_decoder
# Enable ffmpeg decoder.
# @category Liquidsoap
def enable_external_ffmpeg_decoder() =
  ffmpeg     = get(default="ffmpeg#{exe_ext}","decoder.external.ffmpeg.path")
  ffprobe    = get(default="ffprobe#{exe_ext}","decoder.external.ffprobe.path")
  mime_types = get(default=[],"decoder.mime_types.ffmpeg")

  def ffprobe_test(fname) =
    json = get_process_output("#{ffprobe} -print_format json -show_streams #{quote(fname)}")
    default = [("streams",[[("channels",0)]])]
    data = of_json(default=default,json)
    streams = list.assoc(default=[],"streams",data)
    stream = list.hd(default=[],streams)
    list.assoc(default=0,"channels",stream)
  end

  if which(ffmpeg) != "" and which (ffprobe) != "" then
   log(label="external.decoder","Enabling FFMPEG decoder")
    add_decoder(
      name="FFMPEG",
      description="Decode files using the ffmpeg decoder binary",
      mimes=mime_types,
      test=ffprobe_test,
      "#{ffmpeg} -i - -f wav - ")
  else
    log(label="external.decoder","Could not find ffmpeg or ffprobe binary. Please adjust using the \"decoder.external.ffmpeg.path\" setting.")
  end
end

register(
  name="mpcdec path",
  descr="Path to mpcdec binary",
  "decoder.external.mpcdec.path",
  "mpcdec#{exe_ext}")

# Enable external Musepack decoder.
# @category Liquidsoap
def enable_external_mpc_decoder() =
  # A list of know extensions and content-type for Musepack.
  # Values from http://en.wikipedia.org/wiki/Musepack
  mpc_mimes = [ "audio/x-musepack", "audio/musepack" ]
  mpc_filexts = [ "mpc", "mp+", "mpp" ]

  mpcdec = get(default="mpcdec#{exe_ext}","decoder.external.mpcdec.path") 

  def test_mpc(file) =
    def get_channels(file) =
      int_of_string(
        list.hd(default="",
          get_process_lines("#{mpcdec} -i #{quote(file)} 2>&1 \
                             | grep channels | cut -d' ' -f 2")))
    end
    # Get the file's mime
    mime = get_mime(file)
    # Test mime
    if list.mem(mime,mpc_mimes) then
      get_channels(file)
    else
      # Otherwise test file extension
      ret = string.extract(pattern='\.(.+)$',file)
      if list.length(ret) != 0 then
        ext = ret["1"]
        if list.mem(ext,mpc_filexts) then
          get_channels(file)
        else
          0
        end
      else
        get_channels(file)
      end
    end
  end

  if which(mpcdec) != "" then
    log(label="external.decoder","Enabling MPCDEC external decoder.")
    mpcdec_p = fun(f) -> "#{mpcdec} #{quote(f)} - 2>/dev/null"
    add_oblivious_decoder(name="MPCDEC",description="Decode files using the mpcdec \
                musepack decoder binary",test=test_mpc,mpcdec_p)
  else
    log(label="external.decoder","Could not find mpcdec binary, please adjust using the \"decoder.external.mpcdec.path\" setting.")
  end
end

register(
  name="flac path",
  descr="Path to flac binary",
  "decoder.external.flac.path",
  "flac#{exe_ext}")
register(
  name="metaflac path",
  descr="Path to metaflac binary",
  "decoder.external.metaflac.path",
  "metaflac#{exe_ext}")

# Enable external FLAC decoders. Please note that built-in support for 
# FLAC is available in liquidsoap if compiled and should be preferred
# over the external decoder.
# @category Liquidsoap
def enable_external_flac_decoder() =
  flac = get(default="flac#{exe_ext}","decoder.external.flac.path")
  metaflac = get(default="metaflac#{exe_ext}","decoder.external.metaflac.path")

  if which(flac) != "" then
    log(label="external.decoder","Enabling EXTERNAL_FLAC external decoder.")
    flac_p = "#{flac} -d -c - 2>/dev/null"
    def test_flac(file) =
      if which(metaflac) != "" then
        channels = list.hd(default="",
                           get_process_lines("#{metaflac} \
                                              --show-channels #{quote(file)} \
                                              2>/dev/null"))
        # If the value is not an int, this returns 0 and we are ok :)
        int_of_string(channels)
      else
        if string.match(pattern="flac",file) then
          # We do not know the number of audio channels
          # so setting to -1
          (-1)
        else
          # All tests failed: no audio decodable using flac..
          0
        end
      end
    end
    add_decoder(name="EXTERNAL_FLAC",description="Decode files using the flac \
                decoder binary.", mimes=["audio/x-flac"], test=test_flac,flac_p)
  else
    log(label="decoder.external","Did not find flac binary, please adjust using the \"decoder.external.flac.path\" setting.")
  end

  if which(metaflac) != "" then
    log(label="decoder.external","Enabling EXTERNAL_FLAC metadata \
                resolver.")
    def flac_meta(file)
      ret = get_process_lines("#{metaflac} --export-tags-to=- \
                   #{quote(file)} 2>/dev/null")
      ret = list.map(string.split(separator="="),ret)
      # Could be made better..
      def f(l',l)=
        if list.length(l) >= 2 then
          list.append([(list.hd(default="",l),list.nth(default="",l,1))],l')
        else
          if list.length(l) >= 1 then
            list.append([(list.hd(default="",l),"")],l')
          else
            l'
          end
        end
      end
      list.fold(f,[],ret)
    end
    add_metadata_resolver("EXTERNAL_FLAC",flac_meta)
  else
    log(label="decoder.external","Did not find metaflac binary. Please adjust using the \"decoder.external.metaflac.path\" setting.")
  end
end
%endif

%ifdef add_oblivious_decoder
register(
  name="faad path",
  descr="Path to faad binary",
  "decoder.external.faad.path",
  "faad#{exe_ext}")

# Enable or disable external FAAD (AAC/AAC+/M4A) decoders. Does not work
# on Win32.
# Please note that built-in support for faad is available in liquidsoap if
# compiled and should be preferred over the external decoder.
# @category Liquidsoap
def enable_external_faad_decoder() =
  faad = get(default="faad#{exe_ext}","decoder.external.faad.path")

  # A list of know extensions and content-type for AAC.
  # Values from http://en.wikipedia.org/wiki/Advanced_Audio_Coding
  # TODO: can we register a setting for that ??
  aac_mimes =
    ["audio/aac", "audio/aacp", "audio/3gpp", "audio/3gpp2", "audio/mp4",
     "audio/MP4A-LATM", "audio/mpeg4-generic", "audio/x-hx-aac-adts"]
  aac_filexts = ["m4a", "m4b", "m4p", "m4v",
                 "m4r", "3gp", "mp4", "aac"]

  # Faad is not very selective so we are checking only file that end with a
  # known extension or mime type
  def faad_test(file) =
    # Get the file's mime
    mime = get_mime(file)
    # Test mime
    if list.mem(mime,aac_mimes) then
      true
    else
      # Otherwise test file extension
      ret = string.extract(pattern='\.(.+)$',file)
      if list.length(ret) != 0 then
        ext = ret["1"]
        list.mem(ext,aac_filexts)
      else
        false
      end
    end
  end

  if which(faad) != "" then
    log(label="decoder.external","Enabling EXTERNAL_FAAD decoder and \
                 metadata resolver.")
    faad_p = (fun (f) -> "#{faad} -w #{quote(f)} 2>/dev/null")
    def test_faad(file) =
      if faad_test(file) then
        channels = list.hd(default="",
                           get_process_lines("#{faad} -i #{quote(file)} 2>&1 | \
                                              grep 'ch,'"))
        ret = string.extract(pattern=", (\d) ch,",channels)
        ret =
          if list.length(ret) == 0 then
            # If we pass the faad_test, chances are high that the file will
            # contain aac audio data..
            "-1"
          else
            ret["1"]
          end
        int_of_string(default=(-1),ret)
      else
        0
      end
    end
    add_oblivious_decoder(name="EXTERNAL_FAAD",description="Decode files using \
                          the faad binary.", test=test_faad, faad_p)
    def faad_meta(file) =
      if faad_test(file) then
        ret = get_process_lines("#{faad} -i \
                     #{quote(file)} 2>&1")
        # Yea, this is ugly programming (again)!
        def get_meta(l,s)=
          ret = string.extract(pattern="^(\w+):\s(.+)$",s)
          if list.length(ret) > 0 then
            list.append([(ret["1"],ret["2"])],l)
          else
            l
          end
        end
        list.fold(get_meta,[],ret)
      else
        []
      end
    end
    add_metadata_resolver("EXTERNAL_FAAD",faad_meta)
  else
    log(label="external.decoder","Did not find faad binary. Please adjust using the \"decoder.external.faad.path\" setting.")
  end
end
%endif

# Standard function for displaying metadata.
# Shows artist and title, using "Unknown" when a field is empty.
# @param m Metadata packet to be displayed.
# @category String
def string_of_metadata(m)
  artist = m["artist"]
  title  = m["title"]
  artist = if ""==artist then "Unknown" else artist end
  title  = if ""==title  then "Unknown" else title  end
  "#{artist} -- #{title}"
end

# Use X On Screen Display to display metadata info.
# @param ~color    Color of the text.
# @param ~position Position of the text (top|middle|bottom).
# @param ~font     Font used (xfontsel is your friend...)
# @param ~display  Function used to display a metadata packet.
# @category Source / Track Processing
def osd_metadata(~color="green",~position="top",
        ~font="-*-courier-*-r-*-*-*-240-*-*-*-*-*-*",
        ~display=string_of_metadata,
        s)
  osd = 'osd_cat -p #{position} --font #{quote(font)}'
      ^ ' --color #{color}'
  def feedback(m)
    system("echo #{quote(display(m))} | #{osd} &")
  end
  on_metadata(feedback,s)
end

# Use notify to display metadata info.
# @param ~urgency Urgency (low|normal|critical).
# @param ~icon    Icon filename or stock icon to display.
# @param ~time    Timeout in milliseconds.
# @param ~display Function used to display a metadata packet.
# @param ~title   Title of the notification message.
# @category Source / Track Processing
def notify_metadata(~urgency="low",~icon="stock_smiley-22",~time=3000,
           ~display=string_of_metadata,
           ~title="Liquidsoap: new track",s)
  send = 'notify-send -i #{icon} -u #{urgency}'
       ^ ' -t #{time} #{quote(title)} '
  on_metadata(fun (m) -> system(send^quote(display(m))),s)
end

%ifdef input.external
# Stream data from mplayer
# @category Source / Input
# @param s data URI.
# @param ~restart restart on exit.
# @param ~restart_on_error restart on exit with error.
# @param ~buffer Duration of the pre-buffered data.
# @param ~max Maximum duration of the buffered data.
# @category Source / Input
def input.mplayer(~id="input.mplayer",
         ~restart=true,~restart_on_error=false,
         ~buffer=0.2,~max=10.,s) =
  input.external(id=id,restart=restart,
                 restart_on_error=restart_on_error,
                 buffer=buffer,max=max,
                 "mplayer -really-quiet \
                    -af resample=#{get(default=44100,\"frame.audio.samplerate\")},channels=#{get(default=2,\"frame.audio.channels\")} \
                    -ao pcm:file=/dev/stdout \
                    -vc null -vo null #{quote(s)} 2>/dev/null")
end
%endif

%ifdef input.external.avi
  # Stream video from ffmpeg
  # @category Source / Input
  # @param s data URI.
  # @param ~restart restart on exit.
  # @param ~restart_on_error restart on exit with error.
  # @param ~buffer Duration of the pre-buffered data.
  # @param ~max Maximum duration of the buffered data.
  # @category Source / Input
  def input.ffmpeg.video(~id="input.ffmpeg.video",
    ~restart=true,~restart_on_error=false,
    ~buffer=0.2,~max=10.,~format="",s) =
      ffmpeg = get(default="ffmpeg#{exe_ext}","decoder.external.ffmpeg.path")
      format = if format == "" then "" else "-f #{format}" end
      audiorate = get(default=44100,"frame.audio.samplerate")
      width = get(default=320,"frame.video.width")
      height = get(default=240,"frame.video.height")
      videorate = get(default=24,"frame.video.samplerate")
      command = "#{ffmpeg} -v 16 #{format} -i #{quote(s)} \
      -f avi -vf format=rgb24 -vcodec rawvideo -r #{videorate} -s #{width}x#{height} \
      -acodec pcm_s16le -ar #{audiorate} \
      pipe:1"
      log("FFMpeg command: " ^ command)
      input.external.avi(id=id,restart=restart,
        restart_on_error=restart_on_error,
        buffer=buffer,max=max,
        command)
  end
%endif

%ifdef input.external.rawvideo
  # Decode video using memncoder
  # @category Source / Input
  def input.mencoder.video(~id="input.mencoder.video",
    ~restart=true,~restart_on_error=false,
    ~buffer=0.2,~max=10.,s) =
      # audiorate = get(default=44100,"frame.audio.samplerate")
      # width = get(default=320,"frame.video.width")
      # height = get(default=240,"frame.video.height")
      # videorate = get(default=24,"frame.video.samplerate")
      command = "mencoder -really-quiet #{quote(s)} -of rawvideo -ovc raw -vf format=rgb24 -oac copy -o -"
      log(command)
      input.external.rawvideo(id=id,restart=restart,
        restart_on_error=restart_on_error,
        buffer=buffer,max=max,
        command)
  end
%endif