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