Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Framework
longer have that function called. If you really need that (never heard of
someone who does), clear the frame's "_convert_image_callbacks" property
and use `mlt_frame_append_convert_image()` instead.
- `fx_cut` (Adjustment Clip) now applies its filters only to tracks
below it. Higher tracks composite on top of the filtered result.
New image-stack helpers used by tractor stacking and this path:
- `mlt_frame_prepend_image_from_service()`
- `mlt_frame_push_image_with_fx_cut()`

Modules
- Image converters (`movit.convert`, `avcolor_space`, `imageconvert`) are
Expand Down
2 changes: 2 additions & 0 deletions src/framework/mlt.vers
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,6 @@ MLT_7.40.0 {
MLT_7.42.0 {
global:
mlt_audio_format_id;
mlt_frame_prepend_image_from_service;
mlt_frame_push_image_with_fx_cut;
} MLT_7.40.0;
181 changes: 181 additions & 0 deletions src/framework/mlt_frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,187 @@ void mlt_frame_copy_convert_image(mlt_frame dst, mlt_frame src)
NULL);
}

/** Copy consumer scaling hints from \p src onto \p dst. */
static void copy_consumer_image_hints(mlt_frame dst, mlt_frame src)
{
mlt_properties dst_properties = MLT_FRAME_PROPERTIES(dst);
mlt_properties src_properties = MLT_FRAME_PROPERTIES(src);

mlt_properties_set_int(dst_properties,
"resize_alpha",
mlt_properties_get_int(src_properties, "resize_alpha"));
mlt_properties_set_int(dst_properties,
"distort",
mlt_properties_get_int(src_properties, "distort"));
mlt_properties_copy(dst_properties, src_properties, "consumer.");
// WebVfx uses this to setup a consumer-stopping event handler.
mlt_properties_set_data(dst_properties,
"consumer",
mlt_properties_get_data(src_properties, "consumer", NULL),
0,
NULL,
NULL);
}

/** Install \p data on \p dst without taking ownership.
*
* Skips the set when \p dst already holds \p data: mlt_property_set_data
* nulls the destructor if the pointer is unchanged, which would leak the
* original owner's buffer (fx_cut wrap shares onto the fx frame and back).
*/
static void share_data(mlt_properties dst, const char *name, void *data, int size)
{
void *current = mlt_properties_get_data(dst, name, NULL);
if (data != current)
mlt_properties_set_data(dst, name, data, size, NULL, NULL);
}

/** Copy image properties, alpha, converters, and Movit state from \p src to \p dst.
*
* Does not take ownership of the image or alpha buffers.
*/
static void copy_image_state(mlt_frame dst, mlt_frame src)
{
mlt_properties dst_properties = MLT_FRAME_PROPERTIES(dst);
mlt_properties src_properties = MLT_FRAME_PROPERTIES(src);
int size = 0;
uint8_t *data;

mlt_properties_set_int(dst_properties, "width", mlt_properties_get_int(src_properties, "width"));
mlt_properties_set_int(dst_properties,
"height",
mlt_properties_get_int(src_properties, "height"));
mlt_properties_set_int(dst_properties,
"format",
mlt_properties_get_int(src_properties, "format"));
mlt_properties_set_double(dst_properties, "aspect_ratio", mlt_frame_get_aspect_ratio(src));
mlt_properties_pass_list(
dst_properties,
src_properties,
"progressive,distort,colorspace,full_range,force_full_luma,top_field_first,color_trc");

share_data(dst_properties,
"movit.convert.fence",
mlt_properties_get_data(src_properties, "movit.convert.fence", NULL),
0);
share_data(dst_properties,
"movit.convert.texture",
mlt_properties_get_data(src_properties, "movit.convert.texture", NULL),
0);
mlt_properties_set_int(dst_properties,
"movit.convert.use_texture",
mlt_properties_get_int(src_properties, "movit.convert.use_texture"));
int i;
for (i = 0; i < mlt_properties_count(src_properties); i++) {
char *name = mlt_properties_get_name(src_properties, i);
if (name && !strncmp(name, "_movit ", 7))
share_data(dst_properties, name, mlt_properties_get_data_at(src_properties, i, NULL), 0);
}

data = mlt_frame_get_alpha_size(src, &size);
share_data(dst_properties, "alpha", data, size);
dst->convert_audio = src->convert_audio;
mlt_frame_copy_convert_image(dst, src);
}

/** Share \p image and \p src's image state onto \p dst without taking ownership.
*
* \p image is the pointer returned by mlt_frame_get_image(), which need not be
* stored in src's "image" property. Both frames must remain alive while \p dst's
* image is used (typically both are stored on the tractor output frame).
*/
static void share_image(mlt_frame dst, mlt_frame src, uint8_t *image)
{
share_data(MLT_FRAME_PROPERTIES(dst), "image", image, 0);
copy_image_state(dst, src);
}

