diff --git a/src/framework/mlt_tractor.c b/src/framework/mlt_tractor.c index 0487b31bf..c415e286e 100644 --- a/src/framework/mlt_tractor.c +++ b/src/framework/mlt_tractor.c @@ -440,6 +440,10 @@ static int producer_get_audio(mlt_frame self, "producer_consumer_fps", mlt_properties_get(properties, "producer_consumer_fps")); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); + + // Propagate audio-level metadata onto the tractor output frame. + mlt_properties_copy(properties, frame_properties, "meta."); + mlt_frame_set_audio(self, *buffer, *format, diff --git a/src/modules/core/transition_mix.c b/src/modules/core/transition_mix.c index e976a238b..98a13fc62 100644 --- a/src/modules/core/transition_mix.c +++ b/src/modules/core/transition_mix.c @@ -147,6 +147,7 @@ static int transition_get_audio(mlt_frame frame_a, // Get the properties of the b frame mlt_properties b_props = MLT_FRAME_PROPERTIES(frame_b); + mlt_properties a_props = MLT_FRAME_PROPERTIES(frame_a); transition_mix self = transition->child; float *buffer_b, *buffer_a; @@ -160,6 +161,9 @@ static int transition_get_audio(mlt_frame frame_a, mlt_frame_get_audio(frame_b, (void **) &buffer_b, format, &frequency_b, &channels_b, &samples_b); mlt_frame_get_audio(frame_a, (void **) &buffer_a, format, &frequency_a, &channels_a, &samples_a); + // Merge b-frame level metadata onto a-frame after both callbacks run. + mlt_properties_copy(a_props, b_props, "meta."); + // Prevent dividing by zero. if (!channels_a || !channels_b || !buffer_a || !buffer_b) return 1; diff --git a/src/modules/normalize/filter_audiolevel.c b/src/modules/normalize/filter_audiolevel.c index cec9c0f5e..d9413902a 100644 --- a/src/modules/normalize/filter_audiolevel.c +++ b/src/modules/normalize/filter_audiolevel.c @@ -2,7 +2,7 @@ * filter_audiolevel.c -- get the audio level of each channel * Copyright (C) 2002 Steve Harris * Copyright (C) 2010 Marco Gittler - * Copyright (C) 2012-2023 Dan Dennedy + * Copyright (C) 2012-2026 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,8 +24,42 @@ #include #include +#include #include +static const char *kDefaultPrefix = "meta.media.audio_level."; + +static void append_int(char *out, size_t out_size, size_t *offset, int value) +{ + if (!out || !out_size || !offset || *offset >= out_size) + return; + + const int n = snprintf(out + *offset, out_size - *offset, "%d", value); + if (n < 0) + return; + *offset += (size_t) n; + if (*offset >= out_size) + out[out_size - 1] = '\0'; +} + +static void make_audio_level_key(const char *prefix, int channel, char *key, size_t key_size) +{ + if (!key || key_size == 0) + return; + + if (!prefix || !prefix[0]) + prefix = kDefaultPrefix; + + const int n = snprintf(key, key_size, "%s", prefix); + size_t offset = n > 0 ? (size_t) n : 0; + if (offset >= key_size) + offset = key_size - 1; + key[offset] = '\0'; + + append_int(key, key_size, &offset, channel); + key[offset < key_size ? offset : key_size - 1] = '\0'; +} + #define AMPTODBFS(n) (log10(n) * 20.0) //---------------------------------------------------------------------------- @@ -67,6 +101,7 @@ static int filter_get_audio(mlt_frame frame, int iec_scale = mlt_properties_get_int(filter_props, "iec_scale"); int dbPeak = mlt_properties_get_int(filter_props, "dbpeak"); + const char *prefix = mlt_properties_get(filter_props, "prefix"); *format = mlt_audio_s16; int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error || !buffer || !buffer[0]) @@ -76,7 +111,7 @@ static int filter_get_audio(mlt_frame frame, int num_samples = *samples > 200 ? 200 : *samples; int num_oversample = 0; int c, s; - char key[50]; + char key[128]; int16_t *pcm = (int16_t *) *buffer; for (c = 0; c < *channels; c++) { @@ -119,7 +154,7 @@ static int filter_get_audio(mlt_frame frame, if (iec_scale) level = IEC_Scale(AMPTODBFS(level)); } - sprintf(key, "meta.media.audio_level.%d", c); + make_audio_level_key(prefix, c, key, sizeof(key)); mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), key, level); sprintf(key, "_audio_level.%d", c); mlt_properties_set_double(filter_props, key, level); @@ -152,6 +187,7 @@ mlt_filter filter_audiolevel_init(mlt_profile profile, if (filter) { filter->process = filter_process; mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "iec_scale", 1); + mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "prefix", kDefaultPrefix); } return filter; } diff --git a/src/modules/normalize/filter_audiolevel.yml b/src/modules/normalize/filter_audiolevel.yml index 17f526da8..1a3f880a0 100644 --- a/src/modules/normalize/filter_audiolevel.yml +++ b/src/modules/normalize/filter_audiolevel.yml @@ -2,7 +2,7 @@ schema_version: 7.2 type: filter identifier: audiolevel title: Audio Levels -version: 3 +version: 4 copyright: Meltytech, LLC, Marco Gittler, and Steve Harris creator: Dan Dennedy contributor: @@ -17,11 +17,20 @@ notes: > troughs of short duration like a VU meter. Applications can also get this data on the frame as meta.media.audio_level. where is the channel number starting with 0. + The prefix is configurable using the prefix property. tags: - Audio audio_formats: - s16 parameters: + - identifier: prefix + title: Frame Property Prefix + description: > + Prefix for frame audio level keys. The filter appends the channel index + to this prefix. + type: string + default: meta.media.audio_level. + - identifier: iec_scale title: Use IEC 60268-18 Scale type: boolean