Skip to content

Commit d84c6b4

Browse files
committed
Refactor image conversion
- Refactored image conversion handling across various modules to use new framework functions for better clarity and maintainability. - Now, there can be multiple possible image converters per frame stopping after the first success. But also, one converter can part of the work (e.g. dowload from GPU) while a later one converts it to the actual requested format. - Replaced direct calls to `convert_image` with `mlt_frame_convert_image` and added checks for the existence of conversion functions. - Improved documentation in YAML files to clarify the purpose of image conversion callbacks. - Added tests to verify the correct propagation of image conversion functions through frames and producers.
1 parent be8031c commit d84c6b4

32 files changed

Lines changed: 580 additions & 234 deletions

NEWS

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
MLT Release Notes
22
-----------------
33

4+
Version 7.40.0
5+
6+
Framework
7+
- Added list-based image-conversion callback dispatch on mlt_frame.
8+
Use `mlt_frame_push_convert_image()` to register a converter instead of
9+
setting `frame->convert_image` directly. Multiple converters are tried in
10+
registration order; the first that succeeds wins. New public API:
11+
- `mlt_frame_push_convert_image()`
12+
- `mlt_frame_convert_image()`
13+
- `mlt_frame_next_convert_image()`
14+
- `mlt_frame_has_convert_image()`
15+
- `mlt_frame_copy_convert_image()`
16+
- API behavior change: `mlt_frame_s::convert_image` is now read-only after
17+
init and is permanently set to the dispatcher `mlt_frame_convert_image()`.
18+
External code that set this field directly to a custom function will no
19+
longer have that function called. If you really need that (never heard of
20+
someone who does), clear the frame's "_convert_image_callbacks" property
21+
and use `mlt_frame_push_convert_image()` instead.
22+
- Image converters (`movit.convert`, `avcolor_space`, `imageconvert`) are
23+
now attached data-driven via `loader.ini` key `image_convert`.
24+
25+
426
Version 7.38.0
527

628
Framework

src/framework/mlt.vers

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,3 +687,12 @@ MLT_7.36.0 {
687687
mlt_color_convert_trc;
688688
mlt_profile_is_valid;
689689
} MLT_7.34.0;
690+
691+
MLT_7.40.0 {
692+
global:
693+
mlt_frame_push_convert_image;
694+
mlt_frame_has_convert_image;
695+
mlt_frame_convert_image;
696+
mlt_frame_next_convert_image;
697+
mlt_frame_copy_convert_image;
698+
} MLT_7.34.0;

src/framework/mlt_frame.c

Lines changed: 165 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ mlt_frame mlt_frame_init(mlt_service service)
6363
self->stack_image = mlt_deque_init();
6464
self->stack_audio = mlt_deque_init();
6565
self->stack_service = mlt_deque_init();
66+
self->convert_image = mlt_frame_convert_image;
6667
}
6768