/** Get an image from a source frame previously installed by
* mlt_frame_prepend_image_from_service().
*
* Copies consumer scaling onto the source, fetches its image, then shares that
* buffer onto \p self without taking ownership, along with format, alpha,
* converters, and Movit state.
*
* \private \memberof mlt_frame_s
* \return true if the stacked source frame is missing
*/
static int get_image_from_service(mlt_frame self,
uint8_t **buffer,
mlt_image_format *format,
int *width,
int *height,
int writable)
{
mlt_frame frame = mlt_frame_pop_service(self);
if (!frame)
return 1;

copy_consumer_image_hints(frame, self);
mlt_frame_get_image(frame, buffer, format, width, height, writable);
share_image(self, frame, (buffer && *buffer) ? *buffer : NULL);
return 0;
}

/** Install \p source as the image under this frame's existing get_image callbacks.
*
* Prepends so filters already on \p self run first and read \p source. On an
* empty stack this is equivalent to pushing the source then the callback.
* Used by tractor track stacking and the tractor output frame.
*
* \public \memberof mlt_frame_s
* \return true if error
*/
int mlt_frame_prepend_image_from_service(mlt_frame self, mlt_frame source)
{
int error = mlt_deque_push_front(self->stack_image, get_image_from_service);
if (!error)
error = mlt_deque_push_front(self->stack_image, source);
return error;
}

/** Route \p self through \p fx's filter chain without compositing the dummy.
*
* Pops the fx_cut frame, feeds this frame's image through that frame's filters
* via mlt_frame_prepend_image_from_service(), then shares the filtered result back.
*
* \private \memberof mlt_frame_s
*/
static int get_image_with_fx_cut(mlt_frame a_frame,
uint8_t **image,
mlt_image_format *format,
int *width,
int *height,
int writable)
{
mlt_frame fx_frame = mlt_frame_pop_service(a_frame);
if (!fx_frame)
return mlt_frame_get_image(a_frame, image, format, width, height, writable);

copy_consumer_image_hints(fx_frame, a_frame);
mlt_frame_copy_convert_image(fx_frame, a_frame);

mlt_frame_prepend_image_from_service(fx_frame, a_frame);

int error = mlt_frame_get_image(fx_frame, image, format, width, height, writable);
if (!error)
share_image(a_frame, fx_frame, (image && *image) ? *image : NULL);
return error;
}

/** Push an fx_cut wrap so \p self is filtered through \p fx on get_image.
*
* \public \memberof mlt_frame_s
* \return true if error
*/
int mlt_frame_push_image_with_fx_cut(mlt_frame self, mlt_frame fx)
{
int error = mlt_frame_push_service(self, fx);
if (!error)
error = mlt_frame_push_get_image(self, get_image_with_fx_cut);
return error;
}

/***** convenience functions *****/

void mlt_frame_write_ppm(mlt_frame frame)
Expand Down
2 changes: 2 additions & 0 deletions src/framework/mlt_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ MLT_EXPORT int mlt_frame_push_service_int(mlt_frame self, int that);
MLT_EXPORT int mlt_frame_pop_service_int(mlt_frame self);
MLT_EXPORT int mlt_frame_push_audio(mlt_frame self, void *that);
MLT_EXPORT void *mlt_frame_pop_audio(mlt_frame self);
MLT_EXPORT int mlt_frame_prepend_image_from_service(mlt_frame self, mlt_frame source);
MLT_EXPORT int mlt_frame_push_image_with_fx_cut(mlt_frame self, mlt_frame fx);
MLT_EXPORT mlt_deque mlt_frame_service_stack(mlt_frame self);
MLT_EXPORT mlt_producer mlt_frame_get_original_producer(mlt_frame self);
MLT_EXPORT void mlt_frame_close(mlt_frame self);
Expand Down
3 changes: 2 additions & 1 deletion src/framework/mlt_playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* \brief playlist service class
* \see mlt_playlist_s
*
* Copyright (C) 2003-2022 Meltytech, LLC
* Copyright (C) 2003-2026 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -60,6 +60,7 @@ typedef struct playlist_entry_s playlist_entry;
* automatically close producers as they are finished being used to free resources.
* \properties \em meta.fx_cut Set true on a producer to indicate that it is a "fx_cut,"
* which is a way to add filters as a playlist entry - useful only in a multitrack. See FxCut in the docs.
* Filters apply to the composite of lower-index tracks; higher tracks composite on top.
Comment thread
ddennedy marked this conversation as resolved.
* \properties \em mix_in
* \properties \em mix_out
* \properties \em hide Set to 1 to hide the video (make it an audio-only track),
Expand Down
100 changes: 14 additions & 86 deletions src/framework/mlt_tractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -345,84 +345,6 @@ mlt_producer mlt_tractor_get_track(mlt_tractor self, int index)
return mlt_multitrack_track(mlt_tractor_multitrack(self), index);
}

