savonet/liquidsoap

Missing metadata in 2.3.x Rolling Release

gAlleb opened this issue · 16 comments

Description

Since #4039 (as pointed out by @vitoyucepi in #4072) we have no access to on_air and on_air_timestamp metadata.

P.S. The are important tags and used in some Now Playing APIs:

image

Liquidsoap version

Rolling Release 2.3.x

Hi and thanks for reporting.

As I said in the original discussion, I cleaned up the request implementation as part of 2.3.x. That code was super old and wasn't reflecting how we do things now.

In particular, on_air was a proper state of requests, beside idle, resolving, resolved and destroyed. This complicated the code a lot and did not reflect on the fact that a request can be used by multiple sources.

I understand the need to track what's on air at all time. We can definitely add the metadata back.

Here's my current thinking:

  • Outputs, like sources, have a last_metadata method. This is a much better source for what's currently playing.
  • We can add on_air back on request, it would be true whenever the request is played in any source.
  • We can add back on_air_timestamp and make it the earliest time the request was ever played.
  • We can make the on_air metadata optional, enabled via a setting, and add a warning in the logs whenever a request is used in multiple sources while on_air is activated

This way we make it easier to transition to 2.3.x when using on_air while making it clear that this is not the best method to implement nowplaying.

What do y'all think?

I think it's a valid thinking. It would be great if could keep on_air_timestamp and keep the way it has worked - indicating when track started playing on a source. on_air thingy is not that important imho, but a more representetive.

and did not reflect on the fact that a request can be used by multiple sources.

I kinda understand that) Kinda) Ok, it can be used by multiple sources - thus on_air_timestamp can have different values depending from what source we request this value. Can't this be a thing?)

Outputs, like sources, have a last_metadata method. This is a much better source for what's currently playing.

I do personally use on_air_timestamp to get the data along in two ways. One via on_metadata:

def save_history(m, ~last=10)

np = { played_at = null_list("on_air_timestamp", m) }

end

s.on_metadata(save_history)

But I suppose I may change it then to

def save_history(_, ~last=10)

m = s.last_metadata() ?? [] 

np = { played_at = null_list("on_air_timestamp", m) }

end

s.on_track(save_history)

Ha I see.

Yeah, the data would be better grabbed on the output on_track handler. There, you are sure to grab this exact time the output sees a given metadata.

Do I get it right?

We can add on_air back on request, it would be true whenever the request is played in any source.

  1. on_air will be changed when the same request is played on another source?

We can add back on_air_timestamp and make it the earliest time the request was ever played.

  1. Basically it means that we keep old functionality for on_air_timestamp if we are talking about a single source?

I made a mistake. I though on_air was a boolean but it's actually a pretty-print of on_air_timestamp.

Here's what I wrote for the migration doc:

on_air and on_air_timestamp metadata are deprecated. These values were never reliable. They are set at the request level when request.dynamic and all its derived sources start playing a request. However, a request can be used in multiple sources and the source using it can be used in multiple outputs or even not be actually being on the air if, for instance, it is not selected by a switch or fallback.

Instead, it is recommended to use output's on_track methods to track the metadata currently being played and the time at which it started being played. If needed, outputs (like sources) also have a last_metadata method to return the metadata last seen on any given output.

For backward compatibility and easier migration, on_air and on_air_timestamp metadata can be enabled using the request.deprecated_on_air_metadata setting:

request.deprecated_on_air_metadata := true

However, it is highly recommended to migrate your script to use output's on_track instead.

on_track methods to track the metadata currently being played

Something like this?

def f(m)
  t = time.local()
end

s.on_track(f)

Yeah! Or to mimic on_air_timestamp behavior while saving song history and now_playing info access:

np_timestamp = ref(0.0)
songHistory = ref([])

def save_history(_, ~last=10)
     np_timestamp := time()
     sh = { 
           np = { played_at = np_timestamp() }
           }

    songHistory := sh::songHistory()
            
   if list.length(songHistory()) > last then
    songHistory := list.prefix(last,songHistory())
  end  
