1 /*
2  * producer_image.c -- a QT/QImage based producer for MLT
3  *
4  * NB: This module is designed to be functionally equivalent to the
5  * gtk2 image loading module so it can be used as replacement.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21 
22 #include <framework/mlt_producer.h>
23 #include <framework/mlt_cache.h>
24 #include <framework/mlt_events.h>
25 #include "qimage_wrapper.h"
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <math.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 #include <ctype.h>
34 
35 static void load_filenames( producer_qimage self, mlt_properties producer_properties );
36 static int producer_get_frame( mlt_producer parent, mlt_frame_ptr frame, int index );
37 static void producer_close( mlt_producer parent );
38 
refresh_length(mlt_properties properties,producer_qimage self)39 static void refresh_length( mlt_properties properties, producer_qimage self )
40 {
41 	if ( self->count > mlt_properties_get_int( properties, "length" ) ||
42 	     mlt_properties_get_int( properties, "autolength" ) )
43 	{
44 		int ttl = mlt_properties_get_int( properties, "ttl" );
45 		mlt_position length = self->count * ttl;
46 		mlt_properties_set_position( properties, "length", length );
47 		mlt_properties_set_position( properties, "out", length - 1 );
48 	}
49 }
50 
on_property_changed(mlt_service owner,mlt_producer producer,char * name)51 static void on_property_changed( mlt_service owner, mlt_producer producer, char *name )
52 {
53 	if ( !strcmp( name, "ttl" ) )
54 		refresh_length( MLT_PRODUCER_PROPERTIES(producer), producer->child );
55 }
56 
producer_qimage_init(mlt_profile profile,mlt_service_type type,const char * id,char * filename)57 mlt_producer producer_qimage_init( mlt_profile profile, mlt_service_type type, const char *id, char *filename )
58 {
59 	producer_qimage self = calloc( 1, sizeof( struct producer_qimage_s ) );
60 	if ( self != NULL && mlt_producer_init( &self->parent, self ) == 0 )
61 	{
62 		mlt_producer producer = &self->parent;
63 
64 		// Get the properties interface
65 		mlt_properties properties = MLT_PRODUCER_PROPERTIES( &self->parent );
66 
67 		// Initialize KDE image plugins
68 		if ( !init_qimage( producer, filename ) )
69 		{
70 			// Reject if animation.
71 			mlt_producer_close( producer );
72 			free( self );
73 			return NULL;
74 		}
75 
76 		// Callback registration
77 		producer->get_frame = producer_get_frame;
78 		producer->close = ( mlt_destructor )producer_close;
79 
80 		// Set the default properties
81 		mlt_properties_set( properties, "resource", filename );
82 		mlt_properties_set_int( properties, "ttl", 25 );
83 		mlt_properties_set_int( properties, "aspect_ratio", 1 );
84 		mlt_properties_set_int( properties, "progressive", 1 );
85 		mlt_properties_set_int( properties, "seekable", 1 );
86 
87 		// Validate the resource
88 		if ( filename )
89 			load_filenames( self, properties );
90 		if ( self->count )
91 		{
92 			mlt_frame frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
93 			if ( frame )
94 			{
95 				mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame );
96 				mlt_properties_set_data( frame_properties, "producer_qimage", self, 0, NULL, NULL );
97 				mlt_frame_set_position( frame, mlt_producer_position( producer ) );
98 				int enable_caching = self->count == 1;
99 				refresh_qimage( self, frame, enable_caching );
100 				if ( enable_caching )
101 				{
102 					mlt_cache_item_close( self->qimage_cache );
103 				}
104 				mlt_frame_close( frame );
105 			}
106 		}
107 		if ( self->current_width == 0 )
108 		{
109 			producer_close( producer );
110 			producer = NULL;
111 		}
112 		else
113 		{
114 			mlt_events_listen( properties, self, "property-changed", (mlt_listener) on_property_changed );
115 		}
116 		return producer;
117 	}
118 	free( self );
119 	return NULL;
120 }
121 
load_svg(producer_qimage self,mlt_properties properties,const char * filename)122 static int load_svg( producer_qimage self, mlt_properties properties, const char *filename )
123 {
124 	int result = 0;
125 
126 	// Read xml string
127 	if ( strstr( filename, "<svg" ) )
128 	{
129 		make_tempfile( self, filename );
130 		result = 1;
131 	}
132 	return result;
133 }
134 
load_sequence_deprecated(producer_qimage self,mlt_properties properties,const char * filename)135 static int load_sequence_deprecated( producer_qimage self, mlt_properties properties, const char *filename )
136 {
137 	int result = 0;
138 	const char *start;
139 
140 	// Obtain filenames with pattern containing a begin value, e.g. foo%1234d.png
141 	if ( ( start = strchr( filename, '%' ) ) )
142 	{
143 		const char *end = ++start;
144 		while ( isdigit( *end ) ) end++;
145 		if ( end > start && ( end[0] == 'd' || end[0] == 'i' || end[0] == 'u' ) )
146 		{
147 			int n = end - start;
148 			char *s = calloc( 1, n + 1 );
149 			strncpy( s, start, n );
150 			mlt_properties_set( properties, "begin", s );
151 			free( s );
152 			s = calloc( 1, strlen( filename ) + 2 );
153 			strncpy( s, filename, start - filename );
154 			sprintf( s + ( start - filename ), ".%d%s", n, end );
155 			result = load_sequence_sprintf( self, properties, s );
156 			free( s );
157 		}
158 	}
159 	return result;
160 }
161 
load_sequence_querystring(producer_qimage self,mlt_properties properties,const char * filename)162 static int load_sequence_querystring( producer_qimage self, mlt_properties properties, const char *filename )
163 {
164 	int result = 0;
165 
166 	// Obtain filenames with pattern and begin value in query string
167 	if ( strchr( filename, '%' ) && strchr( filename, '?' ) )
168 	{
169 		// Split filename into pattern and query string
170 		char *s = strdup( filename );
171 		char *querystring = strrchr( s, '?' );
172 		*querystring++ = '\0';
173 		if ( strstr( filename, "begin=" ) )
174 			mlt_properties_set( properties, "begin", strstr( querystring, "begin=" ) + 6 );
175 		else if ( strstr( filename, "begin:" ) )
176 			mlt_properties_set( properties, "begin", strstr( querystring, "begin:" ) + 6 );
177 		// Coerce to an int value so serialization does not have any extra query string cruft
178 		mlt_properties_set_int( properties, "begin", mlt_properties_get_int( properties, "begin" ) );
179 		result = load_sequence_sprintf( self, properties, s );
180 		free( s );
181 	}
182 	return result;
183 }
184 
load_folder(producer_qimage self,mlt_properties properties,const char * filename)185 static int load_folder( producer_qimage self, mlt_properties properties, const char *filename )
186 {
187 	int result = 0;
188 
189 	// Obtain filenames within folder
190 	if ( strstr( filename, "/.all." ) != NULL )
191 	{
192 		char wildcard[ 1024 ];
193 		char *dir_name = strdup( filename );
194 		char *extension = strrchr( dir_name, '.' );
195 
196 		*( strstr( dir_name, "/.all." ) + 1 ) = '\0';
197 		sprintf( wildcard, "*%s", extension );
198 
199 		mlt_properties_dir_list( self->filenames, dir_name, wildcard, 1 );
200 
201 		free( dir_name );
202 		result = 1;
203 	}
204 	return result;
205 }
206 
load_filenames(producer_qimage self,mlt_properties properties)207 static void load_filenames( producer_qimage self, mlt_properties properties )
208 {
209 	char *filename = mlt_properties_get( properties, "resource" );
210 	self->filenames = mlt_properties_new( );
211 
212 	if (!load_svg( self, properties, filename ) &&
213 		!load_sequence_querystring( self, properties, filename ) &&
214 		!load_sequence_sprintf( self, properties, filename ) &&
215 		!load_sequence_deprecated( self, properties, filename ) &&
216 		!load_folder( self, properties, filename ) )
217 	{
218 		mlt_properties_set( self->filenames, "0", filename );
219 	}
220 	self->count = mlt_properties_count( self->filenames );
221 	refresh_length( properties, self );
222 }
223 
producer_get_image(mlt_frame frame,uint8_t ** buffer,mlt_image_format * format,int * width,int * height,int writable)224 static int producer_get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable )
225 {
226 	int error = 0;
227 
228 	// Obtain properties of frame and producer
229 	mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
230 	producer_qimage self = mlt_properties_get_data( properties, "producer_qimage", NULL );
231 	mlt_producer producer = &self->parent;
232 
233 	// Use the width and height suggested by the rescale filter because we can do our own scaling.
234 	if ( mlt_properties_get_int( properties, "rescale_width" ) > 0 )
235 		*width = mlt_properties_get_int( properties, "rescale_width" );
236 	if ( mlt_properties_get_int( properties, "rescale_height" ) > 0 )
237 		*height = mlt_properties_get_int( properties, "rescale_height" );
238 	mlt_service_lock( MLT_PRODUCER_SERVICE( &self->parent ) );
239 	int enable_caching = ( self->count <= 1 || mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( producer ), "ttl" ) > 1 );
240 
241 	// Refresh the image
242 	if ( enable_caching )
243 	{
244 		self->qimage_cache = mlt_service_cache_get( MLT_PRODUCER_SERVICE( producer ), "qimage.qimage" );
245 		self->qimage = mlt_cache_item_data( self->qimage_cache, NULL );
246 		self->image_cache = mlt_service_cache_get( MLT_PRODUCER_SERVICE( producer ), "qimage.image" );
247 		self->current_image = mlt_cache_item_data( self->image_cache, NULL );
248 		self->alpha_cache = mlt_service_cache_get( MLT_PRODUCER_SERVICE( producer ), "qimage.alpha" );
249 		self->current_alpha = mlt_cache_item_data( self->alpha_cache, &self->alpha_size );
250 	}
251 	refresh_image( self, frame, *format, *width, *height, enable_caching );
252 
253 	// Get width and height (may have changed during the refresh)
254 	*width = mlt_properties_get_int( properties, "width" );
255 	*height = mlt_properties_get_int( properties, "height" );
256 	*format = self->format;
257 
258 	// NB: Cloning is necessary with this producer (due to processing of images ahead of use)
259 	// The fault is not in the design of mlt, but in the implementation of the qimage producer...
260 	if ( self->current_image )
261 	{
262 		int image_size = mlt_image_format_size( self->format, self->current_width, self->current_height, NULL );
263 		if ( enable_caching )
264 		{
265 			// Clone the image and the alpha
266 			uint8_t *image_copy = mlt_pool_alloc( image_size );
267 			memcpy( image_copy, self->current_image, image_size );
268 			// Now update properties so we free the copy after
269 			mlt_frame_set_image( frame, image_copy, image_size, mlt_pool_release );
270 			// We're going to pass the copy on
271 			*buffer = image_copy;
272 			mlt_log_debug( MLT_PRODUCER_SERVICE( &self->parent ), "%dx%d (%s)\n",
273 				self->current_width, self->current_height, mlt_image_format_name( *format ) );
274 			// Clone the alpha channel
275 			if ( self->current_alpha )
276 			{
277 				if ( !self->alpha_size )
278 					self->alpha_size = self->current_width * self->current_height;
279 				uint8_t * alpha_copy = mlt_pool_alloc( self->alpha_size );
280 				memcpy( alpha_copy, self->current_alpha, self->alpha_size );
281 				mlt_frame_set_alpha( frame, alpha_copy, self->alpha_size, mlt_pool_release );
282 			}
283 		}
284 		else
285 		{
286 			// For image sequences with ttl = 1 we recreate self->current_image on each frame, no need to clone
287 			mlt_frame_set_image( frame, self->current_image, image_size, mlt_pool_release );
288 			*buffer = self->current_image;
289 			if ( self->current_alpha )
290 			{
291 				if ( !self->alpha_size )
292 					self->alpha_size = self->current_width * self->current_height;
293 				mlt_frame_set_alpha( frame, self->current_alpha, self->alpha_size, mlt_pool_release );
294 			}
295 		}
296 	}
297 	else
298 	{
299 		error = 1;
300 	}
301 
302 	if ( enable_caching )
303 	{
304 		// Release references and locks
305 		mlt_cache_item_close( self->qimage_cache );
306 		mlt_cache_item_close( self->image_cache );
307 		mlt_cache_item_close( self->alpha_cache );
308 	}
309 	mlt_service_unlock( MLT_PRODUCER_SERVICE( &self->parent ) );
310 
311 	return error;
312 }
313 
producer_get_frame(mlt_producer producer,mlt_frame_ptr frame,int index)314 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
315 {
316 	// Get the real structure for this producer
317 	producer_qimage self = producer->child;
318 
319 	// Fetch the producers properties
320 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
321 
322 	if ( self->filenames == NULL && mlt_properties_get( producer_properties, "resource" ) != NULL )
323 		load_filenames( self, producer_properties );
324 
325 	// Generate a frame
326 	*frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
327 
328 	if ( *frame != NULL && self->count > 0 )
329 	{
330 		// Obtain properties of frame and producer
331 		mlt_properties properties = MLT_FRAME_PROPERTIES( *frame );
332 
333 		// Set the producer on the frame properties
334 		mlt_properties_set_data( properties, "producer_qimage", self, 0, NULL, NULL );
335 
336 		// Update timecode on the frame we're creating
337 		mlt_frame_set_position( *frame, mlt_producer_position( producer ) );
338 
339 		// Refresh the image
340 		if ( self->count == 1 || mlt_properties_get_int( producer_properties, "ttl" ) > 1 )
341 		{
342 			self->qimage_cache = mlt_service_cache_get( MLT_PRODUCER_SERVICE( producer ), "qimage.qimage" );
343 			self->qimage = mlt_cache_item_data( self->qimage_cache, NULL );
344 			refresh_qimage( self, *frame, 1 );
345 			mlt_cache_item_close( self->qimage_cache );
346 		}
347 
348 		// Set producer-specific frame properties
349 		mlt_properties_set_int( properties, "progressive", mlt_properties_get_int( producer_properties, "progressive" ) );
350 		double force_ratio = mlt_properties_get_double( producer_properties, "force_aspect_ratio" );
351 		if ( force_ratio > 0.0 )
352 			mlt_properties_set_double( properties, "aspect_ratio", force_ratio );
353 		else
354 			mlt_properties_set_double( properties, "aspect_ratio", mlt_properties_get_double( producer_properties, "aspect_ratio" ) );
355 
356 		// Push the get_image method
357 		mlt_frame_push_get_image( *frame, producer_get_image );
358 	}
359 
360 	// Calculate the next timecode
361 	mlt_producer_prepare_next( producer );
362 
363 	return 0;
364 }
365 
producer_close(mlt_producer parent)366 static void producer_close( mlt_producer parent )
367 {
368 	producer_qimage self = parent->child;
369 	parent->close = NULL;
370 	mlt_service_cache_purge( MLT_PRODUCER_SERVICE(parent) );
371 	mlt_producer_close( parent );
372 	mlt_properties_close( self->filenames );
373 	free( self );
374 }
375