static int producer_get_image(mlt_frame self,
uint8_t **buffer,
mlt_image_format *format,
int *width,
int *height,
int writable)
{
uint8_t *data = NULL;
int size = 0;
mlt_properties properties = MLT_FRAME_PROPERTIES(self);
mlt_frame frame = mlt_frame_pop_service(self);
mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame);

mlt_properties_set_int(frame_properties,
"resize_alpha",
mlt_properties_get_int(properties, "resize_alpha"));
mlt_properties_set_int(frame_properties,
"distort",
mlt_properties_get_int(properties, "distort"));
mlt_properties_copy(frame_properties, properties, "consumer.");
// WebVfx uses this to setup a consumer-stopping event handler.
mlt_properties_set_data(frame_properties,
"consumer",
mlt_properties_get_data(properties, "consumer", NULL),
0,
NULL,
NULL);

mlt_frame_get_image(frame, buffer, format, width, height, writable);
mlt_frame_set_image(self, *buffer, 0, NULL);

mlt_properties_set_int(properties, "width", *width);
mlt_properties_set_int(properties, "height", *height);
mlt_properties_set_int(properties, "format", *format);
mlt_properties_set_double(properties, "aspect_ratio", mlt_frame_get_aspect_ratio(frame));
// Pass all required frame properties
mlt_properties_pass_list(
properties,
frame_properties,
"progressive,distort,colorspace,full_range,force_full_luma,top_field_first,color_trc");

mlt_properties_set_data(properties,
"movit.convert.fence",
mlt_properties_get_data(frame_properties, "movit.convert.fence", NULL),
0,
NULL,
NULL);
mlt_properties_set_data(properties,
"movit.convert.texture",
mlt_properties_get_data(frame_properties, "movit.convert.texture", NULL),
0,
NULL,
NULL);
mlt_properties_set_int(properties,
"movit.convert.use_texture",
mlt_properties_get_int(frame_properties, "movit.convert.use_texture"));
int i;
for (i = 0; i < mlt_properties_count(frame_properties); i++) {
char *name = mlt_properties_get_name(frame_properties, i);
if (name && !strncmp(name, "_movit ", 7)) {
mlt_properties_set_data(properties,
name,
mlt_properties_get_data_at(frame_properties, i, NULL),
0,
NULL,
NULL);
}
}

data = mlt_frame_get_alpha_size(frame, &size);
if (data) {
mlt_frame_set_alpha(self, data, size, NULL);
}
self->convert_audio = frame->convert_audio;
mlt_frame_copy_convert_image(self, frame);
return 0;
}

static int producer_get_audio(mlt_frame self,
void **buffer,
mlt_audio_format *format,
Expand Down Expand Up @@ -549,9 +471,18 @@ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int trac
// Check for last track
done = mlt_properties_get_int(temp_properties, "last_track");

// Handle fx only tracks
// Handle fx only tracks. Hide the dummy when nothing below has
// supplied video/audio yet. If a transition already hid this
// frame, keep that hide bit — do not overwrite — so tractor
// stacking does not re-apply filters to the full composite.
// With no transition, leave video visible so the fx_cut can
// filter the lower track (classic melt path).
if (mlt_properties_get_int(temp_properties, "fx_cut")) {
Comment thread
ddennedy marked this conversation as resolved.
int hide = (video == NULL ? 1 : 0) | (audio == NULL ? 2 : 0);
int hide = mlt_properties_get_int(temp_properties, "hide");
if (video == NULL)
hide |= 1;
if (audio == NULL)
hide |= 2;
mlt_properties_set_int(temp_properties, "hide", hide);
}

Expand All @@ -576,10 +507,8 @@ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int trac
}
if (!done && !mlt_frame_is_test_card(temp)
&& !(mlt_properties_get_int(temp_properties, "hide") & 1)) {
if (video != NULL) {
mlt_deque_push_front(MLT_FRAME_IMAGE_STACK(temp), producer_get_image);
mlt_deque_push_front(MLT_FRAME_IMAGE_STACK(temp), video);
}
if (video != NULL)
mlt_frame_prepend_image_from_service(temp, video);
video = temp;
if (first_video == NULL)
first_video = temp;
Expand All @@ -597,8 +526,7 @@ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int trac

if (video != NULL) {
mlt_properties video_properties = MLT_FRAME_PROPERTIES(first_video);
mlt_frame_push_service(*frame, video);
mlt_frame_push_service(*frame, producer_get_image);
mlt_frame_prepend_image_from_service(*frame, video);
mlt_properties_set_int(frame_properties,
"width",
mlt_properties_get_int(video_properties, "width"));
Expand Down
Loading
Loading