From 0705edb39b7115d881c64f5babd2c9460e3fdc0e Mon Sep 17 00:00:00 2001 From: Romain Beauxis Date: Wed, 23 Oct 2024 08:49:44 -0500 Subject: [PATCH] Backport autocue improvements from 2.2.x (#4184) --- src/core/builtins/builtins_resolvers.ml | 30 +++- src/core/builtins/builtins_source.ml | 120 ++++++++++++--- src/core/clock.ml | 21 ++- src/core/clock.mli | 2 + src/core/operators/cross.ml | 189 ++++++++++++++++++++++-- src/core/stream/frame.ml | 7 +- src/core/stream/generator.ml | 10 ++ src/core/stream/generator.mli | 3 + src/libs/autocue.liq | 30 +++- src/libs/fades.liq | 103 ++++++++++--- tests/streams/crossfade-plot.new.txt | 1 - 11 files changed, 445 insertions(+), 71 deletions(-) diff --git a/src/core/builtins/builtins_resolvers.ml b/src/core/builtins/builtins_resolvers.ml index 5499a047a5..03cb1ae20b 100644 --- a/src/core/builtins/builtins_resolvers.ml +++ b/src/core/builtins/builtins_resolvers.ml @@ -35,6 +35,18 @@ let _ = Lang.getter_t Lang.int_t, Some (Lang.int 1), Some "Resolver's priority." ); + ( "mime_types", + Lang.nullable_t (Lang.list_t Lang.string_t), + Some Lang.null, + Some + "Decode files that match the mime types in this list. Accept any \ + file if `null`." ); + ( "file_extensions", + Lang.nullable_t (Lang.list_t Lang.string_t), + Some Lang.null, + Some + "Decode files that have the file extensions in this list. Accept any \ + file if `null`." ); ("", Lang.string_t, None, Some "Format/resolver's name."); ( "", resolver_t, @@ -47,11 +59,25 @@ let _ = (fun p -> let format = Lang.to_string (Lang.assoc "" 1 p) in let f = Lang.assoc "" 2 p in + let mimes = + Lang.to_valued_option + (fun v -> List.map Lang.to_string (Lang.to_list v)) + (List.assoc "mime_types" p) + in + let extensions = + Lang.to_valued_option + (fun v -> List.map Lang.to_string (Lang.to_list v)) + (List.assoc "file_extensions" p) + in + let log = Log.make ["decoder"; "metadata"] in let priority = Lang.to_int_getter (List.assoc "priority" p) in - let resolver ~metadata ~extension:_ ~mime:_ name = + let resolver ~metadata ~extension ~mime fname = + if + not (Decoder.test_file ~log ~extension ~mime ~mimes ~extensions fname) + then raise Metadata.Invalid; let ret = Lang.apply f - [("metadata", Lang.metadata metadata); ("", Lang.string name)] + [("metadata", Lang.metadata metadata); ("", Lang.string fname)] in let ret = Lang.to_list ret in let ret = List.map Lang.to_product ret in diff --git a/src/core/builtins/builtins_source.ml b/src/core/builtins/builtins_source.ml index 4bd5ad1f18..675d50aa56 100644 --- a/src/core/builtins/builtins_source.ml +++ b/src/core/builtins/builtins_source.ml @@ -178,11 +178,24 @@ let _ = Some "Time ratio. A value of `50` means process data at `50x` real rate, \ when possible." ); + ( "timeout", + Lang.float_t, + Some (Lang.float 1.), + Some + "Stop processing the source if it has not started after the given \ + timeout." ); + ( "sleep_latency", + Lang.float_t, + Some (Lang.float 0.1), + Some + "How much time ahead, in seconds, should we should be before pausing \ + the processing." ); ] Lang.unit_t (fun p -> let module Time = (val Clock.time_implementation () : Liq_time.T) in let open Time in + let started = ref false in let stopped = ref false in let proto = let p = Pipe_output.file_proto (Lang.univ_t ()) in @@ -198,18 +211,44 @@ let _ = p in let proto = ("fallible", Lang.bool true) :: proto in - let p = (("id", Lang.string "source_dumper") :: p) @ proto in - let clock = Clock.create ~id:"source_dumper" ~sync:`Passive () in - let _ = Pipe_output.new_file_output ~clock p in + let p = (("id", Lang.string "source.drop") :: p) @ proto in + let clock = + Clock.create ~id:"source.dump" ~sync:`Passive + ~on_error:(fun exn bt -> + stopped := true; + Utils.log_exception ~log + ~bt:(Printexc.raw_backtrace_to_string bt) + (Printf.sprintf "Error while dropping source: %s" + (Printexc.to_string exn))) + () + in + let s = Pipe_output.new_file_output ~clock p in let ratio = Lang.to_float (List.assoc "ratio" p) in - let latency = Time.of_float (Lazy.force Frame.duration /. ratio) in - Clock.start clock; + let timeout = Time.of_float (Lang.to_float (List.assoc "timeout" p)) in + let sleep_latency = + Time.of_float (Lang.to_float (List.assoc "sleep_latency" p)) + in + Clock.start ~force:true clock; log#info "Start dumping source (ratio: %.02fx)" ratio; - while (not (Atomic.get should_stop)) && not !stopped do - let start_time = Time.time () in - Clock.tick clock; - sleep_until (start_time |+| latency) - done; + let start_time = Time.time () in + let timeout_time = Time.(start_time |+| timeout) in + let target_time () = + Time.( + start_time |+| sleep_latency |+| of_float (Clock.time clock /. ratio)) + in + (try + while (not (Atomic.get should_stop)) && not !stopped do + if not !started then started := s#is_ready; + if (not !started) && Time.(timeout_time |<=| start_time) then ( + log#important "Timeout while waiting for the source to start!"; + stopped := true) + else ( + Clock.tick clock; + let target_time = target_time () in + if Time.(time () |<| (target_time |+| sleep_latency)) then + sleep_until target_time) + done + with Clock.Has_stopped -> ()); log#info "Source dumped."; Clock.stop clock; Lang.unit) @@ -227,14 +266,36 @@ let _ = Some "Time ratio. A value of `50` means process data at `50x` real rate, \ when possible." ); + ( "timeout", + Lang.float_t, + Some (Lang.float 1.), + Some + "Stop processing the source if it has not started after the given \ + timeout." ); + ( "sleep_latency", + Lang.float_t, + Some (Lang.float 0.1), + Some + "How much time ahead, in seconds, should we should be before pausing \ + the processing." ); ] Lang.unit_t (fun p -> let module Time = (val Clock.time_implementation () : Liq_time.T) in let open Time in let s = List.assoc "" p |> Lang.to_source in + let started = ref false in let stopped = ref false in - let clock = Clock.create ~id:"source_dumper" ~sync:`Passive () in + let clock = + Clock.create ~id:"source.dump" ~sync:`Passive + ~on_error:(fun exn bt -> + stopped := true; + Utils.log_exception ~log + ~bt:(Printexc.raw_backtrace_to_string bt) + (Printf.sprintf "Error while dropping source: %s" + (Printexc.to_string exn))) + () + in let _ = new Output.dummy ~clock ~infallible:false @@ -243,14 +304,35 @@ let _ = ~register_telnet:false ~autostart:true (Lang.source s) in let ratio = Lang.to_float (List.assoc "ratio" p) in - let latency = Time.of_float (Lazy.force Frame.duration /. ratio) in - Clock.start clock; + let timeout = Time.of_float (Lang.to_float (List.assoc "timeout" p)) in + let sleep_latency = + Time.of_float (Lang.to_float (List.assoc "sleep_latency" p)) + in + Clock.start ~force:true clock; log#info "Start dropping source (ratio: %.02fx)" ratio; - while (not (Atomic.get should_stop)) && not !stopped do - let start_time = Time.time () in - Clock.tick clock; - sleep_until (start_time |+| latency) - done; - log#info "Source dropped."; + let start_time = Time.time () in + let timeout_time = Time.(start_time |+| timeout) in + let target_time () = + Time.(start_time |+| of_float (Clock.time clock /. ratio)) + in + (try + while (not (Atomic.get should_stop)) && not !stopped do + let start_time = Time.time () in + if not !started then started := s#is_ready; + if (not !started) && Time.(timeout_time |<=| start_time) then ( + log#important "Timeout while waiting for the source to start!"; + stopped := true) + else ( + Clock.tick clock; + let target_time = target_time () in + if Time.(time () |<| (target_time |+| sleep_latency)) then + sleep_until target_time) + done + with Clock.Has_stopped -> ()); + let processing_time = Time.(to_float (time () |-| start_time)) in + let effective_ratio = Clock.time clock /. processing_time in + log#info + "Source dropped. Total processing time: %.02fs, effective ratio: %.02fx" + processing_time effective_ratio; Clock.stop clock; Lang.unit) diff --git a/src/core/clock.ml b/src/core/clock.ml index 79bb50dd7e..36ff5d4393 100644 --- a/src/core/clock.ml +++ b/src/core/clock.ml @@ -94,7 +94,7 @@ let conf_clock_preferred = let conf_clock_sleep_latency = Dtools.Conf.int ~p:(conf_clock#plug "sleep_latency") - ~d:1 + ~d:5 "How much time ahead (in frame duration) we should be until we let the \ streaming loop sleep." ~comments: @@ -316,9 +316,13 @@ let ticks c = | `Stopped _ -> 0 | `Stopping { ticks } | `Started { ticks } -> Atomic.get ticks -let _target_time { time_implementation; t0; frame_duration; ticks } = +let _time { time_implementation; frame_duration; ticks } = let module Time = (val time_implementation : Liq_time.T) in - Time.(t0 |+| (frame_duration |*| of_float (float_of_int (Atomic.get ticks)))) + Time.(frame_duration |*| of_float (float_of_int (Atomic.get ticks))) + +let _target_time ({ time_implementation; t0 } as c) = + let module Time = (val time_implementation : Liq_time.T) in + Time.(t0 |+| _time c) let _set_time { time_implementation; t0; frame_duration; ticks } t = let module Time = (val time_implementation : Liq_time.T) in @@ -464,7 +468,7 @@ and _can_start ?(force = false) clock = `True sync | _ -> `False -and _start ~sync clock = +and _start ?force ~sync clock = Unifier.set clock.id (Lang_string.generate_id (Unifier.deref clock.id)); let id = _id clock in log#important "Starting clock %s with %d source(s) and sync: %s" id @@ -497,14 +501,14 @@ and _start ~sync clock = ticks = Atomic.make 0; } in - Queue.iter clock.sub_clocks (fun c -> start c); + Queue.iter clock.sub_clocks (fun c -> start ?force c); Atomic.set clock.state (`Started x); if sync <> `Passive then _clock_thread ~clock x and start ?force c = let clock = Unifier.deref c in match _can_start ?force clock with - | `True sync -> _start ~sync clock + | `True sync -> _start ?force ~sync clock | `False -> () let create ?(stack = []) ?on_error ?(id = "generic") ?(sub_ids = []) @@ -526,6 +530,11 @@ let create ?(stack = []) ?on_error ?(id = "generic") ?(sub_ids = []) Queue.push clocks c; c +let time c = + let ({ time_implementation } as c) = active_params c in + let module Time = (val time_implementation : Liq_time.T) in + Time.to_float (_time c) + let start_pending () = let c = Queue.flush_elements clocks in let c = List.map (fun c -> (c, Unifier.deref c)) c in diff --git a/src/core/clock.mli b/src/core/clock.mli index e3f29827b1..57e8185cbc 100644 --- a/src/core/clock.mli +++ b/src/core/clock.mli @@ -21,6 +21,7 @@ *****************************************************************************) exception Invalid_state +exception Has_stopped type t type active_source = < reset : unit ; output : unit > @@ -96,6 +97,7 @@ val started : t -> bool val stop : t -> unit val set_stack : t -> Liquidsoap_lang.Pos.t list -> unit val self_sync : t -> bool +val time : t -> float val unify : pos:Liquidsoap_lang.Pos.Option.t -> t -> t -> unit val create_sub_clock : id:string -> t -> t val attach : t -> source -> unit diff --git a/src/core/operators/cross.ml b/src/core/operators/cross.ml index 21344a0fa1..6c7107631b 100644 --- a/src/core/operators/cross.ml +++ b/src/core/operators/cross.ml @@ -23,6 +23,16 @@ open Mm open Source +let conf = + Dtools.Conf.void ~p:(Configure.conf#plug "crossfade") "Crossfade settings" + +let conf_assume_autocue = + Dtools.Conf.bool + ~p:(conf#plug "assume_autocue") + ~d:false + "Assume autocue when all 4 cue in/out and fade in/out metadata override \ + are present." + class consumer ~clock buffer = object (self) inherit Source.source ~clock ~name:"cross.buffer" () @@ -42,7 +52,8 @@ class consumer ~clock buffer = * [cross_length] is in ticks (like #remaining estimations) and must be at least one frame. *) class cross val_source ~end_duration_getter ~override_end_duration ~override_duration ~start_duration_getter ~override_start_duration - ~override_max_start_duration ~persist_override ~rms_width transition = + ~override_max_start_duration ~persist_override ~rms_width ~assume_autocue + transition = let s = Lang.to_source val_source in let original_end_duration_getter = end_duration_getter in let original_start_duration_getter = start_duration_getter in @@ -159,6 +170,34 @@ class cross val_source ~end_duration_getter ~override_end_duration val mutable rmsi_after = 0 val mutable after_metadata = None + method private autocue_enabled mode = + let metadata, set_metadata = + match mode with + | `Before -> (before_metadata, fun m -> before_metadata <- Some m) + | `After -> (after_metadata, fun m -> after_metadata <- Some m) + in + match metadata with + | Some h -> ( + let has_metadata = + List.for_all + (fun v -> Frame.Metadata.mem v h) + ["liq_cue_in"; "liq_cue_out"; "liq_fade_in"; "liq_fade_out"] + in + let has_marker = Frame.Metadata.mem "liq_autocue" h in + match (has_marker, has_metadata, assume_autocue) with + | true, true, _ -> true + | true, false, _ -> + self#log#critical + "`\"liq_autocue\"` metadata is present but some of the cue \ + in/out and fade in/out metadata are missing!"; + false + | false, true, true -> + self#log#info "Assuming autocue"; + set_metadata (Frame.Metadata.add "liq_autocue" "assumed" h); + true + | _ -> false) + | None -> false + method private reset_analysis = gen_before <- Generator.create self#content_type; gen_after <- Generator.create self#content_type; @@ -351,8 +390,78 @@ class cross val_source ~end_duration_getter ~override_end_duration in f ~is_first:true () + method private append_before_metadata lbl value = + before_metadata <- + Some + (Frame.Metadata.add lbl value + (Option.value ~default:Frame.Metadata.empty before_metadata)) + + method private append_after_metadata lbl value = + after_metadata <- + Some + (Frame.Metadata.add lbl value + (Option.value ~default:Frame.Metadata.empty after_metadata)) + + method private autocue_adjustements ~before_autocue ~after_autocue + ~buffered_before ~buffered_after ~buffered () = + let before_metadata = + Option.value ~default:Frame.Metadata.empty before_metadata + in + let extra_cross_duration = buffered_before - buffered_after in + if after_autocue then ( + if before_autocue && 0 < extra_cross_duration then ( + let new_cross_duration = buffered_before - extra_cross_duration in + Generator.keep gen_before new_cross_duration; + let new_cross_duration = Frame.seconds_of_main new_cross_duration in + let extra_cross_duration = + Frame.seconds_of_main extra_cross_duration + in + self#log#info + "Shortening ending track by %.2f to match the starting track's \ + buffer." + extra_cross_duration; + let fade_out = + float_of_string (Frame.Metadata.find "liq_fade_out" before_metadata) + in + let fade_out = min new_cross_duration fade_out in + let fade_out_delay = max (new_cross_duration -. fade_out) 0. in + self#append_before_metadata "liq_fade_out" (string_of_float fade_out); + self#append_before_metadata "liq_fade_out_delay" + (string_of_float fade_out_delay)); + (try + let cross_duration = Frame.seconds_of_main buffered in + let cue_out = + float_of_string (Frame.Metadata.find "liq_cue_out" before_metadata) + in + let start_next = + float_of_string + (Frame.Metadata.find "liq_cross_start_next" before_metadata) + in + if cue_out -. start_next < cross_duration then ( + self#log#info "Adding fade-in delay to match start next"; + self#append_after_metadata "liq_fade_in_delay" + (string_of_float (cross_duration -. cue_out +. start_next))) + with _ -> ()); + let fade_out_delay = + try + float_of_string + (Frame.Metadata.find "liq_fade_out_delay" before_metadata) + with _ -> 0. + in + if 0. < fade_out_delay then ( + self#log#info + "Adding %.2f fade-in delay to match the ending track's buffer" + fade_out_delay; + self#append_after_metadata "liq_fade_in_delay" + (string_of_float fade_out_delay))) + (* Sum up analysis and build the transition *) method private create_after = + let before_autocue = self#autocue_enabled `Before in + let after_autocue = self#autocue_enabled `After in + let buffered_before = Generator.length gen_before in + let buffered_after = Generator.length gen_after in + let buffered = min buffered_before buffered_after in let db_after = Audio.dB_of_lin (sqrt (rms_after /. float rmsi_after /. float self#audio_channels)) @@ -361,20 +470,51 @@ class cross val_source ~end_duration_getter ~override_end_duration Audio.dB_of_lin (sqrt (rms_before /. float rmsi_before /. float self#audio_channels)) in - let buffered_before = Generator.length gen_before in - let buffered_after = Generator.length gen_after in + self#autocue_adjustements ~before_autocue ~after_autocue ~buffered_before + ~buffered_after ~buffered (); let compound = let metadata = function None -> Frame.Metadata.empty | Some m -> m in let before_metadata = metadata before_metadata in let after_metadata = metadata after_metadata in - let before = new consumer ~clock:source#clock gen_before in - Typing.(before#frame_type <: self#frame_type); - let before = new Insert_metadata.replay before_metadata before in + let before_head = + if (not after_autocue) && buffered < buffered_before then ( + let head = + Generator.slice gen_before (buffered_before - buffered) + in + let head_gen = + Generator.create ~content:head (Generator.content_type gen_before) + in + let s = new consumer ~clock:source#clock head_gen in + s#set_id (self#id ^ "_before_head"); + Typing.(s#frame_type <: self#frame_type); + Some s) + else None + in + let before = + new Insert_metadata.replay + before_metadata + (new consumer ~clock:source#clock gen_before) + in Typing.(before#frame_type <: self#frame_type); - before#set_id (self#id ^ "_before"); - let after = new consumer ~clock:source#clock gen_after in - Typing.(after#frame_type <: self#frame_type); - let after = new Insert_metadata.replay after_metadata after in + let after_tail = + if (not after_autocue) && buffered < buffered_after then ( + let head = Generator.slice gen_after buffered in + let head_gen = + Generator.create ~content:head (Generator.content_type gen_after) + in + let tail_gen = gen_after in + gen_after <- head_gen; + let s = new consumer ~clock:source#clock tail_gen in + Typing.(s#frame_type <: self#frame_type); + s#set_id (self#id ^ "_after_tail"); + Some s) + else None + in + let after = + new Insert_metadata.replay + after_metadata + (new consumer ~clock:source#clock gen_after) + in Typing.(after#frame_type <: self#frame_type); before#set_id (self#id ^ "_before"); after#set_id (self#id ^ "_after"); @@ -404,6 +544,19 @@ class cross val_source ~end_duration_getter ~override_end_duration Lang.to_source (Lang.apply transition params) in Typing.(compound#frame_type <: self#frame_type); + let compound = + match (before_head, after_tail) with + | None, None -> (compound :> Source.source) + | Some s, None -> + (new Sequence.sequence ~merge:true [s; compound] + :> Source.source) + | None, Some s -> + (new Sequence.sequence ~single_track:false [compound; s] + :> Source.source) + | Some _, Some _ -> assert false + in + Clock.unify ~pos:self#pos compound#clock s#clock; + Typing.(compound#frame_type <: self#frame_type); compound in self#prepare_source compound; @@ -466,6 +619,13 @@ let _ = Some "Duration (in seconds) of buffered data from the end and start of \ each track that is used to compute the transition between tracks." ); + ( "assume_autocue", + Lang.nullable_t Lang.bool_t, + Some Lang.null, + Some + "Assume that a track has autocue enabled when all four cue in/out \ + and fade in/out override metadata are present. Defaults to \ + `settings.crossfade.assume_autocue` when `null`." ); ( "override_start_duration", Lang.string_t, Some (Lang.string "liq_cross_start_duration"), @@ -529,6 +689,12 @@ let _ = depending on the relative power of the signal before and after the end \ of track." (fun p -> + let assume_autocue = + Lang.to_valued_option Lang.to_bool (List.assoc "assume_autocue" p) + in + let assume_autocue = + Option.value ~default:conf_assume_autocue#get assume_autocue + in let start_duration_getter = Lang.to_valued_option Lang.to_float_getter (List.assoc "start_duration" p) @@ -563,4 +729,5 @@ let _ = new cross source transition ~start_duration_getter ~end_duration_getter ~rms_width ~override_start_duration ~override_max_start_duration - ~override_end_duration ~override_duration ~persist_override) + ~override_end_duration ~override_duration ~persist_override + ~assume_autocue) diff --git a/src/core/stream/frame.ml b/src/core/stream/frame.ml index 7c98a4df5b..c4b40fc5ed 100644 --- a/src/core/stream/frame.ml +++ b/src/core/stream/frame.ml @@ -47,9 +47,12 @@ module S = Set.Make (struct let compare = Stdlib.compare end) +let filter_implicit_fields (lbl, _) = + if List.mem lbl [Fields.metadata; Fields.track_marks] then None else Some lbl + let assert_compatible c c' = - let f = List.map fst (Fields.bindings c) in - let f' = List.map fst (Fields.bindings c') in + let f = List.filter_map filter_implicit_fields (Fields.bindings c) in + let f' = List.filter_map filter_implicit_fields (Fields.bindings c') in if not S.(equal (of_list f) (of_list f')) then failwith (Printf.sprintf diff --git a/src/core/stream/generator.ml b/src/core/stream/generator.ml index 0fef15ae00..b903f28e49 100644 --- a/src/core/stream/generator.ml +++ b/src/core/stream/generator.ml @@ -112,6 +112,16 @@ let _truncate ?(allow_desync = false) gen len = let truncate gen = Mutex_utils.mutexify gen.lock (_truncate gen) +let _keep gen len = + Atomic.set gen.content + (Frame_base.Fields.map + (fun content -> + assert (len <= Content.length content); + Content.sub content 0 len) + (Atomic.get gen.content)) + +let keep gen = Mutex_utils.mutexify gen.lock (_keep gen) + let _slice gen len = let content = Atomic.get gen.content in let len = diff --git a/src/core/stream/generator.mli b/src/core/stream/generator.mli index 4b305d2675..5f12e47a0e 100644 --- a/src/core/stream/generator.mli +++ b/src/core/stream/generator.mli @@ -70,6 +70,9 @@ val remaining : t -> int (* Drop given length of content at the beginning of the generator. *) val truncate : t -> int -> unit +(* Keep only the given length of data from the beginning of the generator. *) +val keep : t -> int -> unit + (* Return at most the given len of data from the start of the generator and truncate the generator of that data. *) val slice : t -> int -> Frame.t diff --git a/src/libs/autocue.liq b/src/libs/autocue.liq index d6763ef501..78eac15f4e 100644 --- a/src/libs/autocue.liq +++ b/src/libs/autocue.liq @@ -155,7 +155,7 @@ let settings.autocue.internal.ratio = settings.make( description= "Maximum real time ratio to control speed of LUFS data analysis", - 50. + 223. ) let settings.autocue.internal.timeout = @@ -217,7 +217,10 @@ def autocue.internal.ebur128(~duration, ~ratio=50., ~timeout=10., filename) = let {audio = a} = source.tracks(s) a = ffmpeg.filter.audio.input(graph, a) let ([a], _) = ffmpeg.filter.ebur128(metadata=true, graph, a) - a = ffmpeg.filter.audio.output(graph, a) + + # ebur filter seems to generate invalid PTS. + a = ffmpeg.filter.asetpts(expr="N/SR/TB", graph, a) + a = ffmpeg.filter.audio.output(id="filter_output", graph, a) source({audio=a, metadata=track.metadata(a)}) end @@ -805,7 +808,7 @@ let file.autocue = () # Return the file's autocue values as metadata suitable for metadata override. # @category Source / Audio processing -def file.autocue.metadata(~metadata, uri) = +def file.autocue.metadata(~request_metadata, uri) = preferred_implementation = settings.autocue.preferred() implementations = settings.autocue.implementations() @@ -856,7 +859,7 @@ def file.autocue.metadata(~metadata, uri) = try autocue = implementation( - request_metadata=metadata, + request_metadata=request_metadata, file_metadata=request.metadata(r), request.filename(r) ) @@ -987,7 +990,7 @@ def enable_autocue_metadata() = ) [] else - autocue_metadata = file.autocue.metadata(metadata=metadata, fname) + autocue_metadata = file.autocue.metadata(request_metadata=metadata, fname) all_amplify = [...settings.autocue.amplify_aliases(), "liq_amplify"] @@ -1062,15 +1065,28 @@ def enable_autocue_metadata() = autocue_metadata end end + +%ifdef settings.decoder.mime_types.ffmpeg + mime_types = settings.decoder.mime_types.ffmpeg() + file_extensions = settings.decoder.file_extensions.ffmpeg() +%else + mime_types = null() + file_extensions = null() +%endif + decoder.metadata.add( - priority=settings.autocue.metadata.priority, "autocue", autocue_metadata + mime_types=mime_types, + file_extensions=file_extensions, + priority=settings.autocue.metadata.priority, + "autocue", + autocue_metadata ) end # Define autocue protocol # @flag hidden def protocol.autocue(~rlog=_, ~maxtime=_, arg) = - cue_metadata = file.autocue.metadata(metadata=[], arg) + cue_metadata = file.autocue.metadata(request_metadata=[], arg) if cue_metadata != [] diff --git a/src/libs/fades.liq b/src/libs/fades.liq index f305ce0170..520bc6c000 100644 --- a/src/libs/fades.liq +++ b/src/libs/fades.liq @@ -1,5 +1,62 @@ fade = () +let settings.fade = + settings.make.void( + "Settings for the fade in/out operators" + ) + +let settings.fade.in = + settings.make.void( + "Settings for fade.in operators" + ) + +let settings.fade.in.duration = + settings.make( + description= + "Default fade.in duration", + 3. + ) + +let settings.fade.in.type = + settings.make( + description= + "Default fade.in type", + "lin" + ) + +let settings.fade.in.curve = + settings.make( + description= + "Default fade.in curve", + 10. + ) + +let settings.fade.out = + settings.make.void( + "Settings for fade.out operators" + ) + +let settings.fade.out.duration = + settings.make( + description= + "Default fade.out duration", + 3. + ) + +let settings.fade.out.type = + settings.make( + description= + "Default fade.out type", + "exp" + ) + +let settings.fade.out.curve = + settings.make( + description= + "Default fade.out curve", + 3. + ) + # Make a fade function based on a source's clock. # @category Source / Fade # @param ~curve Fade curve for `"log"` and `"exp"` shapes. If `null`, depends on the type of fade. \ @@ -31,7 +88,7 @@ def mkfade( (1. + sin((x - 0.5) * pi)) / 2. end - exp_curve = curve ?? 2. + exp_curve = curve ?? 3. m = exp(exp_curve - 1.) - exp(-1.) def exp_shape(x) = @@ -104,9 +161,9 @@ end # Fade the end of tracks. # @category Source / Fade # @param ~id Force the value of the source ID. -# @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. -# @param ~delay Initial delay before starting fade. -# @param ~curve Fade curve. Default if `null`. +# @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. Defaults to `settings.fade.out.curve` if `null`. +# @param ~delay Initial delay before starting fade. Defaults to `settings.fade.out.delay` if `null`. +# @param ~curve Fade curve. Defaults to `settings.fade.out.curve` if `null`. # @param ~override_duration Metadata field which, if present and containing a float, overrides the 'duration' parameter for the current track. # @param ~override_type Metadata field which, if present and correct, overrides the 'type' parameter for the current track. # @param ~override_curve Metadata field which, if presents and correct, overrides the `curve` parameter for the current track. Use `"default"` \ @@ -115,10 +172,10 @@ end # @param ~persist_overrides Keep duration and type overrides on track change. # @param ~track_sensitive Be track sensitive (if `false` we only fade ou once at the beginning of the track). # @param ~initial_metadata Initial metadata. -# @param ~type Fader shape (lin|sin|log|exp): linear, sinusoidal, logarithmic or exponential. +# @param ~type Fader shape. One of: "lin"", "sin", "log" or "exp". Defaults to `settings.fade.out.type` if `null`. def fade.out( ~id="fade.out", - ~duration=3., + ~duration=null(), ~delay=0., ~curve=null(), ~override_duration="liq_fade_out", @@ -128,7 +185,7 @@ def fade.out( ~persist_overrides=false, ~track_sensitive=false, ~initial_metadata=[], - ~type="lin", + ~type=null(), s ) = def log(x) = @@ -136,12 +193,12 @@ def fade.out( end fn = ref(fun () -> 1.) - original_type = type - type = ref(type) - original_curve = curve - curve = ref(curve) - original_duration = duration - duration = ref(duration) + original_type = type ?? settings.fade.out.type() + type = ref(original_type) + original_curve = (curve ?? settings.fade.out.curve() : float?) + curve = ref(original_curve) + original_duration = duration ?? settings.fade.out.duration() + duration = ref(original_duration) original_delay = delay delay = ref(original_delay) start_time = ref(-1.) @@ -475,9 +532,9 @@ end # Fade the beginning of tracks. # @category Source / Fade # @param ~id Force the value of the source ID. -# @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. +# @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. Defaults to `settings.fade.in.duration` if `null`. # @param ~delay Initial delay before starting fade. -# @param ~curve Fade curve. Default if `null`. +# @param ~curve Fade curve. Defaults to `settings.fade.in.curve` if `null`. # @param ~override_duration Metadata field which, if present and containing a float, overrides the 'duration' parameter for the current track. # @param ~override_type Metadata field which, if present and correct, overrides the 'type' parameter for the current track. # @param ~override_curve Metadata field which, if presents and correct, overrides the `curve` parameter for the current track. Use `"default"` \ @@ -486,10 +543,10 @@ end # @param ~persist_overrides Keep duration and type overrides on track change. # @param ~track_sensitive Be track sensitive (if `false` we only fade in once at the beginning of the track). # @param ~initial_metadata Initial metadata. -# @param ~type Fader shape (lin|sin|log|exp): linear, sinusoidal, logarithmic or exponential. +# @param ~type Fader shape. One of: "lin"", "sin", "log" or "exp". Defaults to `settings.fade.in.type` if `null`. def fade.in( ~id="fade.in", - ~duration=3., + ~duration=null(), ~delay=0., ~curve=null(), ~override_duration="liq_fade_in", @@ -499,7 +556,7 @@ def fade.in( ~persist_overrides=false, ~track_sensitive=false, ~initial_metadata=[], - ~type="lin", + ~type=null(), s ) = def log(x) = @@ -507,13 +564,13 @@ def fade.in( end fn = ref(fun () -> 0.) - original_duration = duration - duration = ref(duration) + original_duration = duration ?? settings.fade.in.duration() + duration = ref(original_duration) original_delay = delay delay = ref(original_delay) - original_type = type - type = ref(type) - original_curve = curve + original_type = type ?? settings.fade.in.type() + type = ref(original_type) + original_curve = curve ?? settings.fade.in.curve() curve = ref(curve) last_metadata = ref(initial_metadata) diff --git a/tests/streams/crossfade-plot.new.txt b/tests/streams/crossfade-plot.new.txt index 1f4eb392ab..117252ba73 100644 --- a/tests/streams/crossfade-plot.new.txt +++ b/tests/streams/crossfade-plot.new.txt @@ -248,4 +248,3 @@ 4.94 0.70746027805 4.96 0.708973694512 4.98 0.707908080993 -5.0 0.70573157718