# # Aura Engine (https://gitlab.servus.at/aura/engine) # # Copyright (C) 2017-2020 - The Aura Engine Team. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # METADATA # # Merge with previous metadata, avoid duplicates def merge_meta(last_meta, meta) = log(level=5, label="metadata", "Merge | last metadata: #{last_meta}") log(level=5, label="metadata", "Merge | current metadata: #{meta}") merged = ref(last_meta) def add_meta_entry(entry) = let (k, _) = entry if list.assoc.mem(k, !merged) then log(level=5, label="metadata", "Remove existing entry #{entry}") merged := list.assoc.remove(k, !merged) end merged := list.add(entry, !merged) end list.iter(add_meta_entry, (meta)) log(level=5, label="metadata", "Merge | resulting metadata: #{!merged}") !merged end # Checks for the existence of show-specific metadata def has_show_meta(meta) = list.assoc.mem(engine_meta_key_show_id, meta) ? true : false end # Checks if the show ID in two metadata objects matches def is_same_show(last_meta, current_meta) = last_meta[engine_meta_key_show_id] == current_meta[engine_meta_key_show_id] ? true : false end # Checks if the current show metadata is same as the previous one def is_same_show(last_meta, current_meta) = if has_show_meta(last_meta) then if not has_show_meta(current_meta) then # No current show meta: handle as same show true elsif is_same_show(last_meta, current_meta) then true else # A new show has started false end else # Last show has no show meta if not has_show_meta(current_meta) then # And the current one either: handle as same show true else # Treat missing last show info as the same show true end end end # Handles either insert or merge & insert of metadata, depending on the show ID def do_meta_insert(last_meta_callback, insert_meta_callback, meta) = lm = (last_meta_callback() ?? []) if is_same_show(lm, meta) then lm = (last_meta_callback() ?? []) merged = merge_meta(lm, meta) insert_meta_callback(merged) else insert_meta_callback(meta) end end # Builds a metadata object from data passed as JSON def build_metadata(json_string) = let json.parse (data : { show_name: string, show_id: int, timeslot_id: int, playlist_id: int, playlist_item: string, track_type: int, track_start: string?, track_duration: int?, track_title: string?, track_album: string?, track_artist: string? }) = json_string [ ("show_name", data.show_name), ("show_id", "#{data.show_id}"), ("timeslot_id", "#{data.timeslot_id}"), ("playlist_id", "#{data.playlist_id}"), ("playlist_item", "#{data.playlist_item}"), ("track_type", "#{data.track_type}"), ("track_start", "#{data.track_start}"), ("track_duration", "#{data.track_duration}"), ("track_title", "#{data.track_title}"), ("track_album", "#{data.track_album}"), ("track_artist", "#{data.track_artist}"), ] end # Reads the track duration # a.) when available from the file # b.) as a fallback from the meta field "track_duration" # # Returns # (int) duration in seconds def get_meta_track_duration(meta) = track_duration = int_of_float(request.duration(meta["filename"])) if track_duration != -1 then track_duration else int_of_string(meta["track_duration"]) end end # Posts a playlog to the Engine API def post_playlog(api_url, data) = json_data = json() json_data.add("log_source", int_of_string(list.assoc("log_source", data))) json_data.add("show_name", list.assoc("show_name", data)) json_data.add("show_id", int_of_string(list.assoc("show_id", data))) json_data.add("timeslot_id", int_of_string(list.assoc("timeslot_id", data))) json_data.add("playlist_id", int_of_string(list.assoc("playlist_id", data))) json_data.add("track_type", int_of_string(list.assoc("track_type", data))) json_data.add("track_start", list.assoc("track_start", data)) json_data.add("track_duration", int_of_string(list.assoc("track_duration", data))) json_data.add("track_title", list.assoc("track_title", data)) json_data.add("track_album", list.assoc("track_album", data)) json_data.add("track_artist", list.assoc("track_artist", data)) if list.assoc("track_num", data) != "" then json_data.add("track_num", int_of_string(list.assoc("track_num", data))) end playlog = json.stringify(json_data) log("Posting playlog to '#{api_url}': #{playlog}") headers = [("Content-Type","application/json")] result = http.post(headers=headers, data="#{playlog}", "#{api_url}") if result.status_code < 400 then log("Successfully posted playlog to Engine API.") else log("ERROR during playlog POST: #{result.status_code} | #{result.status_message}") end end # # SOURCE # # Evaluate the type of source # # Returns # "fallback", "queue", "stream", "analog_in", "unknown_source" def eval_source_type(source_id) = type_mapping = [ ("fallback_folder", "fallback"), ("fallback_playlist", "fallback"), ("in_queue_0", "queue"), ("in_queue_1", "queue"), ("in_stream_0", "stream"), ("in_stream_1", "stream"), ("in_line_0", "analog_in"), ("in_line_1", "analog_in"), ("in_line_2", "analog_in"), ("in_line_3", "analog_in"), ("in_line_4", "analog_in") ] let source_type = list.assoc( default="unknown_source", source_id, type_mapping ) source_type end # Evaluates the track type based on the given: # a.) "meta.track_type" passed as annotation, and if not available on # b.) "engine_current_track_type" passed via server function # c.) "meta.source" and if not available on # d.) configured default track type setting # # Returns: # 0=QUEUE/FILE, 1=STREAM, 2=LIVE ANALOG, 3=PLAYLIST # def eval_track_type(meta_track_type, meta_source) = type_mapping = [ ("fallback_folder", "0"), ("fallback_playlist", "3"), ("in_queue_0", "0"), ("in_queue_1", "0"), ("in_stream_0", "1"), ("in_stream_1", "1"), ("in_line_0", "2"), ("in_line_1", "2"), ("in_line_2", "2"), ("in_line_3", "2"), ("in_line_4", "2") ] track_type = list.assoc( default=engine_default_track_type, meta_source, type_mapping ) if meta_track_type != "" then meta_track_type elsif track_type != "" then track_type else engine_default_track_type end end