6869
return self;
@@ -423,8 +424,8 @@ static int generate_test_image(mlt_properties properties,
423424
mlt_frame_get_aspect_ratio(test_frame));
424425
mlt_properties_set_int(properties, "width", *width);
425426
mlt_properties_set_int(properties, "height", *height);
426-
if (test_frame->convert_image && requested_format != mlt_image_none)
427-
test_frame->convert_image(test_frame, buffer, format, requested_format);
427+
if (mlt_frame_has_convert_image(test_frame) && requested_format != mlt_image_none)
428+
mlt_frame_convert_image(test_frame, buffer, format, requested_format);
428429
mlt_properties_set_int(properties, "format", *format);
429430
}
430431
} else {
@@ -516,8 +517,8 @@ int mlt_frame_get_image(mlt_frame self,
516517
if (!error && buffer && *buffer) {
517518
mlt_properties_set_int(properties, "width", *width);
518519
mlt_properties_set_int(properties, "height", *height);
519-
if (self->convert_image && requested_format != mlt_image_none)
520-
self->convert_image(self, buffer, format, requested_format);
520+
if (mlt_frame_has_convert_image(self) && requested_format != mlt_image_none)
521+
mlt_frame_convert_image(self, buffer, format, requested_format);
521522
mlt_properties_set_int(properties, "format", *format);
522523
} else {
523524
error = generate_test_image(properties, buffer, format, width, height, writable);
@@ -527,8 +528,8 @@ int mlt_frame_get_image(mlt_frame self,
527528
*buffer = mlt_properties_get_data(properties, "image", NULL);
528529
*width = mlt_properties_get_int(properties, "width");
529530
*height = mlt_properties_get_int(properties, "height");
530-
if (self->convert_image && *buffer && requested_format != mlt_image_none) {
531-
self->convert_image(self, buffer, format, requested_format);
531+
if (mlt_frame_has_convert_image(self) && *buffer && requested_format != mlt_image_none) {
532+
mlt_frame_convert_image(self, buffer, format, requested_format);
532533
mlt_properties_set_int(properties, "format", *format);
533534
}
534535
} else {
@@ -829,6 +830,161 @@ void mlt_frame_close(mlt_frame self)
829830
}
830831
}
831832

833+
/* ---- Image conversion callback list ---- */
834+
835+
#define CONVERT_IMAGE_CALLBACKS "_convert_image_callbacks"
836+
#define CONVERT_IMAGE_IDX "_convert_image_idx"
837+
838+
static int do_convert_image(
839+
mlt_frame self, uint8_t **image, mlt_image_format *format, mlt_image_format output, int idx)
840+
{
841+
mlt_properties props = MLT_FRAME_PROPERTIES(self);
842+
mlt_deque list = mlt_properties_get_data(props, CONVERT_IMAGE_CALLBACKS, NULL);
843+
int count = list ? mlt_deque_count(list) : 0;
844+
845+
while (idx < count) {
846+
mlt_properties_set_int(props, CONVERT_IMAGE_IDX, idx);
847+
mlt_convert_image fn = (mlt_convert_image) mlt_deque_peek(list, idx);
848+
int error = fn(self, image, format, output);
849+
if (!error)
850+
return 0;
851+
// fn may have internally advanced _convert_image_idx via mlt_frame_next_convert_image;
852+
// skip past any indices already tried to avoid double-calling.
853+
int next = mlt_properties_get_int(props, CONVERT_IMAGE_IDX);
854+
idx = (next > idx) ? next + 1 : idx + 1;
855+
}
856+
return 1;
857+
}
858+
859+
/** Register an image-conversion callback on the frame.
860+
*
861+
* Callbacks are appended to a list and dispatched in order by
862+
* \p mlt_frame_convert_image. If \p convert is already registered on this
863+
* frame it is silently ignored (deduplication). Has no effect if either
864+
* argument is NULL.
865+
*
866+
* \public \memberof mlt_frame_s
867+
* \param self a frame
868+
* \param convert the conversion callback to register
869+
*/
870+
void mlt_frame_push_convert_image(mlt_frame self, mlt_convert_image convert)
871+
{
872+
if (!self || !convert)
873+
return;
874+
mlt_properties props = MLT_FRAME_PROPERTIES(self);
875+
mlt_deque list = mlt_properties_get_data(props, CONVERT_IMAGE_CALLBACKS, NULL);
876+
if (!list) {
877+
list = mlt_deque_init();
878+
mlt_properties_set_data(props,
879+
CONVERT_IMAGE_CALLBACKS,
880+
list,
881+
0,
882+
(mlt_destructor) mlt_deque_close,
883+
NULL);
884+
}
885+
// Deduplicate: don't register the same function pointer twice.
886+
for (int i = 0; i < mlt_deque_count(list); i++)
887+
if (mlt_deque_peek(list, i) == (void *) convert)
888+
return;
889+
mlt_deque_push_back(list, (void *) convert);
890+
}
891+
892+
/** Determine whether any image-conversion callbacks are registered.
893+
*
894+
* \public \memberof mlt_frame_s
895+
* \param self a frame
896+
* \return non-zero if at least one callback has been pushed, zero otherwise
897+
*/
898+
int mlt_frame_has_convert_image(mlt_frame self)
899+
{
900+
if (!self)
901+
return 0;
902+
mlt_deque list = mlt_properties_get_data(MLT_FRAME_PROPERTIES(self),
903+
CONVERT_IMAGE_CALLBACKS,
904+
NULL);
905+
return list && mlt_deque_count(list) > 0;
906+
}
907+
908+
/** Convert the frame image to the requested format.
909+
*
910+
* Dispatches the registered conversion callbacks in order, stopping at the
911+
* first one that succeeds (returns 0). If no callback succeeds, returns 1.
912+
* This is also the function stored in the \p convert_image field of every
913+
* frame; external callers that hold a pointer to that field will therefore
914+
* invoke this dispatcher transparently.
915+
*
916+
* \public \memberof mlt_frame_s
917+
* \param self a frame
918+
* \param[in,out] image the image buffer pointer
919+
* \param[in,out] format the current image format; updated to \p output on success
920+
* \param output the desired image format
921+
* \return 0 on success, 1 if no callback could perform the conversion
922+
*/
923+
int mlt_frame_convert_image(mlt_frame self,
924+
uint8_t **image,
925+
mlt_image_format *format,
926+
mlt_image_format output)
927+
{
928+
return do_convert_image(self, image, format, output, 0);
929+
}
930+
931+
/** Advance to the next image-conversion callback and continue dispatching.
932+
*
933+
* Call this from inside a conversion callback to delegate to the next
934+
* registered callback in the list (fallback chaining). The current
935+
* callback's index is read from the frame's internal dispatch state, so
936+
* this must only be called from within an active \p mlt_frame_convert_image
937+
* dispatch.
938+
*
939+
* \public \memberof mlt_frame_s
940+
* \param self a frame
941+
* \param[in,out] image the image buffer pointer
942+
* \param[in,out] format the current image format
943+
* \param output the desired image format
944+
* \return 0 if a subsequent callback succeeded, 1 if none did
945+
*/
946+
int mlt_frame_next_convert_image(mlt_frame self,
947+
uint8_t **image,
948+
mlt_image_format *format,
949+
mlt_image_format output)
950+
{
951+
int idx = mlt_properties_get_int(MLT_FRAME_PROPERTIES(self), CONVERT_IMAGE_IDX);
952+
return do_convert_image(self, image, format, output, idx + 1);
953+
}
954+
955+
/** Copy the image-conversion callback list from one frame to another.
956+
*
957+
* Used by the tractor and clone functions to propagate converters attached
958+
* to track or source frames onto merged or cloned frames. If \p dst already
959+
* has a callback list the copy is skipped (first-wins semantics).
960+
*
961+
* \public \memberof mlt_frame_s
962+
* \param dst the frame to copy callbacks onto
963+
* \param src the frame to copy callbacks from
964+
*/
965+
void mlt_frame_copy_convert_image(mlt_frame dst, mlt_frame src)
966+
{
967+
mlt_deque src_list = mlt_properties_get_data(MLT_FRAME_PROPERTIES(src),
968+
CONVERT_IMAGE_CALLBACKS,
969+
NULL);
970+
if (!src_list || !mlt_deque_count(src_list))
971+
return;
972+
mlt_deque dst_list = mlt_properties_get_data(MLT_FRAME_PROPERTIES(dst),
973+
CONVERT_IMAGE_CALLBACKS,
974+
NULL);
975+
if (dst_list)
976+
return;
977+
dst_list = mlt_deque_init();
978+
for (int i = 0; i < mlt_deque_count(src_list); i++)
979+
mlt_deque_push_back(dst_list, mlt_deque_peek(src_list, i));
980+
mlt_properties_set_data(MLT_FRAME_PROPERTIES(dst),
981+
CONVERT_IMAGE_CALLBACKS,
982+
dst_list,
983+
0,
984+
(mlt_destructor) mlt_deque_close,
985+
NULL);
986+
}
987+
832988
/***** convenience functions *****/
833989

834990
void mlt_frame_write_ppm(mlt_frame frame)
@@ -942,18 +1098,7 @@ mlt_frame mlt_frame_clone(mlt_frame self, int is_deep)
9421098
0,
9431099
NULL,
9441100
NULL);
945-
mlt_properties_set_data(new_props,
946-
"movit.convert",
947-
mlt_properties_get_data(properties, "movit.convert", NULL),
948-
0,
949-
NULL,
950-
NULL);
951-
mlt_properties_set_data(new_props,
952-
"_movit cpu_convert",
953-
mlt_properties_get_data(properties, "_movit cpu_convert", NULL),
954-
0,
955-
NULL,
956-
NULL);
1101+
mlt_frame_copy_convert_image(new_frame, self);
9571102

9581103
if (is_deep) {
9591104
data = mlt_properties_get_data(properties, "audio", &size);
@@ -1045,18 +1190,7 @@ mlt_frame mlt_frame_clone_audio(mlt_frame self, int is_deep)
10451190
0,
10461191
NULL,
10471192
NULL);
1048-
mlt_properties_set_data(new_props,
1049-
"movit.convert",
1050-
mlt_properties_get_data(properties, "movit.convert", NULL),
1051-
0,
1052-
NULL,
1053-
NULL);
1054-
mlt_properties_set_data(new_props,
1055-
"_movit cpu_convert",
1056-
mlt_properties_get_data(properties, "_movit cpu_convert", NULL),
1057-
0,
1058-
NULL,
1059-
NULL);
1193+
mlt_frame_copy_convert_image(new_frame, self);
10601194

10611195
if (is_deep) {
10621196
data = mlt_properties_get_data(properties, "audio", &size);
@@ -1117,18 +1251,7 @@ mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep)
11171251
0,
11181252
NULL,
11191253
NULL);
1120-
mlt_properties_set_data(new_props,
1121-
"movit.convert",
1122-
mlt_properties_get_data(properties, "movit.convert", NULL),
1123-
0,
1124-
NULL,
1125-
NULL);
1126-
mlt_properties_set_data(new_props,
1127-
"_movit cpu_convert",
1128-
mlt_properties_get_data(properties, "_movit cpu_convert", NULL),
1129-
0,
1130-
NULL,
1131-
NULL);
1254+
mlt_frame_copy_convert_image(new_frame, self);
11321255

11331256
if (is_deep) {
11341257
data = mlt_properties_get_data(properties, "image", &size);

src/framework/mlt_frame.h

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* \brief interface for all frame classes
44
* \see mlt_frame_s
55
*
6-
* Copyright (C) 2003-2023 Meltytech, LLC
6+
* Copyright (C) 2003-2026 Meltytech, LLC
77
*
88
* This library is free software; you can redistribute it and/or
99
* modify it under the terms of the GNU Lesser General Public
@@ -52,6 +52,16 @@ typedef int (*mlt_get_audio)(mlt_frame self,
5252
int *channels,
5353
int *samples);
5454

55+
/** Callback function to convert image format.
56+
*
57+
* Registered converters are tried in order. Return non-zero to indicate the
58+
* conversion was not handled so the next registered converter will be tried.
59+
*/
60+
typedef int (*mlt_convert_image)(mlt_frame self,
61+
uint8_t **image,
62+
mlt_image_format *input,
63+
mlt_image_format output);
64+
5565
/** \brief Frame class
5666
*
5767
* The frame is the primary data object that gets passed around to and through services.
@@ -91,7 +101,9 @@ struct mlt_frame_s
91101
{
92102
struct mlt_properties_s parent; /**< \private A frame extends properties. */
93103

94-
/** Convert the image format (callback function).
104+
/** Image format conversion dispatcher (read-only after init).
105+
* Always set to mlt_frame_convert_image by mlt_frame_init().
106+
* Do not set this field directly; use mlt_frame_push_convert_image() instead.
95107
* \param self a frame
96108
* \param[in,out] image a buffer of image data
97109
* \param[in,out] input the image format of supplied image data
@@ -171,6 +183,17 @@ MLT_EXPORT mlt_producer mlt_frame_get_original_producer(mlt_frame self);
171183
MLT_EXPORT void mlt_frame_close(mlt_frame self);
172184
MLT_EXPORT mlt_properties mlt_frame_unique_properties(mlt_frame self, mlt_service service);
173185
MLT_EXPORT mlt_properties mlt_frame_get_unique_properties(mlt_frame self, mlt_service service);
186+
MLT_EXPORT void mlt_frame_push_convert_image(mlt_frame self, mlt_convert_image convert);
187+
MLT_EXPORT int mlt_frame_has_convert_image(mlt_frame self);
188+
MLT_EXPORT int mlt_frame_convert_image(mlt_frame self,
189+
uint8_t **image,
190+
mlt_image_format *format,
191+
mlt_image_format output);
192+
MLT_EXPORT int mlt_frame_next_convert_image(mlt_frame self,
193+
uint8_t **image,
194+
mlt_image_format *format,
195+
mlt_image_format output);
196+
MLT_EXPORT void mlt_frame_copy_convert_image(mlt_frame dst, mlt_frame src);
174197
MLT_EXPORT mlt_frame mlt_frame_clone(mlt_frame self, int is_deep);
175198
MLT_EXPORT mlt_frame mlt_frame_clone_audio(mlt_frame self, int is_deep);
176199
MLT_EXPORT mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep);

src/framework/mlt_tractor.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* \brief tractor service class
44
* \see mlt_tractor_s
55
*
6-
* Copyright (C) 2003-2022 Meltytech, LLC
6+
* Copyright (C) 2003-2026 Meltytech, LLC
77
*
88
* This library is free software; you can redistribute it and/or
99
* modify it under the terms of the GNU Lesser General Public
@@ -418,8 +418,8 @@ static int producer_get_image(mlt_frame self,
418418
if (data) {
419419
mlt_frame_set_alpha(self, data, size, NULL);
420420
}
421-
self->convert_image = frame->convert_image;
422421
self->convert_audio = frame->convert_audio;
422+
mlt_frame_copy_convert_image(self, frame);
423423
return 0;
424424
}
425425

@@ -537,11 +537,10 @@ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int trac
537537
subtitle_properties);
538538
}
539539

540-
// Copy the format conversion virtual functions
541-
if (!(*frame)->convert_image && temp->convert_image)
542-
(*frame)->convert_image = temp->convert_image;
540+
// Copy the format conversion callbacks
543541
if (!(*frame)->convert_audio && temp->convert_audio)
544542
(*frame)->convert_audio = temp->convert_audio;
543+
mlt_frame_copy_convert_image(*frame, temp);
545544

546545
// Check for last track
547546
done = mlt_properties_get_int(temp_properties, "last_track");

0 commit comments

Comments
 (0)