From 7825744006f760d4a3c6412f02813b4a1b240489 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Fri, 19 May 2023 12:30:47 +0200 Subject: [PATCH] Fix duration/progress parsing. --- icedreammusic/liquidsoap/metadata_api.liq | 127 +++++++++++++++------- icedreammusic/tunaposter/main.go | 51 +++++---- 2 files changed, 118 insertions(+), 60 deletions(-) diff --git a/icedreammusic/liquidsoap/metadata_api.liq b/icedreammusic/liquidsoap/metadata_api.liq index 7457aa8..65a4ddc 100644 --- a/icedreammusic/liquidsoap/metadata_api.liq +++ b/icedreammusic/liquidsoap/metadata_api.liq @@ -4,17 +4,18 @@ metadata_api_hostname = environment.get(default="icedream-bitwave", "METADATA_API_HOSTNAME") -def http_export_meta(%argsof(json.stringify), m) = - j = json() - list.iter((fun (v) -> j.add(fst(v), (snd(v):string))), m) - json.stringify(%argsof(json.stringify), j) -end +def setup_harbor_metadata_api(~metadata_api_port=21338, ~id="", s) = + # HACK - work around https://github.com/savonet/liquidsoap/issues/2996 + id = if id != "" then id else s.id() end -def setup_harbor_metadata_api(~metadata_api_port=21338, s) = - id = s.id() s = drop_metadata(s) # stream metadata wipes out own data s = insert_metadata(s) + # holder for dynamic meta (things we don't want to go out over icecast + # because they change a LOT like duration) + dynamic_duration = ref(null()) + dynamic_progress = ref(null()) + # Handler for fetching metadata def on_http_get_metadata(request) = http_version = request.http_version @@ -36,7 +37,19 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) = # data = metadata.json.stringify(compact=true, m) m = metadata.cover.remove(m) - data = http_export_meta(compact=true, m) + + j = json() + list.iter((fun (v) -> j.add(fst(v), (snd(v):string))), m) + + # add dynamic metadata + if null.defined(dynamic_duration()) then + j.add("duration", null.get(dynamic_duration())) + end + if null.defined(dynamic_progress()) then + j.add("progress", null.get(dynamic_progress())) + end + + data = json.stringify(compact=true, j) http.response(http_version=http_version, status_code=200, headers=[ ("access-control-allow-origin","*"), @@ -81,42 +94,78 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) = # Handler for receiving metadata def on_http_metadata(request) = http_version = request.http_version - data = request.data() + raw_data = request.body() headers = request.headers - let json.parse (data : { - data: [(string * string)] as json.object - }) = data + # log.info("New data for #{id}: #{data}") - m = data.data + let json.parse ({ + data = { + duration, + progress, + }, + } : { + data: { + duration: int?, + progress: int?, + } + }) = raw_data + let json.parse ({ + data + } : { + data: [(string * string?)] as json.object + }) = raw_data + m = data - # TODO - we remove cover art for now as it disturbs REKT, this needs fixing - # m = metadata.cover.remove(m) - - new_track = if list.assoc.mem("new_track", m) then bool_of_string(string_of(list.assoc("new_track", m))) else false end - - # merge old metadata except for the ones we expect to change + # old artist and title oldm = s.last_metadata() ?? [] - oldm = if list.assoc.mem("artist", oldm) then list.assoc.remove("artist", oldm) else oldm end - oldm = if list.assoc.mem("title", oldm) then list.assoc.remove("title", oldm) else oldm end - oldm = if list.assoc.mem("album", oldm) then list.assoc.remove("album", oldm) else oldm end - oldm = if list.assoc.mem("publisher", oldm) then list.assoc.remove("publisher", oldm) else oldm end - oldm = if list.assoc.mem("genre", oldm) then list.assoc.remove("genre", oldm) else oldm end - oldm = if list.assoc.mem("date", oldm) then list.assoc.remove("date", oldm) else oldm end - oldm = if list.assoc.mem("tracknumber", oldm) then list.assoc.remove("tracknumber", oldm) else oldm end - oldm = if list.assoc.mem("comment", oldm) then list.assoc.remove("comment", oldm) else oldm end - oldm = if list.assoc.mem("track", oldm) then list.assoc.remove("track", oldm) else oldm end - oldm = if list.assoc.mem("year", oldm) then list.assoc.remove("year", oldm) else oldm end - oldm = if list.assoc.mem("dj", oldm) then list.assoc.remove("dj", oldm) else oldm end - oldm = if list.assoc.mem("next", oldm) then list.assoc.remove("next", oldm) else oldm end - oldm = if list.assoc.mem("apic", oldm) then list.assoc.remove("apic", oldm) else oldm end - oldm = if list.assoc.mem("metadata_block_picture", oldm) then list.assoc.remove("metadata_block_picture", oldm) else oldm end - oldm = if list.assoc.mem("coverart", oldm) then list.assoc.remove("coverart", oldm) else oldm end - oldm = if list.assoc.mem("cover_url", oldm) then list.assoc.remove("cover_url", oldm) else oldm end - m = list.append(oldm ?? [], m) + old_artist = if list.assoc.mem("artist", oldm) then oldm["artist"] else "" end + old_title = if list.assoc.mem("title", oldm) then oldm["title"] else "" end + new_artist = if list.assoc.mem("artist", m) then list.assoc("artist", m) ?? "" else "" end + new_title = if list.assoc.mem("title", m) then list.assoc("title", m) ?? "" else "" end - # set metadata on stream - s.insert_metadata(new_track=new_track, m) + if old_artist != new_artist or old_title != new_title then + # filter dynamic metadata + m = if list.assoc.mem("progress", m) then list.assoc.remove("progress", m) else m end + m = if list.assoc.mem("duration", m) then list.assoc.remove("duration", m) else m end + + # TODO - we remove cover art for now as it disturbs REKT, this needs fixing + # m = metadata.cover.remove(m) + + new_track = if list.assoc.mem("new_track", m) then bool_of_string(string_of(list.assoc("new_track", m))) else false end + + # merge old metadata except for the ones we expect to change + oldm = if list.assoc.mem("artist", oldm) then list.assoc.remove("artist", oldm) else oldm end + oldm = if list.assoc.mem("title", oldm) then list.assoc.remove("title", oldm) else oldm end + oldm = if list.assoc.mem("album", oldm) then list.assoc.remove("album", oldm) else oldm end + oldm = if list.assoc.mem("publisher", oldm) then list.assoc.remove("publisher", oldm) else oldm end + oldm = if list.assoc.mem("genre", oldm) then list.assoc.remove("genre", oldm) else oldm end + oldm = if list.assoc.mem("date", oldm) then list.assoc.remove("date", oldm) else oldm end + oldm = if list.assoc.mem("tracknumber", oldm) then list.assoc.remove("tracknumber", oldm) else oldm end + oldm = if list.assoc.mem("comment", oldm) then list.assoc.remove("comment", oldm) else oldm end + oldm = if list.assoc.mem("track", oldm) then list.assoc.remove("track", oldm) else oldm end + oldm = if list.assoc.mem("year", oldm) then list.assoc.remove("year", oldm) else oldm end + oldm = if list.assoc.mem("dj", oldm) then list.assoc.remove("dj", oldm) else oldm end + oldm = if list.assoc.mem("next", oldm) then list.assoc.remove("next", oldm) else oldm end + oldm = if list.assoc.mem("apic", oldm) then list.assoc.remove("apic", oldm) else oldm end + oldm = if list.assoc.mem("metadata_block_picture", oldm) then list.assoc.remove("metadata_block_picture", oldm) else oldm end + oldm = if list.assoc.mem("coverart", oldm) then list.assoc.remove("coverart", oldm) else oldm end + oldm = if list.assoc.mem("cover_url", oldm) then list.assoc.remove("cover_url", oldm) else oldm end + m = list.append(oldm ?? [], m) + + # set metadata on stream + m = list.assoc.filter(fun (_, v) -> null.defined(v), m) + m = list.map(fun (v) -> (fst(v), null.get(snd(v))), m) + log.info("New metadata for #{id}: #{json.stringify(m)}") + s.insert_metadata(new_track=new_track, m) + else + log.info("No new metadata for #{id}") + end + + # set new dynamic meta + log.info("Updating dynamic meta for #{id}") + dynamic_duration.set(duration) + dynamic_progress.set(progress) http.response(http_version=http_version, status_code=200, headers=[ ("allow","POST"), @@ -125,7 +174,7 @@ def setup_harbor_metadata_api(~metadata_api_port=21338, s) = ("access-control-allow-methods","GET,POST"), ("access-control-allow-headers","Origin,X-Requested-With,Content-Type,Accept,Authorization,access-control-allow-headers,access-control-allow-origin"), ("content-type","application/json"), - ], data=json.stringify(data)) + ], data=raw_data) end # Just in case we use a browser to send data to this (for example while emulating Tuna) diff --git a/icedreammusic/tunaposter/main.go b/icedreammusic/tunaposter/main.go index 86343ba..ce0367b 100644 --- a/icedreammusic/tunaposter/main.go +++ b/icedreammusic/tunaposter/main.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "image" + "image/jpeg" "io" "log" "net" @@ -16,7 +17,6 @@ import ( "strings" "time" - "image/jpeg" _ "image/jpeg" _ "image/png" @@ -42,6 +42,8 @@ type liquidsoapMetadata struct { Title string `json:"title"` Publisher string `json:"publisher,omitempty"` Year string `json:"year,omitempty"` + Duration uint64 `json:"duration,omitempty"` + Progress uint64 `json:"progress,omitempty"` } func (lm *liquidsoapMetadata) SetCover(r io.Reader, compressToJPEG bool) (err error) { @@ -147,9 +149,12 @@ func main() { tunaData := new(tuna.TunaData) if err = json.NewDecoder(resp.Body).Decode(tunaData); err == nil { // skip empty or same metadata - differentDataReceived := oldTunaData == nil || + differentSongReceived := oldTunaData == nil || oldTunaData.Title != tunaData.Title || len(oldTunaData.Artists) != len(tunaData.Artists) + differentDataReceived := differentSongReceived || + oldTunaData.Progress != tunaData.Progress || + oldTunaData.Duration != tunaData.Duration if !differentDataReceived { for i, artist := range oldTunaData.Artists { differentDataReceived = differentDataReceived || artist != tunaData.Artists[i] @@ -161,6 +166,8 @@ func main() { CoverURL: tunaData.CoverURL, Publisher: tunaData.Label, Title: tunaData.Title, + Duration: tunaData.Duration, + Progress: tunaData.Progress, } if tunaData.Year > 0 { @@ -168,28 +175,30 @@ func main() { } // transfer cover to liquidsoap metadata - if coverURL, err := url.Parse(tunaData.CoverURL); err == nil { - if strings.EqualFold(coverURL.Scheme, "http") || - strings.EqualFold(coverURL.Scheme, "https") { - log.Println("Downloading cover:", tunaData.CoverURL) - resp, err := http.Get(tunaData.CoverURL) - if err == nil { - err = liquidsoapMetadata.SetCover(resp.Body, true) - resp.Body.Close() - if err != nil { - log.Println("WARNING: Failed to transfer cover to liquidsoap metadata, skipping:", err.Error()) + if differentSongReceived { + if coverURL, err := url.Parse(tunaData.CoverURL); err == nil { + if strings.EqualFold(coverURL.Scheme, "http") || + strings.EqualFold(coverURL.Scheme, "https") { + log.Println("Downloading cover:", tunaData.CoverURL) + resp, err := http.Get(tunaData.CoverURL) + if err == nil { + err = liquidsoapMetadata.SetCover(resp.Body, true) + resp.Body.Close() + if err != nil { + log.Println("WARNING: Failed to transfer cover to liquidsoap metadata, skipping:", err.Error()) + } } - } - // remove reference to localhost/127.*.*.* - localhost := coverURL.Host == "localhost" || strings.HasSuffix(coverURL.Host, ".localhost") - if !localhost { - if ip := net.ParseIP(coverURL.Host); ip != nil { - localhost = ip[0] == 127 + // remove reference to localhost/127.*.*.* + localhost := coverURL.Host == "localhost" || strings.HasSuffix(coverURL.Host, ".localhost") + if !localhost { + if ip := net.ParseIP(coverURL.Host); ip != nil { + localhost = ip[0] == 127 + } + } + if localhost { + liquidsoapMetadata.CoverURL = "" } - } - if localhost { - liquidsoapMetadata.CoverURL = "" } } }