“AutoDJ” liquidsoap code for 4.5.0 (updated)

I’ve extended the default Liquidsoap setup to automatically play a random music playlist whenever no shows are scheduled or a DJ isn’t live. I tested it on LibreTime 4.5.0, and it appears to work just as well on earlier releases. The core steps are:

1- Create autodj.liq

sudo nano /opt/libretime/lib/python3.9/site-packages/libretime_playout/liquidsoap/1.4/autodj.liq

Copy&past the code below and change path and .mp3 accordingly

# Your jingles
jingles = playlist("/myradio/jingle")

# Your backup playlist
musics = playlist(mode="randomize",reload=60,reload_mode="rounds","/myradio/music")

# Your hours time signals 
clock = single(id="clock","/myradio/clock/clock.mp3")

# Your security jingle if the AutoDJ drop
security = single("/myradio/offair/offair.mp3")

# 1 jingle every 4 songs
streamradio = rotate(weights=[1,4], [jingles, musics])

# Setting up the crossfade (You do not have to change anything here)
def crossfade (~start_next=5.,~fade_in=3.,
               ~fade_out=3., ~width=2.,
               ~conservative=false,s)
  high   = -20.
  medium = -32.
  margin = 4.
  fade.out = fade.out(type="sin",duration=fade_out)
  fade.in  = fade.in(type="sin",duration=fade_in)
  add = fun (a,b) -> add(normalize=false,[b,a])
  log = log(label="crossfade")

  def transition(a,b,ma,mb,sa,sb)

    list.iter(fun(x)->
       log(level=4,"Before: #{x}"),ma)
    list.iter(fun(x)->
       log(level=4,"After : #{x}"),mb)

    if
      # If A and B and not too loud and close,
      # fully cross-fade them.
      a <= medium and
      b <= medium and
      abs(a - b) <= margin
    then
      log("Transition: crossed, fade-in, fade-out.")
      add(fade.out(sa),fade.in(sb))

    elsif
      # If B is significantly louder than A,
      # only fade-out A.
      # We don't want to fade almost silent things,
      # ask for >medium.
      b >= a + margin and a >= medium and b <= high
    then
      log("Transition: crossed, fade-out.")
      add(fade.out(sa),sb)

    elsif
      # Do not fade if it's already very low.
      b >= a + margin and a <= medium and b <= high
    then
      log("Transition: crossed, no fade-out.")
      add(sa,sb)

    elsif
      # Opposite as the previous one.
      a >= b + margin and b >= medium and a <= high
    then
      log("Transition: crossed, fade-in.")
      add(sa,fade.in(sb))

    # What to do with a loud end and
    # a quiet beginning ?
    # A good idea is to use a jingle to separate
    # the two tracks, but that's another story.

    else
      # Otherwise, A and B are just too loud
      # to overlap nicely, or the difference
      # between them is too large and
      # overlapping would completely mask one
      # of them.
      log("No transition: just sequencing.")
      sequence([sa, sb])
    end
  end

  cross(width=width, duration=start_next,
        conservative=conservative,
        transition,s)
end

# Enable crossfade between Tracks and Jingles
streamradio = crossfade(start_next=5.,fade_out=3.,fade_in=3.,streamradio)


# Enable every hours time signals 
streamradio = add([streamradio, switch([
                        ({0m0s},clock),
                        ])
])

# Metadata updater
def update_metadata(m) =
  title = m["title"]
  ignore(title)
  artist = m["artist"]
  ignore(artist)

  [("artist","#{artist}"),("title","#{title}")]
end

# Enable AutoDJ with metadata
autodj = fallback(track_sensitive = false,[streamradio,security])
autodj = map_metadata(update_metadata, autodj)

Save your new created autodj.liq

2- Include autodj.liq in your ls_script.liq script

sudo nano /opt/libretime/lib/python3.9/site-packages/libretime_playout/liquidsoap/1.4/ls_script.liq

Find %include "ls_lib.liq" and copy&past just below:

%include "autodj.liq"

3- Comment (disable) those line in ls_script.liq

#default = amplify(id="silence_src", 0.00001, noise())

#def map_message_offline(m) =
#  [("title", message_offline())]
#end

#default = map_metadata(id="map_metadata:offline", map_message_offline, default)
#ignore(output.dummy(default, fallible=true))

4- Smooth fade between AutoDJ and shows in ls_script.liq

Copy&past the following code beneath the lines you just commented.

# Whenever no shows are scheduled and no DJ is live, it switches to AutoDJ.
default = autodj

# AutoDJ → scheduled show: 3-second fade-out, then a clean start
def to_schedule(old, new) =
  log("TRANSITION → fade-out 3s autodj → Scheduled show start no fade in")
  sequence([
    fade.final(duration=3.0, old),
    new
  ])
end

