1 /*
2  * producer_count.c -- counting producer
3  * Copyright (C) 2013 Brian Matherly
4  * Author: Brian Matherly <pez4brian@yahoo.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include <framework/mlt.h>
22 #include <math.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 /* Private Constants */
28 #define MAX_TEXT_LEN        512
29 #define LINE_PIXEL_VALUE    0x00
30 #define RING_PIXEL_VALUE    0xff
31 #define CLOCK_PIXEL_VALUE   0x50
32 #define FRAME_BACKGROUND_COLOR   "0xd0d0d0ff"
33 #define TEXT_BACKGROUND_COLOR    "0x00000000"
34 #define TEXT_FOREGROUND_COLOR    "0x000000ff"
35 // Ratio of graphic elements relative to image size
36 #define LINE_WIDTH_RATIO     1
37 #define OUTER_RING_RATIO    90
38 #define INNER_RING_RATIO    80
39 #define TEXT_SIZE_RATIO     70
40 
41 typedef struct
42 {
43 	mlt_position position;
44 	int fps;
45 	int hours;
46 	int minutes;
47 	int seconds;
48 	int frames;
49 	char sep; // Either : or ; (for ndf)
50 } time_info;
51 
get_time_info(mlt_producer producer,mlt_frame frame,time_info * info)52 static void get_time_info( mlt_producer producer, mlt_frame frame, time_info* info )
53 {
54 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
55 	mlt_position position = mlt_frame_original_position( frame );
56 
57 	info->fps = ceil( mlt_producer_get_fps( producer ) );
58 
59 	char* direction = mlt_properties_get( producer_properties, "direction" );
60 	if( !strcmp( direction, "down" ) )
61 	{
62 		mlt_position length = mlt_properties_get_int( producer_properties, "length" );
63 		info->position = length - 1 - position;
64 	}
65 	else
66 	{
67 		info->position = position;
68 	}
69 
70 	char* tc_str = NULL;
71 	if( mlt_properties_get_int( producer_properties, "drop" ) )
72 	{
73 		tc_str = mlt_properties_frames_to_time( producer_properties, info->position, mlt_time_smpte_df );
74 	}
75 	else
76 	{
77 		tc_str = mlt_properties_frames_to_time( producer_properties, info->position, mlt_time_smpte_ndf );
78 	}
79 	sscanf( tc_str, "%02d:%02d:%02d%c%d", &info->hours, &info->minutes, &info->seconds, &info->sep, &info->frames );
80 }
81 
mix_pixel(uint8_t * image,int width,int x,int y,int value,float mix)82 static inline void mix_pixel( uint8_t* image, int width, int x, int y, int value, float mix )
83 {
84 	uint8_t* p = image + (( y * width ) + x ) * 4;
85 
86 	if( mix != 1.0 )
87 	{
88 		value = ((float)value * mix) + ((float)*p * (1.0 - mix));
89 	}
90 
91 	*p = value;
92 	p++;
93 	*p = value;
94 	p++;
95 	*p = value;
96 }
97 
98 /** Fill an audio buffer with 1kHz samples.
99 */
100 
fill_beep(mlt_properties producer_properties,float * buffer,int frequency,int channels,int samples)101 static void fill_beep( mlt_properties producer_properties, float* buffer, int frequency, int channels, int samples )
102 {
103 	int s = 0;
104 	int c = 0;
105 
106 	for( s = 0; s < samples; s++ )
107 	{
108 		float f = 1000.0;
109 		float t = (float)s/(float)frequency;
110 		float value = sin( 2*M_PI*f*t );
111 
112 		for( c = 0; c < channels; c++ )
113 		{
114 			float* sample_ptr = buffer + (c * samples) + s;
115 			*sample_ptr = value;
116 		}
117 	}
118 }
119 
producer_get_audio(mlt_frame frame,int16_t ** buffer,mlt_audio_format * format,int * frequency,int * channels,int * samples)120 static int producer_get_audio( mlt_frame frame, int16_t** buffer, mlt_audio_format* format, int* frequency, int* channels, int* samples )
121 {
122 	mlt_producer producer = (mlt_producer)mlt_frame_pop_audio( frame );
123 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
124 	char* sound = mlt_properties_get( producer_properties, "sound" );
125 	double fps = mlt_producer_get_fps( producer );
126 	mlt_position position = mlt_frame_original_position( frame );
127 	int size = 0;
128 	int do_beep = 0;
129 	time_info info;
130 
131 	if( fps == 0 ) fps = 25;
132 
133 	// Correct the returns if necessary
134 	*format = mlt_audio_float;
135 	*frequency = *frequency <= 0 ? 48000 : *frequency;
136 	*channels = *channels <= 0 ? 2 : *channels;
137 	*samples = *samples <= 0 ? mlt_audio_calculate_frame_samples( fps, *frequency, position ) : *samples;
138 
139 	// Allocate the buffer
140 	size = *samples * *channels * sizeof( float );
141 	*buffer = mlt_pool_alloc( size );
142 
143 	mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
144 
145 	get_time_info( producer, frame, &info );
146 
147 	// Determine if this should be a tone or silence.
148 	if( strcmp( sound, "none") )
149 	{
150 		if( !strcmp( sound, "2pop" ) )
151 		{
152 			mlt_position out = mlt_properties_get_int( producer_properties, "out" );
153 			mlt_position frames = out - position;
154 
155 			if( frames == ( info.fps * 2 ) )
156 			{
157 				do_beep = 1;
158 			}
159 		}
160 		else if( !strcmp( sound, "frame0" ) )
161 		{
162 			if( info.frames == 0 )
163 			{
164 				do_beep = 1;
165 			}
166 		}
167 	}
168 
169 	if( do_beep )
170 	{
171 		fill_beep( producer_properties, (float*)*buffer, *frequency, *channels, *samples );
172 	}
173 	else
174 	{
175 		// Fill silence.
176 		memset( *buffer, 0, size );
177 	}
178 
179 	mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
180 
181 	// Set the buffer for destruction
182 	mlt_frame_set_audio( frame, *buffer, *format, size, mlt_pool_release );
183 	return 0;
184 }
185 
get_background_frame(mlt_producer producer)186 static mlt_frame get_background_frame( mlt_producer producer )
187 {
188 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
189 	mlt_frame bg_frame = NULL;
190 	mlt_producer color_producer = mlt_properties_get_data( producer_properties, "_color_producer", NULL );
191 
192 	if( !color_producer )
193 	{
194 		mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
195 		color_producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "colour:" );
196 		mlt_properties_set_data( producer_properties, "_color_producer", color_producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
197 
198 		mlt_properties color_properties = MLT_PRODUCER_PROPERTIES( color_producer );
199 		mlt_properties_set( color_properties, "colour", FRAME_BACKGROUND_COLOR );
200 	}
201 
202 	if( color_producer )
203 	{
204 		mlt_producer_seek( color_producer, 0 );
205 		mlt_service_get_frame( MLT_PRODUCER_SERVICE( color_producer ), &bg_frame, 0 );
206 	}
207 
208 	return bg_frame;
209 }
210 
get_text_frame(mlt_producer producer,time_info * info)211 static mlt_frame get_text_frame( mlt_producer producer, time_info* info  )
212 {
213 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
214 	mlt_producer text_producer = mlt_properties_get_data( producer_properties, "_text_producer", NULL );
215 	mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
216 	mlt_frame text_frame = NULL;
217 
218 	if( !text_producer )
219 	{
220 		text_producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "qtext:" );
221 
222 		// Use pango if qtext is not available.
223 		if( !text_producer )
224 			text_producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "pango:" );
225 
226 		if( !text_producer )
227 			mlt_log_warning( MLT_PRODUCER_SERVICE(producer), "QT or GTK modules required for count producer.\n" );
228 
229 		// Save the producer for future use.
230 		mlt_properties_set_data( producer_properties, "_text_producer", text_producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
231 
232 		// Calculate the font size.
233 		char font_size[MAX_TEXT_LEN];
234 		snprintf( font_size, MAX_TEXT_LEN - 1, "%dpx", profile->height * TEXT_SIZE_RATIO / 100 );
235 
236 		// Configure the producer.
237 		mlt_properties text_properties = MLT_PRODUCER_PROPERTIES( text_producer );
238 		mlt_properties_set( text_properties, "size", font_size );
239 		mlt_properties_set( text_properties, "weight", "400" );
240 		mlt_properties_set( text_properties, "fgcolour", TEXT_FOREGROUND_COLOR );
241 		mlt_properties_set( text_properties, "bgcolour", TEXT_BACKGROUND_COLOR );
242 		mlt_properties_set( text_properties, "pad", "0" );
243 		mlt_properties_set( text_properties, "outline", "0" );
244 		mlt_properties_set( text_properties, "align", "center" );
245 	}
246 
247 	if( text_producer )
248 	{
249 		mlt_properties text_properties = MLT_PRODUCER_PROPERTIES( text_producer );
250 		char* style = mlt_properties_get( producer_properties, "style" );
251 		char text[MAX_TEXT_LEN] = "";
252 
253 		// Apply the time style
254 		if( !strcmp( style, "frames" ) )
255 		{
256 			snprintf( text, MAX_TEXT_LEN - 1, MLT_POSITION_FMT, info->position );
257 		}
258 		else if( !strcmp( style, "timecode" ) )
259 		{
260 			snprintf( text, MAX_TEXT_LEN - 1, "%02d:%02d:%02d%c%0*d",
261 					info->hours, info->minutes, info->seconds, info->sep,
262 					( info->fps > 999? 4 : info->fps > 99? 3 : 2 ), info->frames );
263 		}
264 		else if( !strcmp( style, "clock" ) )
265 		{
266 			snprintf( text, MAX_TEXT_LEN - 1, "%.2d:%.2d:%.2d", info->hours, info->minutes, info->seconds );
267 		}
268 		else if( !strcmp( style, "seconds+1" ) )
269 		{
270 			snprintf( text, MAX_TEXT_LEN - 1, "%d", info->seconds + 1 );
271 		}
272 		else // seconds
273 		{
274 			snprintf( text, MAX_TEXT_LEN - 1, "%d", info->seconds );
275 		}
276 
277 		mlt_properties_set( text_properties, "text", text );
278 
279 		// Get the frame.
280 		mlt_service_get_frame( MLT_PRODUCER_SERVICE( text_producer ), &text_frame, 0 );
281 	}
282 
283 	return text_frame;
284 }
285 
draw_ring(uint8_t * image,mlt_profile profile,int radius,int line_width)286 static void draw_ring( uint8_t* image, mlt_profile profile, int radius, int line_width )
287 {
288 	float sar = mlt_profile_sar( profile );
289 	int x_center = profile->width / 2;
290 	int y_center = profile->height / 2;
291 	int max_radius = radius + line_width;
292 	int a = max_radius + 1;
293 	int b = 0;
294 
295 	line_width += 1; // Compensate for aliasing.
296 
297 	// Scan through each pixel in one quadrant of the circle.
298 	while( a-- )
299 	{
300 		b = ( max_radius / sar ) + 1.0;
301 		while( b-- )
302 		{
303 			// Use Pythagorean theorem to determine the distance from this pixel to the center.
304 			float a2 = a*a;
305 			float b2 = b*sar*b*sar;
306 			float c = sqrtf( a2 + b2 );
307 			float distance = c - radius;
308 
309 			if( distance > 0 && distance < line_width )
310 			{
311 				// This pixel is within the ring.
312 				float mix = 1.0;
313 
314 				if( distance < 1.0 )
315 				{
316 					// Antialias the outside of the ring
317 					mix = distance;
318 				}
319 				else if( (float)line_width - distance < 1.0 )
320 				{
321 					// Antialias the inside of the ring
322 					mix = (float)line_width - distance;
323 				}
324 
325 				// Apply this value to all 4 quadrants of the circle.
326 				mix_pixel( image, profile->width, x_center + b, y_center - a, RING_PIXEL_VALUE, mix );
327 				mix_pixel( image, profile->width, x_center - b, y_center - a, RING_PIXEL_VALUE, mix );
328 				mix_pixel( image, profile->width, x_center + b, y_center + a, RING_PIXEL_VALUE, mix );
329 				mix_pixel( image, profile->width, x_center - b, y_center + a, RING_PIXEL_VALUE, mix );
330 			}
331 		}
332 	}
333 }
334 
draw_cross(uint8_t * image,mlt_profile profile,int line_width)335 static void draw_cross( uint8_t* image, mlt_profile profile, int line_width )
336 {
337 	int x = 0;
338 	int y = 0;
339 	int i = 0;
340 
341 	// Draw a horizontal line
342 	i = line_width;
343 	while( i-- )
344 	{
345 		y = ( profile->height - line_width ) / 2 + i;
346 		x = profile->width - 1;
347 		while( x-- )
348 		{
349 			mix_pixel( image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0 );
350 		}
351 	}
352 
353 	// Draw a vertical line
354 	line_width = lrint((float)line_width * mlt_profile_sar( profile ));
355 	i = line_width;
356 	while( i-- )
357 	{
358 		x = ( profile->width - line_width ) / 2 + i;
359 		y = profile->height - 1;
360 		while( y-- )
361 		{
362 			mix_pixel( image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0 );
363 		}
364 	}
365 }
366 
draw_clock(uint8_t * image,mlt_profile profile,int angle,int line_width)367 static void draw_clock( uint8_t* image, mlt_profile profile, int angle, int line_width )
368 {
369 	float sar = mlt_profile_sar( profile );
370 	int q = 0;
371 	int x_center = profile->width / 2;
372 	int y_center = profile->height / 2;
373 
374 	line_width += 1; // Compensate for aliasing.
375 
376 	// Look at each quadrant of the frame to see what should be done.
377 	for( q = 1; q <= 4; q++ )
378 	{
379 		int max_angle = q * 90;
380 		int x_sign = ( q == 1 || q == 2 ) ? 1 : -1;
381 		int y_sign = ( q == 1 || q == 4 ) ? 1 : -1;
382 		int x_start = x_center * x_sign;
383 		int y_start = y_center * y_sign;
384 
385 		// Compensate for rounding error of even lengths
386 		// (there is no "middle" pixel so everything is offset).
387 		if( x_sign == 1 && profile->width % 2 == 0 ) x_start--;
388 		if( y_sign == -1 && profile->height % 2 == 0 ) y_start++;
389 
390 		if( angle >= max_angle )
391 		{
392 			// This quadrant is completely behind the clock hand. Fill it in.
393 			int dx = x_start + x_sign;
394 			while( dx )
395 			{
396 				dx -= x_sign;
397 				int dy = y_start + y_sign;
398 				while( dy )
399 				{
400 					dy -= y_sign;
401 					mix_pixel( image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0 );
402 				}
403 			}
404 		}
405 		else if ( max_angle - angle < 90 )
406 		{
407 			// This quadrant is partially filled
408 			// Calculate a point (vx,vy) that lies on the line created by the angle from 0,0.
409 			int vx = 0;
410 			int vy = y_start;
411 			float lv = 0;
412 
413 			// Assume maximum y and calculate the corresponding x value
414 			// for a point at the other end of this line.
415 			if( x_sign * y_sign == 1 )
416 			{
417 				vx = x_sign * sar * y_center / tan( ( max_angle - angle ) * M_PI / 180.0 );
418 			}
419 			else
420 			{
421 				vx = x_sign * sar * y_center * tan( ( max_angle - angle ) * M_PI / 180.0 );
422 			}
423 
424 			// Calculate the length of the line defined by vx,vy
425 			lv = sqrtf((float)(vx*vx)*sar*sar + (float)vy*vy);
426 
427 			// Scan through each pixel in the quadrant counting up/down to 0,0.
428 			int dx = x_start + x_sign;
429 			while( dx )
430 			{
431 				dx -= x_sign;
432 				int dy = y_start + y_sign;
433 				while( dy )
434 				{
435 					dy -= y_sign;
436 					// Calculate the cross product to determine which side of
437 					// the line this pixel lies on.
438 					int xp = vx * (vy - dy) - vy * (vx - dx);
439 					xp = xp * -1; // Easier to work with positive. Positive number means "behind" the line.
440 					if( xp > 0 )
441 					{
442 						// This pixel is behind the clock hand and should be filled in.
443 						// Calculate the distance from the pixel to the line to determine
444 						// if it is part of the clock hand.
445 						float distance = (float)xp / lv;
446 						int val = CLOCK_PIXEL_VALUE;
447 						float mix = 1.0;
448 
449 						if( distance < line_width )
450 						{
451 							// This pixel makes up the clock hand.
452 							val = LINE_PIXEL_VALUE;
453 
454 							if( distance < 1.0 )
455 							{
456 								// Antialias the outside of the clock hand
457 								mix = distance;
458 							}
459 							else if( (float)line_width - distance < 1.0 )
460 							{
461 								// Antialias the inside of the clock hand
462 								mix_pixel( image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0 );
463 								mix = (float)line_width - distance;
464 							}
465 						}
466 
467 						mix_pixel( image, profile->width, x_center + dx, y_center - dy, val, mix );
468 					}
469 				}
470 			}
471 		}
472 	}
473 }
474 
add_clock_to_frame(mlt_producer producer,mlt_frame frame,time_info * info)475 static void add_clock_to_frame( mlt_producer producer, mlt_frame frame, time_info* info )
476 {
477 	mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
478 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
479 	uint8_t* image = NULL;
480 	mlt_image_format format = mlt_image_rgb24a;
481 	int size = 0;
482 	int width = profile->width;
483 	int height = profile->height;
484 	int line_width = LINE_WIDTH_RATIO * (width > height ? height : width) / 100;
485 	int radius = (width > height ? height : width) / 2;
486 	char* direction = mlt_properties_get( producer_properties, "direction" );
487 	int clock_angle = 0;
488 
489 	mlt_frame_get_image( frame, &image, &format, &width, &height, 1 );
490 
491 	// Calculate the angle for the clock.
492 	int frames = info->frames;
493 	if( !strcmp( direction, "down" ) )
494 	{
495 		frames = info->fps - info->frames - 1;
496 	}
497 	clock_angle = (frames + 1) * 360 / info->fps;
498 
499 	draw_clock( image, profile, clock_angle, line_width );
500 	draw_cross( image, profile, line_width );
501 	draw_ring( image, profile, ( radius * OUTER_RING_RATIO ) / 100, line_width );
502 	draw_ring( image, profile, ( radius * INNER_RING_RATIO ) / 100, line_width );
503 
504 	size = mlt_image_format_size( format, width, height, NULL );
505 	mlt_frame_set_image( frame, image, size, mlt_pool_release );
506 }
507 
508 
add_text_to_bg(mlt_producer producer,mlt_frame bg_frame,mlt_frame text_frame)509 static void add_text_to_bg( mlt_producer producer, mlt_frame bg_frame, mlt_frame text_frame )
510 {
511 	mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
512 	mlt_transition transition = mlt_properties_get_data( producer_properties, "_transition", NULL );
513 
514 	if( !transition )
515 	{
516 		mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
517 		transition = mlt_factory_transition( profile, "composite", NULL );
518 
519 		// Save the transition for future use.
520 		mlt_properties_set_data( producer_properties, "_transition", transition, 0, ( mlt_destructor )mlt_transition_close, NULL );
521 
522 		// Configure the transition.
523 		mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES( transition );
524 		mlt_properties_set( transition_properties, "geometry", "0%/0%:100%x100%:100" );
525 		mlt_properties_set( transition_properties, "halign", "center" );
526 		mlt_properties_set( transition_properties, "valign", "middle" );
527 	}
528 
529 	if( transition && bg_frame && text_frame )
530 	{
531 		// Apply the transition.
532 		mlt_transition_process( transition, bg_frame, text_frame );
533 	}
534 }
535 
producer_get_image(mlt_frame frame,uint8_t ** image,mlt_image_format * format,int * width,int * height,int writable)536 static int producer_get_image( mlt_frame frame, uint8_t** image, mlt_image_format* format, int* width, int* height, int writable )
537 {
538 	mlt_producer producer = mlt_frame_pop_service( frame );
539 	mlt_frame bg_frame = NULL;
540 	mlt_frame text_frame = NULL;
541 	int error = 1;
542 	int size = 0;
543 	char* background = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "background" );
544 	time_info info;
545 
546 	mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
547 
548 	get_time_info( producer, frame, &info );
549 
550 	bg_frame = get_background_frame( producer );
551 	if( !strcmp( background, "clock" ) )
552 	{
553 		add_clock_to_frame( producer, bg_frame, &info );
554 	}
555 	text_frame = get_text_frame( producer, &info );
556 	add_text_to_bg( producer, bg_frame, text_frame );
557 
558 	if( bg_frame )
559 	{
560 		// Get the image from the background frame.
561 		error = mlt_frame_get_image( bg_frame, image, format, width, height, writable );
562 		size = mlt_image_format_size( *format, *width, *height, NULL );
563 		// Detach the image from the bg_frame so it is not released.
564 		mlt_frame_set_image( bg_frame, *image, size, NULL );
565 		// Attach the image to the input frame.
566 		mlt_frame_set_image( frame, *image, size, mlt_pool_release );
567 		mlt_frame_close( bg_frame );
568 	}
569 
570 	if( text_frame )
571 	{
572 		mlt_frame_close( text_frame );
573 	}
574 
575 	mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
576 
577 	return error;
578 }
579 
producer_get_frame(mlt_producer producer,mlt_frame_ptr frame,int index)580 static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
581 {
582 	// Generate a frame
583 	*frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
584 	mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
585 
586 	if ( *frame != NULL )
587 	{
588 		// Obtain properties of frame
589 		mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame );
590 
591 		// Update time code on the frame
592 		mlt_frame_set_position( *frame, mlt_producer_frame( producer ) );
593 
594 		mlt_properties_set_int( frame_properties, "progressive", 1 );
595 		mlt_properties_set_double( frame_properties, "aspect_ratio", mlt_profile_sar( profile ) );
596 		mlt_properties_set_int( frame_properties, "meta.media.width", profile->width );
597 		mlt_properties_set_int( frame_properties, "meta.media.height", profile->height );
598 
599 		// Configure callbacks
600 		mlt_frame_push_service( *frame, producer );
601 		mlt_frame_push_get_image( *frame, producer_get_image );
602 		mlt_frame_push_audio( *frame, producer );
603 		mlt_frame_push_audio( *frame, producer_get_audio );
604 	}
605 
606 	// Calculate the next time code
607 	mlt_producer_prepare_next( producer );
608 
609 	return 0;
610 }
611 
producer_close(mlt_producer this)612 static void producer_close( mlt_producer this )
613 {
614 	this->close = NULL;
615 	mlt_producer_close( this );
616 	free( this );
617 }
618 
619 /** Initialize.
620 */
621 
producer_count_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)622 mlt_producer producer_count_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
623 {
624 	// Create a new producer object
625 	mlt_producer producer = mlt_producer_new( profile );
626 
627 	// Initialize the producer
628 	if ( producer )
629 	{
630 		mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
631 		mlt_properties_set( properties, "direction", "down" );
632 		mlt_properties_set( properties, "style", "seconds+1" );
633 		mlt_properties_set( properties, "sound", "none" );
634 		mlt_properties_set( properties, "background", "clock" );
635 		mlt_properties_set( properties, "drop", "0" );
636 
637 		// Callback registration
638 		producer->get_frame = producer_get_frame;
639 		producer->close = ( mlt_destructor )producer_close;
640 	}
641 
642 	return producer;
643 }
644