@@ -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
834990void 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 );
0 commit comments