# Scheduled show → AutoDJ: 3-second fade-out, then resume AutoDJ
def to_outro(old, new) =
  log("TRANSITION → fade-out 3s shows ending → start autodj")
  sequence([
    fade.final(duration=3.0, old),
    fade.initial(duration=3.0, new)
  ])
end
# fin

5- Update the primary switch in ls_script.liq

Replace -

#s = switch(id="switch:blank+schedule",
#            track_sensitive=false,
#            transitions=[transition_default, transition],
#            [({!schedule_streaming}, stream_queue), ({true}, default)]
#    )

for -

# Switch with fade Intro and Outro
s = switch(id="switch:blank+schedule",
            track_sensitive=false,
            transitions=[to_schedule, to_outro],
  [
    ({ !schedule_streaming }, stream_queue),
    ({ true                        }, autodj)
  ]
 )

# → Final output to Icecast (or other)
ignore(output.dummy(s, fallible=true))

Because it’s impossible to edit my original post I’m sharing an updated autodj.liq script for LibreTime 4.5.0, where I’ve made a few small tweaks to:

  • Instantly detect new music files: Lowering reload from 60 to 1 makes Liquidsoap notice new or removed files immediately after playing all tracks, which is useful if you’re swapping music in real time (e.g. uploading fresh tracks to the directory).
  • Remove extended silences between tracks: Introducing skip_blank strips out any pauses longer than 4 s under -40 dB, so the stream never stalls on extended silence between tracks.
  • Refine the crossfade thresholds for smoother transitions: By raising the “high” level from –20 dB to –15 dB, you allow somewhat louder tracks to be considered for overlapping fades, which can produce a smoother handoff on dynamic material.

Feel free to give it a try and let me know what you think!

# Your jingles
jingles = playlist("/myradio/jingle")

# Your backup playlist
musicsraw = playlist(mode="randomize", reload=1, reload_mode="rounds","/myradio/music")

# Remove any silence > 4 s below -40 dB
musics = skip_blank(musicsraw,
  max_blank = 4.,
  threshold = -40.
)

# Your hours time signals 
clock = single(id="clock","/myradio/clock/clock.mp3")

# Your security jingle if the AutoDJ drop
security = single("/myradio/offair/offair.mp3")

# 1 jingle every 4 songs
streamradio = rotate(weights=[1,4], [jingles, musics])

# Setting up the crossfade (You do not have to change anything here)
def crossfade (~start_next=5.,~fade_in=3.,
               ~fade_out=3., ~width=2.,
               ~conservative=false,s)
  high   = -15.
  medium = -32.
  margin = 4.
  fade.out = fade.out(type="sin",duration=fade_out)
  fade.in  = fade.in(type="sin",duration=fade_in)
  add = fun (a,b) -> add(normalize=false,[b,a])
  log = log(label="crossfade")

  def transition(a,b,ma,mb,sa,sb)

    list.iter(fun(x)->
       log(level=4,"Before: #{x}"),ma)
    list.iter(fun(x)->
       log(level=4,"After : #{x}"),mb)

    if
      # If A and B and not too loud and close,
      # fully cross-fade them.
      a <= medium and
      b <= medium and
      abs(a - b) <= margin
    then
      log("Transition: crossed, fade-in, fade-out.")
      add(fade.out(sa),fade.in(sb))

    elsif
      # If B is significantly louder than A,
      # only fade-out A.
      # We don't want to fade almost silent things,
      # ask for >medium.
      b >= a + margin and a >= medium and b <= high
    then
      log("Transition: crossed, fade-out.")
      add(fade.out(sa),sb)

    elsif
      # Do not fade if it's already very low.
      b >= a + margin and a <= medium and b <= high
    then
      log("Transition: crossed, no fade-out.")
      add(sa,sb)

    elsif
      # Opposite as the previous one.
      a >= b + margin and b >= medium and a <= high
    then
      log("Transition: crossed, fade-in.")
      add(sa,fade.in(sb))

    # What to do with a loud end and
    # a quiet beginning ?

    else
      # Otherwise, A and B are just too loud
      # to overlap nicely, or the difference
      # between them is too large and
      # overlapping would completely mask one
      # of them.
      log("No transition: just sequencing.")
      sequence([sa, sb])
    end
end

  cross(width=width, duration=start_next,
        conservative=conservative,
        transition,s)
end

# Enable crossfade between Tracks and Jingles
streamradio = crossfade(start_next=5.,fade_out=3.,fade_in=3.,streamradio)


# Enable every hours time signals 
streamradio = add([streamradio, switch([
                        ({0m0s},clock),
                        ])
])

# Metadata updater
def update_metadata(m) =
  title = m["title"]
  ignore(title)
  artist = m["artist"]
  ignore(artist)

  [("artist","#{artist}"),("title","#{title}")]
end

# Enable AutoDJ with metadata
autodj = fallback(track_sensitive = false,[streamradio,security])
autodj = map_metadata(update_metadata, autodj)