]> git.sesse.net Git - mlt/blob - src/modules/gtk2/filter_dynamictext.c
7149dd984feb3f3f28d6d8a89a6a8c83f5cc0e6e
[mlt] / src / modules / gtk2 / filter_dynamictext.c
1 /*
2  * filter_dynamictext.c -- dynamic text overlay filter
3  * Copyright (C) 2011 Ushodaya Enterprises Limited
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 <stdlib.h>
23 #include <string.h>
24 #include <math.h>
25 #include <sys/types.h> // for stat()
26 #include <sys/stat.h>  // for stat()
27 #include <unistd.h>    // for stat()
28 #include <time.h>      // for strftime() and gtime()
29
30 #define MAX_TEXT_LEN 512
31
32 /** Get the next token and indicate whether it is enclosed in "# #".
33 */
34 static int get_next_token(char* str, int* pos, char* token, int* is_keyword)
35 {
36         int token_pos = 0;
37         int str_len = strlen( str );
38
39         if( (*pos) >= str_len || str[*pos] == '\0' )
40         {
41                 return 0;
42         }
43
44         if( str[*pos] == '#' )
45         {
46                 *is_keyword = 1;
47                 (*pos)++;
48         }
49         else
50         {
51                 *is_keyword = 0;
52         }
53
54         while( *pos < str_len && token_pos < MAX_TEXT_LEN - 1)
55         {
56                 if( str[*pos] == '\\' && str[(*pos) + 1] == '#' )
57                 {
58                         // Escape Sequence - "#" preceeded by "\" - copy the # into the token.
59                         token[token_pos] = '#';
60                         token_pos++;
61                         (*pos)++; // skip "\"
62                         (*pos)++; // skip "#"
63                 }
64                 else if( str[*pos] == '#' )
65                 {
66                         if( *is_keyword )
67                         {
68                                 // Found the end of the keyword
69                                 (*pos)++;
70                         }
71                         break;
72                 }
73                 else
74                 {
75                         token[token_pos] = str[*pos];
76                         token_pos++;
77                         (*pos)++;
78                 }
79         }
80
81         token[token_pos] = '\0';
82
83         return 1;
84 }
85
86 static void get_timecode_str( mlt_filter filter, mlt_frame frame, char* text )
87 {
88         int frames = mlt_frame_get_position( frame );
89         double fps = mlt_profile_fps( mlt_service_profile( MLT_FILTER_SERVICE( filter ) ) );
90         char tc[12] = "";
91         if (fps == 0)
92         {
93                 strncat( text, "-", MAX_TEXT_LEN - strlen( text ) - 1 );
94         }
95         else
96         {
97                 int seconds = frames / fps;
98                 frames = frames % lrint( fps );
99                 int minutes = seconds / 60;
100                 seconds = seconds % 60;
101                 int hours = minutes / 60;
102                 minutes = minutes % 60;
103                 sprintf(tc, "%.2d:%.2d:%.2d:%.2d", hours, minutes, seconds, frames);
104                 strncat( text, tc, MAX_TEXT_LEN - strlen( text ) - 1 );
105         }
106 }
107
108 static void get_frame_str( mlt_filter filter, mlt_frame frame, char* text )
109 {
110         int pos = mlt_frame_get_position( frame );
111         char s[12];
112         snprintf( s, sizeof( s ) - 1, "%d", pos );
113         strncat( text, s, MAX_TEXT_LEN - strlen( text ) - 1 );
114 }
115
116 static void get_filedate_str( mlt_filter filter, mlt_frame frame, char* text )
117 {
118         mlt_producer producer = mlt_producer_cut_parent( mlt_frame_get_original_producer( frame ) );
119         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
120         char* filename = mlt_properties_get( producer_properties, "resource");
121         struct stat file_info;
122
123         if( !stat(filename, &file_info))
124         {
125                 struct tm* time_info = gmtime( &(file_info.st_mtime) );
126                 char date[11] = "";
127                 strftime( date, 11, "%Y/%m/%d", time_info );
128                 strncat( text, date, MAX_TEXT_LEN - strlen( text ) - 1);
129         }
130 }
131
132 static void get_localfiledate_str( mlt_filter filter, mlt_frame frame, char* text )
133 {
134         mlt_producer producer = mlt_producer_cut_parent( mlt_frame_get_original_producer( frame ) );
135         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
136         char* filename = mlt_properties_get( producer_properties, "resource" );
137         struct stat file_info;
138
139         if( !stat( filename, &file_info ) )
140         {
141                 struct tm* time_info = localtime( &(file_info.st_mtime) );
142                 char date[11] = "";
143                 strftime( date, 11, "%Y/%m/%d", time_info );
144                 strncat( text, date, MAX_TEXT_LEN - strlen( text ) - 1);
145         }
146 }
147
148 static void get_resource_str( mlt_filter filter, mlt_frame frame, char* text )
149 {
150         mlt_producer producer = mlt_producer_cut_parent( mlt_frame_get_original_producer( frame ) );
151         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
152         strncat( text, mlt_properties_get( producer_properties, "resource" ), MAX_TEXT_LEN - strlen( text ) - 1 );
153 }
154
155 /** Perform substitution for keywords that are enclosed in "# #".
156 */
157 static void substitute_keywords(mlt_filter filter, char* result, char* value, mlt_frame frame)
158 {
159         char keyword[MAX_TEXT_LEN] = "";
160         int pos = 0;
161         int is_keyword = 0;
162
163         while ( get_next_token(value, &pos, keyword, &is_keyword) )
164         {
165                 if(!is_keyword)
166                 {
167                         strncat( result, keyword, MAX_TEXT_LEN - strlen( result ) - 1 );
168                 }
169                 else if ( !strcmp( keyword, "timecode" ) )
170                 {
171                         get_timecode_str( filter, frame, result );
172                 }
173                 else if ( !strcmp( keyword, "frame" ) )
174                 {
175                         get_frame_str( filter, frame, result );
176                 }
177                 else if ( !strcmp( keyword, "filedate" ) )
178                 {
179                         get_filedate_str( filter, frame, result );
180                 }
181                 else if ( !strcmp( keyword, "localfiledate" ) )
182                 {
183                         get_localfiledate_str( filter, frame, result );
184                 }
185                 else if ( !strcmp( keyword, "resource" ) )
186                 {
187                         get_resource_str( filter, frame, result );
188                 }
189                 else
190                 {
191                         // replace keyword with property value from this frame
192                         mlt_properties frame_properties = MLT_FRAME_PROPERTIES( frame );
193                         char *frame_value = mlt_properties_get( frame_properties, keyword );
194                         if( frame_value )
195                         {
196                                 strncat( result, frame_value, MAX_TEXT_LEN - strlen(result) - 1 );
197                         }
198                 }
199         }
200 }
201
202 static void setup_producer( mlt_filter filter, mlt_producer producer, mlt_frame frame )
203 {
204         mlt_properties my_properties = MLT_FILTER_PROPERTIES( filter );
205         mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES( producer );
206         char* dynamic_text = mlt_properties_get( my_properties, "argument" );
207
208         // Check for keywords in dynamic text
209         if ( dynamic_text )
210         {
211                 // Apply keyword substitution before passing the text to the filter.
212                 char result[MAX_TEXT_LEN] = "";
213                 substitute_keywords( filter, result, dynamic_text, frame );
214                 mlt_properties_set( producer_properties, "markup", (char*)result );
215         }
216
217         // Pass the properties to the pango producer
218         mlt_properties_set( producer_properties, "family", mlt_properties_get( my_properties, "family" ) );
219         mlt_properties_set( producer_properties, "size", mlt_properties_get( my_properties, "size" ) );
220         mlt_properties_set( producer_properties, "weight", mlt_properties_get( my_properties, "weight" ) );
221         mlt_properties_set( producer_properties, "fgcolour", mlt_properties_get( my_properties, "fgcolour" ) );
222         mlt_properties_set( producer_properties, "bgcolour", mlt_properties_get( my_properties, "bgcolour" ) );
223         mlt_properties_set( producer_properties, "olcolour", mlt_properties_get( my_properties, "olcolour" ) );
224         mlt_properties_set( producer_properties, "pad", mlt_properties_get( my_properties, "pad" ) );
225         mlt_properties_set( producer_properties, "outline", mlt_properties_get( my_properties, "outline" ) );
226         mlt_properties_set( producer_properties, "align", mlt_properties_get( my_properties, "halign" ) );
227 }
228
229 static void setup_transition( mlt_filter filter, mlt_transition transition )
230 {
231         mlt_properties my_properties = MLT_FILTER_PROPERTIES( filter );
232         mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES( transition );
233
234         mlt_properties_set( transition_properties, "geometry", mlt_properties_get( my_properties, "geometry" ) );
235         mlt_properties_set( transition_properties, "halign", mlt_properties_get( my_properties, "halign" ) );
236         mlt_properties_set( transition_properties, "valign", mlt_properties_get( my_properties, "valign" ) );
237         mlt_properties_set_int( transition_properties, "out", mlt_properties_get_int( my_properties, "_out" ) );
238         mlt_properties_set_int( transition_properties, "refresh", 1 );
239 }
240
241
242 /** Get the image.
243 */
244 static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable )
245 {
246         int error = 0;
247         mlt_filter filter = mlt_frame_pop_service( frame );
248         mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
249         mlt_producer producer = mlt_properties_get_data( properties, "_producer", NULL );
250         mlt_transition transition = mlt_properties_get_data( properties, "_transition", NULL );
251         mlt_frame text_frame = NULL;
252         mlt_position position = 0;
253
254         // Configure this filter
255         mlt_service_lock( MLT_FILTER_SERVICE( filter ) );
256         setup_producer( filter, producer, frame );
257         setup_transition( filter, transition );
258         mlt_service_unlock( MLT_FILTER_SERVICE( filter ) );
259
260         // Make sure the producer is in the correct position
261         position = mlt_filter_get_position( filter, frame );
262         mlt_producer_seek( producer, position );
263
264         // Get the b frame and process with transition if successful
265         if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &text_frame, 0 ) == 0 )
266         {
267                 // Get the a and b frame properties
268                 mlt_properties a_props = MLT_FRAME_PROPERTIES( frame );
269                 mlt_properties b_props = MLT_FRAME_PROPERTIES( text_frame );
270
271                 // Set the frame and text_frame to be in the same position and have same consumer requirements
272                 mlt_frame_set_position( text_frame, position );
273                 mlt_frame_set_position( frame, position );
274                 mlt_properties_set_int( b_props, "consumer_deinterlace", mlt_properties_get_int( a_props, "consumer_deinterlace" ) );
275
276                 // Apply all filters that are attached to this filter to the b frame
277                 mlt_service_apply_filters( MLT_FILTER_SERVICE( filter ), text_frame, 0 );
278
279                 // Process the frame
280                 mlt_transition_process( transition, frame, text_frame );
281
282                 // Get the image
283                 *format = mlt_image_yuv422;
284                 error = mlt_frame_get_image( frame, image, format, width, height, 1 );
285
286                 // Close the b frame
287                 mlt_frame_close( text_frame );
288         }
289
290         return error;
291 }
292
293 /** Filter processing.
294 */
295 static mlt_frame filter_process( mlt_filter filter, mlt_frame frame )
296 {
297         // Get the properties of the frame
298         mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
299
300         // Save the frame out point
301         mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "_out", mlt_properties_get_int( properties, "out" ) );
302
303         // Push the filter on to the stack
304         mlt_frame_push_service( frame, filter );
305
306         // Push the get_image on to the stack
307         mlt_frame_push_get_image( frame, filter_get_image );
308
309         return frame;
310 }
311
312 /** Constructor for the filter.
313 */
314 mlt_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
315 {
316         mlt_filter filter = mlt_filter_new();
317         mlt_transition transition = mlt_factory_transition( profile, "composite", NULL );
318         mlt_producer producer = mlt_factory_producer( profile, mlt_environment( "MLT_PRODUCER" ), "pango:" );
319
320         if ( filter && transition && producer )
321         {
322                 mlt_properties my_properties = MLT_FILTER_PROPERTIES( filter );
323
324                 // Register the transition for reuse/destruction
325         mlt_properties_set_data( my_properties, "_transition", transition, 0, ( mlt_destructor )mlt_transition_close, NULL );
326
327                 // Register the producer for reuse/destruction
328                 mlt_properties_set_data( my_properties, "_producer", producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
329
330                 // Ensure that we loop
331                 mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" );
332
333                 // Assign default values
334                 mlt_properties_set( my_properties, "argument", arg ? arg: "#timecode#" );
335                 mlt_properties_set( my_properties, "geometry", "0%/0%:100%x100%:100" );
336                 mlt_properties_set( my_properties, "family", "Sans" );
337                 mlt_properties_set( my_properties, "size", "48" );
338                 mlt_properties_set( my_properties, "weight", "400" );
339                 mlt_properties_set( my_properties, "fgcolour", "0x000000ff" );
340                 mlt_properties_set( my_properties, "bgcolour", "0x00000020" );
341                 mlt_properties_set( my_properties, "olcolour", "0x00000000" );
342                 mlt_properties_set( my_properties, "pad", "0" );
343                 mlt_properties_set( my_properties, "halign", "left" );
344                 mlt_properties_set( my_properties, "valign", "top" );
345                 mlt_properties_set( my_properties, "outline", "0" );
346
347                 mlt_properties_set_int( my_properties, "_filter_private", 1 );
348
349                 filter->process = filter_process;
350         }
351         else
352         {
353                 if( filter )
354                 {
355                         mlt_filter_close( filter );
356                 }
357
358                 if( transition )
359                 {
360                         mlt_transition_close( transition );
361                 }
362
363                 if( producer )
364                 {
365                         mlt_producer_close( producer );
366                 }
367
368                 filter = NULL;
369         }
370         return filter;
371 }