savonet / liquidsoap

Liquidsoap is a statically typed scripting general-purpose language with dedicated operators and backend for all thing media, streaming, file generation, automation, HTTP backend and more.
http://liquidsoap.info
GNU General Public License v2.0
1.4k stars 128 forks source link

Missing metadata in 2.3.x Rolling Release #4073

Closed gAlleb closed 1 month ago

gAlleb commented 1 month ago

Description

Since https://github.com/savonet/liquidsoap/pull/4039 (as pointed out by @vitoyucepi in https://github.com/savonet/liquidsoap/discussions/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
toots commented 1 month ago

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:

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?

gAlleb commented 1 month ago

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)
toots commented 1 month ago

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.

gAlleb commented 1 month ago

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?
toots commented 1 month ago

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.

vitoyucepi commented 1 month ago

on_track methods to track the metadata currently being played

Something like this?

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

s.on_track(f)
gAlleb commented 1 month ago

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.

vitoyucepi commented 1 month ago

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

gAlleb commented 1 month ago

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
vitoyucepi commented 1 month ago

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>.

gAlleb commented 1 month ago

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?

toots commented 1 month ago

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.

gAlleb commented 1 month ago

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  | {
toots commented 1 month ago

Sorry, it is: settings.request.deprecated_on_air_metadata

gAlleb commented 1 month ago

Yeah) I thought so)

Got it working.

image

Strings are on_air and on_air_timespamp

Thanks, Romain!

gAlleb commented 1 month ago

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)