From 7f5034480306a8d55e164127f903a27640048373 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sun, 28 Aug 2011 20:43:29 -0500 Subject: [PATCH] Add filter_dynamictext. --- src/modules/gtk2/Makefile | 1 + src/modules/gtk2/factory.c | 5 + src/modules/gtk2/filter_dynamictext.c | 253 ++++++++++++++++++++++++ src/modules/gtk2/filter_dynamictext.yml | 72 +++++++ 4 files changed, 331 insertions(+) create mode 100644 src/modules/gtk2/filter_dynamictext.c create mode 100644 src/modules/gtk2/filter_dynamictext.yml diff --git a/src/modules/gtk2/Makefile b/src/modules/gtk2/Makefile index c5b3e111..b286dbe4 100644 --- a/src/modules/gtk2/Makefile +++ b/src/modules/gtk2/Makefile @@ -34,6 +34,7 @@ endif ifdef USE_PANGO OBJS += producer_pango.o +OBJS += filter_dynamictext.o CFLAGS += `pkg-config pangoft2 --cflags` LDFLAGS += `pkg-config pangoft2 --libs` ifeq ($(targetos),Darwin) diff --git a/src/modules/gtk2/factory.c b/src/modules/gtk2/factory.c index fa98b22e..5833449e 100644 --- a/src/modules/gtk2/factory.c +++ b/src/modules/gtk2/factory.c @@ -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_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); #endif static void initialise( ) @@ -58,6 +59,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, "dynamictext" ) ) + return filter_dynamictext_init( profile, type, id, arg ); #endif #ifdef USE_PIXBUF @@ -82,11 +85,13 @@ static mlt_properties metadata( mlt_service_type type, const char *id, void *dat MLT_REPOSITORY { + 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( 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" ); MLT_REGISTER_METADATA( producer_type, "pango", metadata, "producer_pango.yml" ); diff --git a/src/modules/gtk2/filter_dynamictext.c b/src/modules/gtk2/filter_dynamictext.c new file mode 100644 index 00000000..a62014c8 --- /dev/null +++ b/src/modules/gtk2/filter_dynamictext.c @@ -0,0 +1,253 @@ +/* + * filter_dynamictext.c -- dynamic text overlay filter + * Copyright (C) 2011 Ushodaya Enterprises Limited + * Author: Brian Matherly + * + * 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 +#include +#include +#include +#include // for stat() +#include // for stat() +#include // for stat() +#include // for strftime() and gtime() + +#define MAX_TEXT_LEN 512 + + +static void get_timecode_str( mlt_filter filter, mlt_frame frame, char* text ) +{ + int frames = mlt_frame_get_position( frame ); + double fps = mlt_profile_fps( mlt_service_profile( MLT_FILTER_SERVICE( filter ) ) ); + char tc[12] = ""; + if (fps == 0) + { + strncat( text, "-", MAX_TEXT_LEN - strlen(text) - 1 ); + } + else + { + int seconds = frames / fps; + frames = frames % lrint( fps ); + int minutes = seconds / 60; + seconds = seconds % 60; + int hours = minutes / 60; + minutes = minutes % 60; + sprintf(tc, "%.2d:%.2d:%.2d:%.2d", hours, minutes, seconds, frames); + strncat( text, tc, MAX_TEXT_LEN - strlen(text) - 1 ); + } +} + +static void get_frame_str( mlt_filter filter, mlt_frame frame, char* text ) +{ + int pos = mlt_frame_get_position( frame ); + char s[12]; + snprintf( s, sizeof(s) - 1, "%d", pos ); + strncat( text, s, MAX_TEXT_LEN - strlen(text) - 1 ); +} + +static void get_filedate_str( mlt_filter filter, mlt_frame frame, char* text ) +{ + mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer( frame )); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); + char* filename = mlt_properties_get( producer_properties, "resource"); + struct stat file_info; + + if( !stat(filename, &file_info)) + { + struct tm* time_info = gmtime( &(file_info.st_mtime) ); + char date[11] = ""; + strftime( date, 11, "%Y/%m/%d", time_info ); + strncat( text, date, MAX_TEXT_LEN - strlen(text) - 1); + } +} + +/** Perform substitution for keywords that are enclosed in "# #". +*/ + +static void substitute_keywords(mlt_filter filter, char* result, char* value, mlt_frame frame) +{ + char value_copy[MAX_TEXT_LEN] = ""; + char* keyword = NULL; + int ct = 0; + int fromStart = 0; + + // Need to copy the value because strtok will modify it. + strncpy(value_copy, value, 512); + keyword = strtok( value_copy, "#" ); + fromStart = ( value_copy[0] == '#' ) ? 1 : 0; + + while ( keyword ) + { + if ( ct % 2 == fromStart ) + { + // backslash in front of # suppresses substitution + if ( keyword[ strlen( keyword ) -1 ] == '\\' ) + { + // keep characters except backslash + strncat( result, keyword, strlen( keyword ) -1 ); + strcat( result, "#" ); + ct++; + } + else + { + strcat( result, keyword ); + } + } + else if ( !strcmp( keyword, "timecode" ) ) + { + get_timecode_str(filter, frame, result); + } + else if ( !strcmp( keyword, "frame" ) ) + { + get_frame_str(filter, frame, result); + } + else if ( !strcmp( keyword, "filedate" ) ) + { + get_filedate_str(filter, frame, result); + } + else if ( !strcmp( keyword, "resource" ) ) + { + // special case: replace #resource# with cut parent resource name + mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer( frame )); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); + strncat( result, mlt_properties_get( producer_properties, "resource"), MAX_TEXT_LEN - strlen(result) - 1 ); + } + else + { + // replace keyword with property value from this frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame ); + char *frame_value = mlt_properties_get( frame_properties, keyword ); + if( frame_value ) + { + strncat( result, frame_value, MAX_TEXT_LEN - strlen(result) - 1 ); + } + } + keyword = strtok( NULL, "#" ); + ct++; + } +} + +static void apply_filter(mlt_filter filter, mlt_frame frame ) +{ + mlt_properties my_properties = MLT_FILTER_PROPERTIES( filter ); + mlt_filter watermark = mlt_properties_get_data( my_properties, "_watermark", NULL ); + mlt_properties watermark_properties = MLT_FILTER_PROPERTIES( watermark ); + char* dynamic_text = mlt_properties_get( my_properties, "argument"); + + // Check for keywords in dynamic text + if ( dynamic_text ) + { + // Apply keyword substitution before passing the text to the filter. + char result[512] = ""; + substitute_keywords( filter, result, dynamic_text, frame ); + mlt_properties_set( watermark_properties, "producer.markup", (char*) result ); + } + + // Pass the properties to the watermark filter composite transition + mlt_properties_set( watermark_properties, "composite.geometry", mlt_properties_get( my_properties, "geometry" ) ); + + // Pass the properties to the watermark filter pango producer + mlt_properties_set( watermark_properties, "producer.font", mlt_properties_get( my_properties, "font" ) ); + mlt_properties_set( watermark_properties, "producer.weight", mlt_properties_get( my_properties, "weight" ) ); + mlt_properties_set( watermark_properties, "producer.fgcolour", mlt_properties_get( my_properties, "fgcolour" ) ); + mlt_properties_set( watermark_properties, "producer.bgcolour", mlt_properties_get( my_properties, "bgcolour" ) ); + + // Process the filter + mlt_filter_process( watermark, frame ); +} + +/** Get the image. +*/ + +static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable ) +{ + // Pop the service + mlt_filter filter = mlt_frame_pop_service( frame ); + + mlt_service_lock( MLT_FILTER_SERVICE( filter ) ); + + apply_filter(filter, frame); + + mlt_service_unlock( MLT_FILTER_SERVICE( filter ) ); + + // Need to get the image + return mlt_frame_get_image( frame, image, format, width, height, 1 ); +} + + +/** Filter processing. +*/ + +static mlt_frame filter_process( mlt_filter filter, mlt_frame frame ) +{ + // Push the filter + mlt_frame_push_service( frame, filter ); + + // Register the get image method + mlt_frame_push_get_image( frame, filter_get_image ); + + // Return the frame + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, void *arg ) +{ + // Create the filter + mlt_filter filter = mlt_filter_new( ); + mlt_filter watermark = mlt_factory_filter( profile, "watermark", "pango:" ); + + // Initialise it + if ( filter && watermark ) + { + // Get the properties + mlt_properties properties = MLT_FILTER_PROPERTIES( filter ); + + // Store the watermark filter for future use + mlt_properties_set_data( properties, "_watermark", watermark, 0, ( mlt_destructor )mlt_filter_close, NULL ); + + // Assign default values + mlt_properties_set( properties, "argument", arg ? arg: "#timecode#" ); + mlt_properties_set( properties, "geometry", "0%/0%:100%x100%:100" ); + mlt_properties_set( properties, "font", "Sans 48" ); + mlt_properties_set( properties, "weight", "400" ); + mlt_properties_set( properties, "fgcolour", "0x000000ff" ); + mlt_properties_set( properties, "bgcolour", "0x00000020" ); + + // Specify the processing method + filter->process = filter_process; + } + else // filter or watermark failed for some reason + { + if( filter ) + { + mlt_filter_close(filter); + } + + if( watermark ) + { + mlt_filter_close(watermark); + } + + filter = NULL; + } + + return filter; +} diff --git a/src/modules/gtk2/filter_dynamictext.yml b/src/modules/gtk2/filter_dynamictext.yml new file mode 100644 index 00000000..9356f60c --- /dev/null +++ b/src/modules/gtk2/filter_dynamictext.yml @@ -0,0 +1,72 @@ +schema_version: 0.1 +type: filter +identifier: dynamictext +title: Dynamic text +version: 1 +copyright: Ushodaya Enterprises Limited +creator: Brian Matherly +license: LGPLv2.1 +language: en +tags: + - Video +description: Overlay dynamic text onto the video +notes: > + The dynamic text filter will search for keywords in the text to be overlayed + and will replace those keywords on a frame-by-frame basis. + +parameters: + - identifier: argument + title: Dynamic text + type: string + description: | + The text to overlay. May include keywords enclosed in "#". + Keywords include: + * #timecode# - timecode of the frame (based on framerate and position) + * #frame# - frame number of the frame + * #filedate# - modification date of the file + Keywords may also be any frame property (e.g. #meta.media.0.codec.frame_rate#) + The # may be escaped with "\". + required: yes + readonly: no + default: > #trick to escape "#" character + #timecode# + widget: text + - identifier: geometry + title: Geometry + type: geometry + description: A set of X/Y coordinates by which to adjust the text. + default: 0%/0%:100%x100%:100 + - identifier: font + title: Font + type: string + description: The typeface to use + default: Sans 48 + readonly: no + mutable: yes + widget: font + - identifier: weight + title: Font weight + type: integer + description: The weight of the font. + minimum: 100 + maximum: 900 + default: 400 + readonly: no + mutable: yes + widget: spinner + - identifier: fgcolour + title: Foreground color + type: string + description: an RGBA colour specification of the text (i.e. 0xrrggbbaa) + default: 0x000000ff + readonly: no + mutable: yes + widget: color + - identifier: bgcolour + title: Background color + type: string + description: an RGBA colour of the background rectangle (i.e. 0xrrggbbaa) + default: 0x00000020 + readonly: no + mutable: yes + widget: color -- 2.39.2