]> git.sesse.net Git - mlt/commitdiff
Add new count producer
authorBrian Matherly <pez4brian@yahoo.com>
Fri, 5 Apr 2013 02:08:23 +0000 (21:08 -0500)
committerBrian Matherly <pez4brian@yahoo.com>
Fri, 5 Apr 2013 02:08:23 +0000 (21:08 -0500)
src/modules/gtk2/Makefile
src/modules/gtk2/factory.c
src/modules/gtk2/producer_count.c [new file with mode: 0644]
src/modules/gtk2/producer_count.yml [new file with mode: 0644]

index 5712644f6d1f35680cadf856ba115d251542bc19..a76fc9a7586c021eb636610d6dcb6d05a96144f1 100644 (file)
@@ -34,6 +34,7 @@ endif
 
 ifdef USE_PANGO
 OBJS += producer_pango.o
+OBJS += producer_count.o
 OBJS += filter_dynamictext.o
 CFLAGS += `pkg-config $(PKGCONFIG_PREFIX) --cflags pangoft2`
 LDFLAGS += `pkg-config $(PKGCONFIG_PREFIX) --libs pangoft2`
index 5833449eeb3adaab14698a7cba54fffc348a9816..08e4f48aa619bc3fccaab8e5a558fcf71d342f37 100644 (file)
@@ -34,6 +34,7 @@ extern mlt_consumer consumer_gtk2_preview_init( mlt_profile profile, void *widge
 
 #ifdef USE_PANGO
 extern mlt_producer producer_pango_init( const char *filename );
+extern mlt_producer producer_count_init( const char *arg );
 extern mlt_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 #endif
 
@@ -59,6 +60,8 @@ void *create_service( mlt_profile profile, mlt_service_type type, const char *id
 #ifdef USE_PANGO
        if ( !strcmp( id, "pango" ) )
                return producer_pango_init( arg );
+       if ( !strcmp( id, "count" ) )
+               return producer_count_init( arg );
        if ( !strcmp( id, "dynamictext" ) )
                return filter_dynamictext_init( profile, type, id, arg );
 #endif
@@ -85,12 +88,14 @@ static mlt_properties metadata( mlt_service_type type, const char *id, void *dat
 
 MLT_REPOSITORY
 {
+       MLT_REGISTER( producer_type, "count", create_service );
        MLT_REGISTER( filter_type, "dynamictext", create_service );
        MLT_REGISTER( consumer_type, "gtk2_preview", create_service );
        MLT_REGISTER( filter_type, "gtkrescale", create_service );
        MLT_REGISTER( producer_type, "pango", create_service );
        MLT_REGISTER( producer_type, "pixbuf", create_service );
 
+       MLT_REGISTER_METADATA( producer_type, "count", metadata, "producer_count.yml" );
        MLT_REGISTER_METADATA( filter_type, "dynamictext", metadata, "filter_dynamictext.yml" );
        MLT_REGISTER_METADATA( consumer_type, "gtk2_preview", metadata, "consumer_gtk2_preview.yml" );
        MLT_REGISTER_METADATA( filter_type, "gtkrescale", metadata, "filter_rescale.yml" );
diff --git a/src/modules/gtk2/producer_count.c b/src/modules/gtk2/producer_count.c
new file mode 100644 (file)
index 0000000..7e8c2c9
--- /dev/null
@@ -0,0 +1,619 @@
+/*
+ * producer_count.c -- counting producer
+ * Copyright (C) 2013 Brian Matherly
+ * Author: Brian Matherly <pez4brian@yahoo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <framework/mlt.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Private Constants */
+#define MAX_TEXT_LEN        512
+#define LINE_PIXEL_VALUE    0x00
+#define RING_PIXEL_VALUE    0xff
+#define CLOCK_PIXEL_VALUE   0x50
+#define FRAME_BACKGROUND_COLOR   "0xd0d0d0ff"
+#define TEXT_BACKGROUND_COLOR    "0x00000000"
+#define TEXT_FOREGROUND_COLOR    "0x000000ff"
+// Ratio of graphic elements relative to image size
+#define LINE_WIDTH_RATIO     1
+#define OUTER_RING_RATIO    90
+#define INNER_RING_RATIO    80
+#define TEXT_SIZE_RATIO     70
+
+static inline void mix_pixel( uint8_t* image, int width, int x, int y, int value, float mix )
+{
+       uint8_t* p = image + (( y * width ) + x ) * 4;
+
+       if( mix != 1.0 )
+       {
+               value = ((float)value * mix) + ((float)*p * (1.0 - mix));
+       }
+
+       *p = value;
+       p++;
+       *p = value;
+       p++;
+       *p = value;
+}
+
+/** Fill an audio buffer with 1kHz samples.
+*/
+
+static void fill_beep( mlt_properties producer_properties, float* buffer, int frequency, int channels, int samples )
+{
+       int s = 0;
+       int c = 0;
+
+       for( s = 0; s < samples; s++ )
+       {
+               float f = 1000.0;
+               float t = (float)s/(float)frequency;
+               float value = sin( 2*M_PI*f*t );
+
+               for( c = 0; c < channels; c++ )
+               {
+                       float* sample_ptr = buffer + (c * samples) + s;
+                       *sample_ptr = value;
+               }
+       }
+}
+
+static int producer_get_audio( mlt_frame frame, int16_t** buffer, mlt_audio_format* format, int* frequency, int* channels, int* samples )
+{
+       mlt_producer producer = mlt_properties_get_data( MLT_FRAME_PROPERTIES( frame ), "_producer_count", NULL );
+       mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
+       char* sound = mlt_properties_get( producer_properties, "sound" );
+       double fps = mlt_producer_get_fps( producer );
+       int position = mlt_frame_get_position( frame );
+       int size = 0;
+       int do_beep = 0;
+
+       if( fps == 0 ) fps = 25;
+
+       // Correct the returns if necessary
+       *format = mlt_audio_float;
+       *frequency = *frequency <= 0 ? 48000 : *frequency;
+       *channels = *channels <= 0 ? 2 : *channels;
+       *samples = *samples <= 0 ? mlt_sample_calculator( fps, *frequency, position ) : *samples;
+
+       // Allocate the buffer
+       size = *samples * *channels * sizeof( float );
+       *buffer = mlt_pool_alloc( size );
+
+       // Determine if this should be a tone or silence.
+       if( strcmp( sound, "none") )
+       {
+               if( !strcmp( sound, "2pop" ) )
+               {
+                       int out = mlt_properties_get_int( producer_properties, "out" );
+                       int frames = out - position;
+
+                       if( frames == lrint( fps * 2 ) )
+                       {
+                               do_beep = 1;
+                       }
+               }
+               else if( !strcmp( sound, "frame0" ) )
+               {
+                       int frames = position;
+
+                       // Apply the direction
+                       char* direction = mlt_properties_get( producer_properties, "direction" );
+                       if( !strcmp( direction, "down" ) )
+                       {
+                               int out = mlt_properties_get_int( producer_properties, "out" );
+                               frames = out - position;
+                       }
+
+                       frames = position % lrint( fps );
+                       if( frames == 0 )
+                       {
+                               do_beep = 1;
+                       }
+               }
+       }
+
+       if( do_beep )
+       {
+               fill_beep( producer_properties, (float*)*buffer, *frequency, *channels, *samples );
+       }
+       else
+       {
+               // Fill silence.
+               memset( *buffer, 0, size );
+       }
+
+       // Set the buffer for destruction
+       mlt_frame_set_audio( frame, *buffer, *format, size, mlt_pool_release );
+       return 0;
+}
+
+static mlt_frame get_background_frame( mlt_producer producer )
+{
+       mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
+       mlt_frame bg_frame = NULL;
+       mlt_producer color_producer = mlt_properties_get_data( producer_properties, "_color_producer", NULL );
+
+       if( !color_producer )
+       {
+               mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
+               color_producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "colour:" );
+               mlt_properties_set_data( producer_properties, "_color_producer", color_producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
+
+               mlt_properties color_properties = MLT_PRODUCER_PROPERTIES( color_producer );
+               mlt_properties_set( color_properties, "colour", FRAME_BACKGROUND_COLOR );
+       }
+
+       if( color_producer )
+       {
+               mlt_producer_seek( color_producer, 0 );
+               mlt_service_get_frame( MLT_PRODUCER_SERVICE( color_producer ), &bg_frame, 0 );
+       }
+
+       return bg_frame;
+}
+
+static mlt_frame get_text_frame( mlt_producer producer, mlt_position position )
+{
+       mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
+       mlt_producer pango_producer = mlt_properties_get_data( producer_properties, "_pango_producer", NULL );
+       mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
+       mlt_frame text_frame = NULL;
+
+       if( !pango_producer )
+       {
+               pango_producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "pango:" );
+
+               // Save the producer for future use.
+               mlt_properties_set_data( producer_properties, "_pango_producer", pango_producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
+
+               // Calculate the font size.
+               char font_size[MAX_TEXT_LEN];
+               snprintf( font_size, MAX_TEXT_LEN - 1, "%dpx", profile->height * TEXT_SIZE_RATIO / 100 );
+
+               // Configure the producer.
+               mlt_properties pango_properties = MLT_PRODUCER_PROPERTIES( pango_producer );
+               mlt_properties_set( pango_properties, "size", font_size );
+               mlt_properties_set( pango_properties, "weight", "400" );
+               mlt_properties_set( pango_properties, "fgcolour", TEXT_FOREGROUND_COLOR );
+               mlt_properties_set( pango_properties, "bgcolour", TEXT_BACKGROUND_COLOR );
+               mlt_properties_set( pango_properties, "pad", "0" );
+               mlt_properties_set( pango_properties, "outline", "0" );
+               mlt_properties_set( pango_properties, "align", "center" );
+       }
+
+       if( pango_producer )
+       {
+               mlt_properties pango_properties = MLT_PRODUCER_PROPERTIES( pango_producer );
+               char* direction = mlt_properties_get( producer_properties, "direction" );
+               char* style = mlt_properties_get( producer_properties, "style" );
+               char text[MAX_TEXT_LEN] = "";
+               int fps = lrint(mlt_profile_fps( profile )); if( fps == 0 ) fps = 25;
+
+               // Apply the direction
+               if( !strcmp( direction, "down" ) )
+               {
+                       int out = mlt_properties_get_int( producer_properties, "out" );
+                       position = out - position;
+               }
+
+               // Calculate clock values
+               int seconds = position / fps;
+               int frames = position % fps;
+               int minutes = seconds / 60;
+               seconds = seconds % 60;
+               int hours = minutes / 60;
+               minutes = minutes % 60;
+
+               // Apply the time style
+               if( !strcmp( style, "frames" ) )
+               {
+                       snprintf( text, MAX_TEXT_LEN - 1, "%d", position );
+               }
+               else if( !strcmp( style, "timecode" ) )
+               {
+                       snprintf( text, MAX_TEXT_LEN - 1, "%.2d:%.2d:%.2d:%.2d", hours, minutes, seconds, frames );
+               }
+               else if( !strcmp( style, "clock" ) )
+               {
+                       snprintf( text, MAX_TEXT_LEN - 1, "%.2d:%.2d:%.2d", hours, minutes, seconds );
+               }
+               else if( !strcmp( style, "seconds+1" ) )
+               {
+                       snprintf( text, MAX_TEXT_LEN - 1, "%d", seconds + 1 );
+               }
+               else // seconds
+               {
+                       snprintf( text, MAX_TEXT_LEN - 1, "%d", seconds );
+               }
+
+               mlt_properties_set( pango_properties, "markup", text );
+
+               // Get the frame.
+               mlt_service_get_frame( MLT_PRODUCER_SERVICE( pango_producer ), &text_frame, 0 );
+       }
+
+       return text_frame;
+}
+
+static void draw_ring( uint8_t* image, mlt_profile profile, int radius, int line_width )
+{
+       float sar = mlt_profile_sar( profile );
+       int x_center = profile->width / 2;
+       int y_center = profile->height / 2;
+       int max_radius = radius + line_width;
+       int a = max_radius + 1;
+       int b = 0;
+
+       line_width += 1; // Compensate for aliasing.
+
+       // Scan through each pixel in one quadrant of the circle.
+       while( a-- )
+       {
+               b = ( max_radius / sar ) + 1.0;
+               while( b-- )
+               {
+                       // Use Pythagorean theorem to determine the distance from this pixel to the center.
+                       float a2 = a*a;
+                       float b2 = b*sar*b*sar;
+                       float c = sqrtf( a2 + b2 );
+                       float distance = c - radius;
+
+                       if( distance > 0 && distance < line_width )
+                       {
+                               // This pixel is within the ring.
+                               float mix = 1.0;
+
+                               if( distance < 1.0 )
+                               {
+                                       // Antialias the outside of the ring
+                                       mix = distance;
+                               }
+                               else if( (float)line_width - distance < 1.0 )
+                               {
+                                       // Antialias the inside of the ring
+                                       mix = (float)line_width - distance;
+                               }
+
+                               // Apply this value to all 4 quadrants of the circle.
+                               mix_pixel( image, profile->width, x_center + b, y_center - a, RING_PIXEL_VALUE, mix );
+                               mix_pixel( image, profile->width, x_center - b, y_center - a, RING_PIXEL_VALUE, mix );
+                               mix_pixel( image, profile->width, x_center + b, y_center + a, RING_PIXEL_VALUE, mix );
+                               mix_pixel( image, profile->width, x_center - b, y_center + a, RING_PIXEL_VALUE, mix );
+                       }
+               }
+       }
+}
+
+static void draw_cross( uint8_t* image, mlt_profile profile, int line_width )
+{
+       int x = 0;
+       int y = 0;
+       int i = 0;
+
+       // Draw a horizontal line
+       i = line_width;
+       while( i-- )
+       {
+               y = ( profile->height - line_width ) / 2 + i;
+               x = profile->width - 1;
+               while( x-- )
+               {
+                       mix_pixel( image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0 );
+               }
+       }
+
+       // Draw a vertical line
+       line_width = lrint((float)line_width * mlt_profile_sar( profile ));
+       i = line_width;
+       while( i-- )
+       {
+               x = ( profile->width - line_width ) / 2 + i;
+               y = profile->height - 1;
+               while( y-- )
+               {
+                       mix_pixel( image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0 );
+               }
+       }
+}
+
+static void draw_clock( uint8_t* image, mlt_profile profile, int angle, int line_width )
+{
+       float sar = mlt_profile_sar( profile );
+       int q = 0;
+       int x_center = profile->width / 2;
+       int y_center = profile->height / 2;
+
+       line_width += 1; // Compensate for aliasing.
+
+       // Look at each quadrant of the frame to see what should be done.
+       for( q = 1; q <= 4; q++ )
+       {
+               int max_angle = q * 90;
+               int x_sign = ( q == 1 || q == 2 ) ? 1 : -1;
+               int y_sign = ( q == 1 || q == 4 ) ? 1 : -1;
+               int x_start = x_center * x_sign;
+               int y_start = y_center * y_sign;
+
+               // Compensate for rounding error of even lengths
+               // (there is no "middle" pixel so everything is offset).
+               if( x_sign == 1 && profile->width % 2 == 0 ) x_start--;
+               if( y_sign == -1 && profile->height % 2 == 0 ) y_start++;
+
+               if( angle >= max_angle )
+               {
+                       // This quadrant is completely behind the clock hand. Fill it in.
+                       int dx = x_start + x_sign;
+                       while( dx )
+                       {
+                               dx -= x_sign;
+                               int dy = y_start + y_sign;
+                               while( dy )
+                               {
+                                       dy -= y_sign;
+                                       mix_pixel( image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0 );
+                               }
+                       }
+               }
+               else if ( max_angle - angle < 90 )
+               {
+                       // This quadrant is partially filled
+                       // Calculate a point (vx,vy) that lies on the line created by the angle from 0,0.
+                       int vx = 0;
+                       int vy = y_start;
+                       float lv = 0;
+
+                       // Assume maximum y and calculate the corresponding x value
+                       // for a point at the other end of this line.
+                       if( x_sign * y_sign == 1 )
+                       {
+                               vx = x_sign * sar * y_center / tan( ( max_angle - angle ) * M_PI / 180.0 );
+                       }
+                       else
+                       {
+                               vx = x_sign * sar * y_center * tan( ( max_angle - angle ) * M_PI / 180.0 );
+                       }
+
+                       // Calculate the length of the line defined by vx,vy
+                       lv = sqrtf((float)(vx*vx)*sar*sar + (float)vy*vy);
+
+                       // Scan through each pixel in the quadrant counting up/down to 0,0.
+                       int dx = x_start + x_sign;
+                       while( dx )
+                       {
+                               dx -= x_sign;
+                               int dy = y_start + y_sign;
+                               while( dy )
+                               {
+                                       dy -= y_sign;
+                                       // Calculate the cross product to determine which side of
+                                       // the line this pixel lies on.
+                                       int xp = vx * (vy - dy) - vy * (vx - dx);
+                                       xp = xp * -1; // Easier to work with positive. Positive number means "behind" the line.
+                                       if( xp > 0 )
+                                       {
+                                               // This pixel is behind the clock hand and should be filled in.
+                                               // Calculate the distance from the pixel to the line to determine
+                                               // if it is part of the clock hand.
+                                               float distance = (float)xp / lv;
+                                               int val = CLOCK_PIXEL_VALUE;
+                                               float mix = 1.0;
+
+                                               if( distance < line_width )
+                                               {
+                                                       // This pixel makes up the clock hand.
+                                                       val = LINE_PIXEL_VALUE;
+
+                                                       if( distance < 1.0 )
+                                                       {
+                                                               // Antialias the outside of the clock hand
+                                                               mix = distance;
+                                                       }
+                                                       else if( (float)line_width - distance < 1.0 )
+                                                       {
+                                                               // Antialias the inside of the clock hand
+                                                               mix_pixel( image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0 );
+                                                               mix = (float)line_width - distance;
+                                                       }
+                                               }
+
+                                               mix_pixel( image, profile->width, x_center + dx, y_center - dy, val, mix );
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+static void add_clock_to_frame( mlt_producer producer, mlt_frame frame, mlt_position position )
+{
+       mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
+       mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
+       uint8_t* image = NULL;
+       mlt_image_format format = mlt_image_rgb24a;
+       int size = 0;
+       int width = profile->width;
+       int height = profile->height;
+       int line_width = LINE_WIDTH_RATIO * (width > height ? height : width) / 100;
+       int fps = lrint(mlt_profile_fps( profile )); if( fps == 0 ) fps = 25;
+       int radius = (width > height ? height : width) / 2;
+       char* direction = mlt_properties_get( producer_properties, "direction" );
+       int clock_angle = 0;
+
+       mlt_frame_get_image( frame, &image, &format, &width, &height, 1 );
+
+       // Calculate the angle for the clock.
+       if( !strcmp( direction, "down" ) )
+       {
+               int out = mlt_producer_get_out( producer );
+               int frames = fps - (( out - position ) % fps);
+               clock_angle = lrint( (frames + 1) * 360 / fps );
+       }
+       else
+       {
+               int frames = position % fps;
+               clock_angle = lrint( (frames + 1) * 360 / fps );
+       }
+
+       draw_clock( image, profile, clock_angle, line_width );
+       draw_cross( image, profile, line_width );
+       draw_ring( image, profile, ( radius * OUTER_RING_RATIO ) / 100, line_width );
+       draw_ring( image, profile, ( radius * INNER_RING_RATIO ) / 100, line_width );
+
+       size = mlt_image_format_size( format, width, height, NULL );
+       mlt_frame_set_image( frame, image, size, mlt_pool_release );
+}
+
+
+static void add_text_to_bg( mlt_producer producer, mlt_frame bg_frame, mlt_frame text_frame )
+{
+       mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
+       mlt_transition transition = mlt_properties_get_data( producer_properties, "_transition", NULL );
+
+       if( !transition )
+       {
+               mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
+               transition = mlt_factory_transition( profile, "composite", NULL );
+
+               // Save the transition for future use.
+               mlt_properties_set_data( producer_properties, "_transition", transition, 0, ( mlt_destructor )mlt_transition_close, NULL );
+
+               // Configure the transition.
+               mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES( transition );
+               mlt_properties_set( transition_properties, "geometry", "0%/0%:100%x100%:100" );
+               mlt_properties_set( transition_properties, "halign", "center" );
+               mlt_properties_set( transition_properties, "valign", "middle" );
+       }
+
+       if( transition && bg_frame && text_frame )
+       {
+               // Apply the transition.
+               mlt_transition_process( transition, bg_frame, text_frame );
+       }
+}
+
+static int producer_get_image( mlt_frame frame, uint8_t** image, mlt_image_format* format, int* width, int* height, int writable )
+{
+       mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
+       mlt_position position = mlt_frame_original_position( frame );
+       mlt_producer producer = mlt_properties_get_data( properties, "_producer_count", NULL );
+       mlt_frame bg_frame = NULL;
+       mlt_frame text_frame = NULL;
+       int error = 1;
+       int size = 0;
+       char* background = mlt_properties_get( MLT_PRODUCER_PROPERTIES( producer ), "background" );
+
+       mlt_service_lock( MLT_PRODUCER_SERVICE( producer ) );
+
+       bg_frame = get_background_frame( producer );
+       if( !strcmp( background, "clock" ) )
+       {
+               add_clock_to_frame( producer, bg_frame, position );
+       }
+       text_frame = get_text_frame( producer, position );
+       add_text_to_bg( producer, bg_frame, text_frame );
+
+       if( bg_frame )
+       {
+               // Get the image from the background frame.
+               error = mlt_frame_get_image( bg_frame, image, format, width, height, writable );
+               size = mlt_image_format_size( *format, *width, *height, NULL );
+               // Detach the image from the bg_frame so it is not released.
+               mlt_frame_set_image( bg_frame, *image, size, NULL );
+               // Attach the image to the input frame.
+               mlt_frame_set_image( frame, *image, size, mlt_pool_release );
+               mlt_frame_close( bg_frame );
+       }
+
+       if( text_frame )
+       {
+               mlt_frame_close( text_frame );
+       }
+
+       mlt_service_unlock( MLT_PRODUCER_SERVICE( producer ) );
+
+       return error;
+}
+
+static int producer_get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
+{
+       // Generate a frame
+       *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) );
+       mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) );
+
+       if ( *frame != NULL )
+       {
+               // Obtain properties of frame
+               mlt_properties frame_properties = MLT_FRAME_PROPERTIES( *frame );
+
+               // Save the producer to be used later
+               mlt_properties_set_data( frame_properties, "_producer_count", producer, 0, NULL, NULL );
+
+               // Update time code on the frame
+               mlt_frame_set_position( *frame, mlt_producer_frame( producer ) );
+
+               mlt_properties_set_int( frame_properties, "progressive", 1 );
+               mlt_properties_set_double( frame_properties, "aspect_ratio", mlt_profile_sar( profile ) );
+               mlt_properties_set_int( frame_properties, "meta.media.width", profile->width );
+               mlt_properties_set_int( frame_properties, "meta.media.height", profile->height );
+
+               // Configure callbacks
+               mlt_frame_push_get_image( *frame, producer_get_image );
+               mlt_frame_push_audio( *frame, producer_get_audio );
+       }
+
+       // Calculate the next time code
+       mlt_producer_prepare_next( producer );
+
+       return 0;
+}
+
+static void producer_close( mlt_producer this )
+{
+       this->close = NULL;
+       mlt_producer_close( this );
+       free( this );
+}
+
+/** Initialize.
+*/
+
+mlt_producer producer_count_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
+{
+       // Create a new producer object
+       mlt_producer producer = mlt_producer_new( profile );
+
+       // Initialize the producer
+       if ( producer )
+       {
+               mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
+               mlt_properties_set( properties, "direction", "down" );
+               mlt_properties_set( properties, "style", "seconds+1" );
+               mlt_properties_set( properties, "sound", "none" );
+               mlt_properties_set( properties, "background", "clock" );
+
+               // Callback registration
+               producer->get_frame = producer_get_frame;
+               producer->close = ( mlt_destructor )producer_close;
+       }
+
+       return producer;
+}
diff --git a/src/modules/gtk2/producer_count.yml b/src/modules/gtk2/producer_count.yml
new file mode 100644 (file)
index 0000000..07e58f3
--- /dev/null
@@ -0,0 +1,72 @@
+schema_version: 0.1
+type: producer
+identifier: count
+title: Count
+version: 1
+copyright: Brian Matherly
+creator: Brian Matherly
+license: LGPLv2.1
+language: en
+tags:
+  - Audio
+  - Video
+description: >
+  Generate frames with a counter and synchronized tone. 
+  The counter can go up or down.
+parameters:
+  - identifier: direction
+    title: Count Direction
+    description: Whether to count up or down.
+    type: string
+    default: down
+    values:
+      - up
+      - down
+    mutable: yes
+    widget: combo
+  - identifier: style
+    title: Counter Style
+    description: |
+      The style of the counter. 
+        * seconds    - seconds counting up from or down to 0
+        * seconds+1  - seconds counting up from or down to 1
+        * frames     - frames
+        * timecode   - timecode in the format HH:MM:SS:FF
+        * clock      - clock in the format HH:MM:SS
+    type: string
+    default: seconds+1
+    values:
+      - seconds
+      - frames
+      - timecode
+      - clock
+    mutable: yes
+    widget: combo
+  - identifier: sound
+    title: Sound
+    description:  |
+      The sound to be produced. 
+        * silent - No sound
+        * 2pop  - A 1kHz beep exactly two seconds before the out point
+        * frame0 - A 1kHz beep at frame 0 of every second
+    type: string
+    default: silent
+    values:
+      - none
+      - 2pop
+      - frame0
+    mutable: yes
+    widget: combo
+- identifier: background
+    title: Background
+    description:  |
+      The background style. 
+        * none - No background
+        * clock  - Film style clock animation
+    type: string
+    default: clock
+    values:
+      - none
+      - clock
+    mutable: yes
+    widget: combo
\ No newline at end of file