Another solution for 24hr jukebox in Airtime
  • Not sure if this should be Dev or Support, just wanted to share.
    I have Airtime set up with the default silent source replaced with a random jukebox of songs. I wanted the tracks for the jukebox to be in the Airtime DB but I had other tracks like shows, jingles and podcasts in the DB too. I solved the problem by having a php script read the PostgreSQL database and make a playlist of files, filtering by particular metadata (I chose genre). I now have a playlist of just music (filtering genres like "Podcast"), and another of just jingles which I have playing in a ratio of 1:4.

    If anyone is interested I can post snippets of the code. I'm a bit pushed for time right now.
    Ideally I would like to be able to integrate the jukebox more with the main airtime system, at least to get the metadata of the random tracks to pass to the dashboard. I've been hacking on Liquidsoap for ages but the python and other web stuff is a bit tougher.
  • 28 Comments sorted by
  • D'oh!

    That is exactly what I'm trying to do in my spare time! :D

    I was planning on using the "request" method instead, but I'm not sure what is better...

    meditatamente,
    Francesco P.

  • Really cool!! please post the code when you get a chance :).
    Airtime Pro Hosting: http://airtime.pro
  • That's what I've done so far (attached): a PHP script to extract a random song with a maximum length, including or excluding from a genre list.

    Also a simple test liquidsoap script which uses the PHP script in a request() method (attached too).

    It works so far, ASAP I have to try to embed in Airtime background playlist hack, which would look like:

    default_music = request.dynamic(fun() -> request.create(list.hd(get_process_lines("/opt/scripts/randomSong.php -t 00:10:00 -e jingle"))))
    default_music = normalize(amplify(1., override="replay_gain", default_music))
    default_jingle = playlist(mode = "randomize", reload = 1, reload_mode = "rounds", "/data/airtime/jingle")
    default_jingle = normalize(amplify(1., override="replay_gain", audio_to_stereo(default_jingle)))
    default = smart_crossfade(rotate(weights = [1, 5], [default_jingle, default_music]))
    ignore(output.dummy(default, fallible=true))

    I have the impression that it is useless to use the replay_gain, I don't understand if it is saved only in the database or also in the original file... I'll discover that, maybe. :)

    sperimentatamente,
    Francesco P.

  • Code attached.
    jukebox.liq is %included before default is defined and I redefined it as "default = auto". The two functions are transitions to replace the ones in the default_switch, nicer fade and skipping the next sequence when going back to the jukebox. There's also an hourly chime defined in there.

    The script.php is ran as a cron job every hour. It's a pretty simple query and write to file. I like the idea of doing a dynamic request through LS instead. Keeps it more up to date.
    I've been getting the impression that the replay_gain is in the database rather than the file. Another reason I'd like to be able to get the main system to handle the jukebox.
  • aukdonk,

    about the replay gain: if the value is only in the database, and if Airtime uses liquidsoap to apply play files and apply replay gain, then there is a way with which also we can use database stored replay gain in our scripts. Tell me where I'm wrong. :)

    ipotizzatamente,
    Francesco P.

  • Reply to @FrancescoP:
    It should be just a case of replacing the metadata for each file with the metadata in the database. http://savonet.sourceforge.net/doc-svn/metadata.html
    It won't work with my way as I'm just producing a playlist of URIs, but your way might work.

    The more I investigate however, the more I want to dig into the scheduler and make it handle it, possibly a foolhardy desire but that won't stop me looking. :)
  • Reply to @FrancescoP:
    My current thought is to have the script you've made only return the id
    of the track. Then have two other scripts, one to collect the full file
    path to be the source and the other collect the replay_gain value to be
    added to the source metadata.

    eg:
    songid = randomsongscript.php -t 00:10:00 -e jingle
    music = getpath.php -id songid
    songgain = getgain.php -id songid
    music = map_metadata([("replay_gain",songgain)],music)
    music = amplify(override="replay_gain",1.,music)

    It's
    possible the Liquidsoap scripting can retrieve more than one variable
    from a script in which case there could be fewer DB calls but this way I
    can get my head around.
    My server is down right now so I can't test it quickly.
  • Reply to @aukondk:

    I think that Airtime is already doing what you want to do. A quick search with "replay_gain" e "replaygain" in her liquidsoap script revealed some kind of external script call - then I had to interrupt investigation.

    But I think we may ask the developer themself how do they do this. :)

    probabilmente,
    Francesco P.

  • Reply to @FrancescoP:

    I'm replying myself to post an updated version of the code to use in ls_script.liq, which I have placed in an external file to be included (thanks for the idea @aukdonk, now even updates are easier to mantain!).

    So, in ls_script, at about line 122 (Airtime 2.2.x), the script will look like this:

    #default = amplify(id="silence_src", 0.00001, noise())
    #default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default)
    %include "jukebox.liq"
    ignore(output.dummy(default, fallible=true))

    The "jukebox.liq" script is attached here, changes are in some mksafe directives and different normalizations.

    Note: on one system where I'm trying this script, sometimes something fails, the request() stops and Airtime goes offline. On another one everything seems to works fine, but I had to schedule a daily reboot to severe Airtime mess up with a repeated use of Master DJ. So, use at your own risk. :)

    aggiornatamente,
    Francesco P.

  • I've decided to get Liquidsoap's script to add the replay gain metadata to the file as it is played. My server is pretty powerful so it shouldn't cause slowdown.
    http://savonet.sourceforge.net/doc-svn/replay_gain.html
    I've made my script append "replay_gain:" to the file URIs and added an amplify line to just the music (leaving out the jingles). Can't tell if it's working as the log isn't verbose about it. Sounds alright though.
  • Reply to @aukondk:

    Can you post a sample? :)

    curiosamente,
    Francesco P.

  • Reply to @FrancescoP:

    script.php
            fwrite ($f, "replay_gain:/srv/airtime/stor/$col_value\n");


    jukebox.liq
    music = playlist(reload = 100, "/home/www/radio/playlist.txt") #random playlist
    music = amplify(1.,override="replay_gain",music)
    music = skip_blank(threshold=-50.,length=3., music)
    jingles = playlist(reload = 100, "/home/www/radio/jingles.txt")
    auto = rotate(weights = [1, 4],[jingles, music])

    The
    playlist function apparently has it's own prefix function but I
    couldn't tell if it was working so I made the php script add the
    "replay_gain:" to the front of the URIs so I could see it in the txt file it makes.

    With your script you probably just need to concatenate "replay_gain:" to the front of your $output variable.
  • Forgot my latest files with new changes.
  • Hello aukondk

    thanks for your script !
    Its ok for script.php but i have a problem to start the 24hr jukebox.
    I suppose that i must include jukebox.liq in ls_script.liq, but where and how ? please. :)
    Can you give me your ls_script, please ?
  • Reply to @Raphael+Fery:

    Go into ls_script.liq and find where default is first defined.
    Looks like this:

    default = amplify(id="silence_src", 0.00001, noise())
    default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default)
    ignore(output.dummy(default, fallible=true))

    Change to look like this:

    %include "jukebox.liq"
    #default = amplify(id="silence_src", 0.00001, noise())
    #default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default)
    default = auto
    #ignore(output.dummy(default, fallible=true))

    The hashes comment out the lines so they can be removed, I leave them in case I want to go back to normal.
  • it would not be easier to ask the script to load an existing playlist. So you could edit that playlist directly in Airtime and call it "default" or "jukebox". So any track that the jukebox playlist contain would be played when nothing is scheduled. Same for the jingles, you could create a playlist "jingles" inside airtime and load it the same way.
  • Ok, thanks, but it doesnt work, i don't why. I will stay with no hack and wait futurs versions of airtime. :)
  • first of all thx for your script
    i've tested your script but in a different version..

    this is my ls_script.liq


    %include "jukebox.liq"
    music = playlist(reload = 100, "/var/www/owncloud/data/niculin/files/random") #random playlist
    music = skip_blank(threshold=-50.,length=3., music)
    jingles = playlist(reload = 100, "/var/www/radio/jingles")
    auto = rotate(weights = [1, 4],[jingles, music])
    default = auto


    and this is my jukebox.liq


    def from_jukebox(old,new)
    add(normalize=false,
    [ sequence([ blank(duration=0.1),
    fade.initial(duration=5.,new) ]),
    fade.final(duration=5.,old) ])
    end

    def to_jukebox(a,b) =
    log("transition called...")
    source.skip(b)
    add(normalize=false,
    [ sequence([ blank(duration=0.01),
    fade.initial(duration=!default_dj_fade, b) ]),
    fade.final(duration=!default_dj_fade, a) ])
    end

    I found two problems:
    1) when liquidsoap change from Jukebox mode to the Scheduled show, liquidsoap interrupt the song without fade
    2) The second problem is that when liquidsoap switch from the scheduled show to the random selection, liquidsoap resumes the song stopped before, creating a bad effect

    any solution for these issues?

    p.s. is possible to have crossfade between songs?
    Post edited by nicolaM at 2013-01-11 19:07:21
  • Reply to @nicolaM:

    Hi Nicola!

    Nice trick the skip_blank(), I forgot about that! :)

    To crossfade between songs, you have both smart_crossfade() and crossfade() functions, the first being a more complete option. You can learn about them here: http://savonet.sourceforge.net/doc-1.0.1/reference.html - I've used smart_crossfade() in my script attached in a previous post of this thread.

    I don't understand one thing about your script: where does from_jukebox() and to_jukebox functions are being called from? Who triggers them?

    curiosamente,
    Francesco P.

  • ok i modified the script with smart_crossfade ;)
    oh yes jukebox functions are not called.. how to use your new functions aukondk? :)




    aukondk said:

    Post edited by nicolaM at 2013-01-13 20:30:16
  • ok i found
    in ls_script.liq

    s = switch(id="default_switch", track_sensitive=false,

        transitions=[from_jukebox, to_jukebox],

        [({!scheduled_play_enabled}, stream_queue),({true},default)])

    but i've some problem.
  • Reply to @nicolaM:
    Hi, sorry I didn't check this thread for a while.

    The two functions from_jukebox and to_jukebox should fade the jukebox out into the scheduled track and skip to the beginning of the "rotate()" cycle when it goes back to the jukebox (so it should play a jingle then 4 songs).

    I've checked my code and mine works with the above code (transitions=[from_jukebox, to_jukebox],)
    If it's not working for you still it may be the %include "jukebox.liq" isn't working. Try pasting the two functions into ls_script.liq just before the s = switch code. I only put my code in a separate file to make it easier/quicker to hack.

  • If anyone is interested. I've added a "simple" way to schedule a track to be placed into the Jukebox playlist but to wait until the current track is finished.

    Simply add:
    auto = fallback([request.queue(id="push"), auto])
    after the auto =rotate() line to create a dynamic request queue which can be added to via telnet commands.
    I have attached a "hit of the day" php script which checks the Airtime database for a track which hat "hit" or "hit" in the genre then sends commands to enqueue an announcement and the track if it finds one. This script is called by cron on the server at set times.
    The code is pretty ugly and opens the telnet socket multiple times because for some reason I couldn't get it to do more than one track at once (although it worked before on my old liquidsoap server).

    It's possible to also let listeners send requests via your website using the same method.
  • Reply to @aukondk:

    no no the script works, but in a strange way :)
    practically .. the script makes the transition from the jukebox to the show  with a little crossfade.. and it's ok, i would prefer a simply fade-out without fade-in but doesn't matter, it's same a great step forward!

    the problem is in the transition from the show to the jukebox, the script play only 2 second of the previous interrupted song, and then it start with a new song..

    Do you have the same problem?

    ls_script
    %include "jukebox.liq"
    music = playlist(reload = 100, "/var/www/owncloud/data/niculin/files/random") #random playlist
    music = skip_blank(threshold=-50.,length=3., music)
    jingles = playlist(reload = 100, "/var/www/radio/jingles")
    auto = rotate(weights = [1, 4],[jingles, music])
    default = auto

    jukebox.liq
    def from_jukebox(old,new)
      add(normalize=false,
          [ sequence([ blank(duration=0.1),
                       fade.initial(duration=5.,new) ]),
            fade.final(duration=5.,old) ])
    end

    def to_jukebox(a,b) =
      log("transition called...")
      source.skip(b)
      add(normalize=false,
         [ sequence([ blank(duration=0.01),
                       fade.initial(duration=!default_dj_fade, b) ]),
            fade.final(duration=!default_dj_fade, a) ])
    end



    anyways good job aukondk! :)
    Post edited by nicolaM at 2013-01-25 20:18:30
  • Reply to @nicolaM:
    The problem seems familiar but I seem to remember for me it was only a split second of the old track and then only occasionally. My server might be a bit more powerful than yours and can process the script quicker.
    Maybe try moving the source.skip() function to the from_jukebox function so the skipping happens earlier:
    eg
    def from_jukebox(old,new)

      add(normalize=false,

          [ sequence([ blank(duration=0.1),

                       fade.initial(duration=5.,new) ]),

            fade.final(duration=5.,old) ])

    source.skip(old)
    end


    and remove source.skip(b) from to_jukebox.
  • aukondk said:

    Reply to @nicolaM:
    The problem seems familiar but I seem to remember for me it was only a split second of the old track and then only occasionally. My server might be a bit more powerful than yours and can process the script quicker.
    Maybe try moving the source.skip() function to the from_jukebox function so the skipping happens earlier:
    eg
    def from_jukebox(old,new)

      add(normalize=false,

          [ sequence([ blank(duration=0.1),

                       fade.initial(duration=5.,new) ]),

            fade.final(duration=5.,old) ])

    source.skip(old)
    end


    and remove source.skip(b) from to_jukebox.



    I've just tried this out because I noticed it happen on my setup. Didn't work :( Not sure what to try next.
  • Vote Up0Vote Down aukondkaukondk
    Posts: 26Member
    New problem with my setup on 2.3.1
    I added my little hacks and now the random jukebox plays but the scheduled shows don't play. The stream doesn't switch from "default" to the show. default_switch has become schedule_noise_switch but I don't see any other real difference on the LS side but don't know if something else changed on the Airtime side.
  • Pretty sure they're re-writing portions of the scheduler. It isn't truly accurate down to the second, so if it isn't done now, it'll need to be done at some point anyway...