“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)

Hey thanks for you script! Where do I locate my playlist path?

Hey, glad you found the script useful! :blush:
The playlist path depends on your installation setup — by default, LibreTime uses a standard media or playlist folder, but you can also create your own directory and update the path in the script accordingly. It just needs to point to where your audio files or playlists are stored.