end

s.on_track(save_history)

def get_NP_with_history(_)

data = { 
        np = { played_at = np_timestamp() },
        song_history = songHistory()
           }
   http.response(...)
end

harbor.http.register.simple("/nowplaying", get_NP_with_history, port=8007, method="GET")

UPDATE. Yeah, this works nicely.

I don't get it. The <source>.last_metadata() has no on_air tag.

Why? I've been using this function for a long time and it worked.

def write_json_nowplaying(s)

  def write_data()

  def null_float(f)
    f == infinity ? null() : f
  end

  def null_list(key, _list)
    #list.assoc.mem(key, _list) ? list.assoc(key, _list) : null()
    list.assoc.mem(key, _list) ? list.assoc(key, _list) : ""
  end

  m = s.last_metadata() ?? []

def get_cover_base64(~coverart_mime=null(), ~base64=true, m) =
  c = metadata.cover(coverart_mime=coverart_mime, m)
    if
      null.defined(c)
    then
      c = null.get(c)
      string.data_uri.encode(base64=base64, mime=c.mime, c)
    else
     ""
    end
end


  np = {
    now_playing = {
      played_at = null_list("on_air_timestamp", m),
      #played_at = np_timestamp(),
      played_at_timestamp = null_list("on_air_timestamp", m),
      #played_at_timestamp = np_timestamp(),
      played_at_date_time = null_list("on_air", m),
      duration = null_float(source.duration(s)),
      elapsed = null_float(source.elapsed(s)),
      remaining = null_float(source.remaining(s)),
      playlist = null_list("playlist", m),
      filename = null_list("filename", m),

      song = { 
     	  	artist = null_list("artist", m),
         	title = null_list("title", m),
          	album = null_list("album", m),
 	 	genre = null_list("genre", m),
		cover = get_cover_base64(m)
	     }
       },
    song_history = songHistory()
  }

if (m["jingle_mode"] != "true") then
send_mq_event(json.stringify(np, compact=true))
end

end
  thread.run(write_data, every=1.0, fast=false)
end
write_json_nowplaying(s)

Now of course it doesn't have neither "on_air" nor "on_air_timestamp".

Ran through 2.2.5

image

Now of course it doesn't have neither "on_air" nor "on_air_timestamp".

That's the problem. I think <source>.on_metadata, <source>.on_track and <source>.last_metadata should contain the start time for the <source>.

Ha) I think too. that's the whole point of this thread)

In 2.2.5 there has been on_air and on_air_timestamp. Now in 2.3.0 there are gone. Inaccessible neither by on_metadata request nor by last_metadata.

Or I don't get something?


I've got another question. Is there a possibility to make on_air with the power of time.local().

time.local() is

  dst : bool,
  year_day : int,
  week_day : int,
  year : int,
  month : int,
  day : int,
  hour : int,
  min : int,
  sec : int

and returns something like {dst=false, year_day=217, week_day=0, year=2024, month=8, day=4, hour=20, min=50, sec=44}. Maybe it can be parsed somehow to look like on_air tag If I want to use it on_track?

I have just pushed a commit to the branch fixing the issue that adds on_air and on_air_timestamp to all outputs metadata, both via on_track/on_metadata / last_metadata and with the telnet metadata command.

Hm. Doesn't seem to work.
Should I put this setting somewhere specific?

liquidsoap-1  | Error 5: 
liquidsoap-1  | this value has no method `deprecated_on_air_metadata`
liquidsoap-1  |   Its type is 
liquidsoap-1  | {

Sorry, it is: settings.request.deprecated_on_air_metadata

Yeah) I thought so)

Got it working.

image

Strings are on_air and on_air_timespamp

Thanks, Romain!

I've got another question. Is there a possibility to make on_air with the power of time.local().

Ok, I got it)

g = time.string("%Y/%m/%d %H:%M:%S")
print(g)