]> git.sesse.net Git - mlt/blob - src/framework/mlt_properties.c
168aff4b0c156b233049cde2f6652c5280e23506
[mlt] / src / framework / mlt_properties.c
1 /*
2  * mlt_properties.c -- base properties class
3  * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4  * Author: Charles Yates <charles.yates@pandora.be>
5  * Contributor: Dan Dennedy <dan@dennedy.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "mlt_properties.h"
23 #include "mlt_property.h"
24 #include "mlt_deque.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <stdarg.h>
31
32 #include <sys/types.h>
33 #include <dirent.h>
34
35 /* ---------------- // Private Implementation // ---------------- */
36
37 /** Private implementation of the property list.
38 */
39
40 typedef struct
41 {
42         int hash[ 199 ];
43         char **name;
44         mlt_property *value;
45         int count;
46         int size;
47         mlt_properties mirror;
48         int ref_count;
49 }
50 property_list;
51
52 /** Memory leak checks.
53 */
54
55 //#define _MLT_PROPERTY_CHECKS_ 2
56
57 #ifdef _MLT_PROPERTY_CHECKS_
58 static int properties_created = 0;
59 static int properties_destroyed = 0;
60 #endif
61
62 /** Basic implementation.
63 */
64
65 int mlt_properties_init( mlt_properties this, void *child )
66 {
67         if ( this != NULL )
68         {
69 #ifdef _MLT_PROPERTY_CHECKS_
70                 // Increment number of properties created
71                 properties_created ++;
72 #endif
73
74                 // NULL all methods
75                 memset( this, 0, sizeof( struct mlt_properties_s ) );
76
77                 // Assign the child of the object
78                 this->child = child;
79
80                 // Allocate the local structure
81                 this->local = calloc( sizeof( property_list ), 1 );
82
83                 // Increment the ref count
84                 ( ( property_list * )this->local )->ref_count = 1;
85         }
86
87         // Check that initialisation was successful
88         return this != NULL && this->local == NULL;
89 }
90
91 /** Constructor for stand alone object.
92 */
93
94 mlt_properties mlt_properties_new( )
95 {
96         // Construct a standalone properties object
97         mlt_properties this = calloc( sizeof( struct mlt_properties_s ), 1 );
98
99         // Initialise this
100         mlt_properties_init( this, NULL );
101
102         // Return the pointer
103         return this;
104 }
105
106 /** Load properties from a .properties file (DEPRECATED).
107 */
108
109 mlt_properties mlt_properties_load( const char *filename )
110 {
111         // Construct a standalone properties object
112         mlt_properties this = mlt_properties_new( );
113
114         if ( this != NULL )
115         {
116                 // Open the file
117                 FILE *file = fopen( filename, "r" );
118
119                 // Load contents of file
120                 if ( file != NULL )
121                 {
122                         // Temp string
123                         char temp[ 1024 ];
124                         char last[ 1024 ] = "";
125
126                         // Read each string from the file
127                         while( fgets( temp, 1024, file ) )
128                         {
129                                 // Chomp the string
130                                 temp[ strlen( temp ) - 1 ] = '\0';
131
132                                 // Check if the line starts with a .
133                                 if ( temp[ 0 ] == '.' )
134                                 {
135                                         char temp2[ 1024 ];
136                                         sprintf( temp2, "%s%s", last, temp );
137                                         strcpy( temp, temp2 );
138                                 }
139                                 else if ( strchr( temp, '=' ) )
140                                 {
141                                         strcpy( last, temp );
142                                         *( strchr( last, '=' ) ) = '\0';
143                                 }
144
145                                 // Parse and set the property
146                                 if ( strcmp( temp, "" ) && temp[ 0 ] != '#' )
147                                         mlt_properties_parse( this, temp );
148                         }
149
150                         // Close the file
151                         fclose( file );
152                 }
153         }
154
155         // Return the pointer
156         return this;
157 }
158
159 static inline int generate_hash( const char *name )
160 {
161         int hash = 0;
162         int i = 1;
163         while ( *name )
164                 hash = ( hash + ( i ++ * ( *name ++ & 31 ) ) ) % 199;
165         return hash;
166 }
167
168 /** Special case - when a container (such as fezzik) is protecting another 
169         producer, we need to ensure that properties are passed through to the
170         real producer.
171 */
172
173 static inline void mlt_properties_do_mirror( mlt_properties this, const char *name )
174 {
175         property_list *list = this->local;
176         if ( list->mirror != NULL ) 
177         {
178                 char *value = mlt_properties_get( this, name );
179                 if ( value != NULL )
180                         mlt_properties_set( list->mirror, name, value );
181         }
182 }
183
184 /** Maintain ref count to allow multiple uses of an mlt object.
185 */
186
187 int mlt_properties_inc_ref( mlt_properties this )
188 {
189         if ( this != NULL )
190         {
191                 property_list *list = this->local;
192                 return ++ list->ref_count;
193         }
194         return 0;
195 }
196
197 /** Maintain ref count to allow multiple uses of an mlt object.
198 */
199
200 int mlt_properties_dec_ref( mlt_properties this )
201 {
202         if ( this != NULL )
203         {
204                 property_list *list = this->local;
205                 return -- list->ref_count;
206         }
207         return 0;
208 }
209
210 /** Return the ref count of this object.
211 */
212
213 int mlt_properties_ref_count( mlt_properties this )
214 {
215         if ( this != NULL )
216         {
217                 property_list *list = this->local;
218                 return list->ref_count;
219         }
220         return 0;
221 }
222
223 /** Mirror properties set on 'this' to 'that'.
224 */
225
226 void mlt_properties_mirror( mlt_properties this, mlt_properties that )
227 {
228         property_list *list = this->local;
229         list->mirror = that;
230 }
231
232 /** Inherit all serialisable properties from that into this.
233 */
234
235 int mlt_properties_inherit( mlt_properties this, mlt_properties that )
236 {
237         int count = mlt_properties_count( that );
238         int i = 0;
239         for ( i = 0; i < count; i ++ )
240         {
241                 char *value = mlt_properties_get_value( that, i );
242                 if ( value != NULL )
243                 {
244                         char *name = mlt_properties_get_name( that, i );
245                         mlt_properties_set( this, name, value );
246                 }
247         }
248         return 0;
249 }
250
251 /** Pass all properties from 'that' that match the prefix to 'this' (excluding the prefix).
252 */
253
254 int mlt_properties_pass( mlt_properties this, mlt_properties that, const char *prefix )
255 {
256         int count = mlt_properties_count( that );
257         int length = strlen( prefix );
258         int i = 0;
259         for ( i = 0; i < count; i ++ )
260         {
261                 char *name = mlt_properties_get_name( that, i );
262                 if ( !strncmp( name, prefix, length ) )
263                 {
264                         char *value = mlt_properties_get_value( that, i );
265                         if ( value != NULL )
266                                 mlt_properties_set( this, name + length, value );
267                 }
268         }
269         return 0;
270 }
271
272 /** Locate a property by name
273 */
274
275 static inline mlt_property mlt_properties_find( mlt_properties this, const char *name )
276 {
277         property_list *list = this->local;
278         mlt_property value = NULL;
279         int key = generate_hash( name );
280         int i = list->hash[ key ] - 1;
281
282         if ( i >= 0 )
283         {
284                 // Check if we're hashed
285                 if ( list->count > 0 &&
286                         name[ 0 ] == list->name[ i ][ 0 ] && 
287                         !strcmp( list->name[ i ], name ) )
288                         value = list->value[ i ];
289
290                 // Locate the item 
291                 for ( i = list->count - 1; value == NULL && i >= 0; i -- )
292                         if ( name[ 0 ] == list->name[ i ][ 0 ] && !strcmp( list->name[ i ], name ) )
293                                 value = list->value[ i ];
294         }
295
296         return value;
297 }
298
299 /** Add a new property.
300 */
301
302 static mlt_property mlt_properties_add( mlt_properties this, const char *name )
303 {
304         property_list *list = this->local;
305         int key = generate_hash( name );
306
307         // Check that we have space and resize if necessary
308         if ( list->count == list->size )
309         {
310                 list->size += 50;
311                 list->name = realloc( list->name, list->size * sizeof( const char * ) );
312                 list->value = realloc( list->value, list->size * sizeof( mlt_property ) );
313         }
314
315         // Assign name/value pair
316         list->name[ list->count ] = strdup( name );
317         list->value[ list->count ] = mlt_property_init( );
318
319         // Assign to hash table
320         if ( list->hash[ key ] == 0 )
321                 list->hash[ key ] = list->count + 1;
322
323         // Return and increment count accordingly
324         return list->value[ list->count ++ ];
325 }
326
327 /** Fetch a property by name - this includes add if not found.
328 */
329
330 static mlt_property mlt_properties_fetch( mlt_properties this, const char *name )
331 {
332         // Try to find an existing property first
333         mlt_property property = mlt_properties_find( this, name );
334
335         // If it wasn't found, create one
336         if ( property == NULL )
337                 property = mlt_properties_add( this, name );
338
339         // Return the property
340         return property;
341 }
342
343 /** Pass property 'name' from 'that' to 'this' 
344 * Who to blame: Zach <zachary.drew@gmail.com>
345 */
346
347 void mlt_properties_pass_property( mlt_properties this, mlt_properties that, const char *name )
348 {
349         // Make sure the source property isn't null.
350         mlt_property that_prop = mlt_properties_find( that, name );
351         if( that_prop == NULL )
352                 return;
353
354         mlt_property_pass( mlt_properties_fetch( this, name ), that_prop );
355 }
356
357 /** Pass all properties from 'that' to 'this' as found in comma seperated 'list'.
358 * Who to blame: Zach <zachary.drew@gmail.com>
359 */
360
361 int mlt_properties_pass_list( mlt_properties this, mlt_properties that, const char *list )
362 {
363         char *props = strdup( list );
364         char *ptr = props;
365         char *delim = " ,\t\n"; // Any combination of spaces, commas, tabs, and newlines
366         int count, done = 0;
367
368         while( !done )
369         {
370                 count = strcspn( ptr, delim );
371
372                 if( ptr[count] == '\0' )
373                         done = 1;
374                 else
375                         ptr[count] = '\0';      // Make it a real string
376
377                 mlt_properties_pass_property( this, that, ptr );
378
379                 ptr += count + 1;
380                 ptr += strspn( ptr, delim );
381         }
382
383         free( props );
384
385         return 0;
386 }
387
388
389 /** Set the property.
390 */
391
392 int mlt_properties_set( mlt_properties this, const char *name, const char *value )
393 {
394         int error = 1;
395
396         // Fetch the property to work with
397         mlt_property property = mlt_properties_fetch( this, name );
398
399         // Set it if not NULL
400         if ( property == NULL )
401         {
402                 fprintf( stderr, "Whoops - %s not found (should never occur)\n", name );
403         }
404         else if ( value == NULL )
405         {
406                 error = mlt_property_set_string( property, value );
407                 mlt_properties_do_mirror( this, name );
408         }
409         else if ( *value != '@' )
410         {
411                 error = mlt_property_set_string( property, value );
412                 mlt_properties_do_mirror( this, name );
413         }
414         else if ( value[ 0 ] == '@' )
415         {
416                 int total = 0;
417                 int current = 0;
418                 char id[ 255 ];
419                 char op = '+';
420
421                 value ++;
422
423                 while ( *value != '\0' )
424                 {
425                         int length = strcspn( value, "+-*/" );
426
427                         // Get the identifier
428                         strncpy( id, value, length );
429                         id[ length ] = '\0';
430                         value += length;
431
432                         // Determine the value
433                         if ( isdigit( id[ 0 ] ) )
434                                 current = atof( id );
435                         else
436                                 current = mlt_properties_get_int( this, id );
437
438                         // Apply the operation
439                         switch( op )
440                         {
441                                 case '+':
442                                         total += current;
443                                         break;
444                                 case '-':
445                                         total -= current;
446                                         break;
447                                 case '*':
448                                         total *= current;
449                                         break;
450                                 case '/':
451                                         total /= current;
452                                         break;
453                         }
454
455                         // Get the next op
456                         op = *value != '\0' ? *value ++ : ' ';
457                 }
458
459                 error = mlt_property_set_int( property, total );
460                 mlt_properties_do_mirror( this, name );
461         }
462
463         mlt_events_fire( this, "property-changed", name, NULL );
464
465         return error;
466 }
467
468 /** Set or default the property.
469 */
470
471 int mlt_properties_set_or_default( mlt_properties this, const char *name, const char *value, const char *def )
472 {
473         return mlt_properties_set( this, name, value == NULL ? def : value );
474 }
475
476 /** Get a string value by name.
477 */
478
479 char *mlt_properties_get( mlt_properties this, const char *name )
480 {
481         mlt_property value = mlt_properties_find( this, name );
482         return value == NULL ? NULL : mlt_property_get_string( value );
483 }
484
485 /** Get a name by index.
486 */
487
488 char *mlt_properties_get_name( mlt_properties this, int index )
489 {
490         property_list *list = this->local;
491         if ( index >= 0 && index < list->count )
492                 return list->name[ index ];
493         return NULL;
494 }
495
496 /** Get a string value by index.
497 */
498
499 char *mlt_properties_get_value( mlt_properties this, int index )
500 {
501         property_list *list = this->local;
502         if ( index >= 0 && index < list->count )
503                 return mlt_property_get_string( list->value[ index ] );
504         return NULL;
505 }
506
507 /** Get a data value by index.
508 */
509
510 void *mlt_properties_get_data_at( mlt_properties this, int index, int *size )
511 {
512         property_list *list = this->local;
513         if ( index >= 0 && index < list->count )
514                 return mlt_property_get_data( list->value[ index ], size );
515         return NULL;
516 }
517
518 /** Return the number of items in the list.
519 */
520
521 int mlt_properties_count( mlt_properties this )
522 {
523         property_list *list = this->local;
524         return list->count;
525 }
526
527 /** Set a value by parsing a name=value string
528 */
529
530 int mlt_properties_parse( mlt_properties this, const char *namevalue )
531 {
532         char *name = strdup( namevalue );
533         char *value = NULL;
534         int error = 0;
535         char *ptr = strchr( name, '=' );
536
537         if ( ptr )
538         {
539                 *( ptr ++ ) = '\0';
540
541                 if ( *ptr != '\"' )
542                 {
543                         value = strdup( ptr );
544                 }
545                 else
546                 {
547                         ptr ++;
548                         value = strdup( ptr );
549                         if ( value != NULL && value[ strlen( value ) - 1 ] == '\"' )
550                                 value[ strlen( value ) - 1 ] = '\0';
551                 }
552         }
553         else
554         {
555                 value = strdup( "" );
556         }
557
558         error = mlt_properties_set( this, name, value );
559
560         free( name );
561         free( value );
562
563         return error;
564 }
565
566 /** Get a value associated to the name.
567 */
568
569 int mlt_properties_get_int( mlt_properties this, const char *name )
570 {
571         mlt_property value = mlt_properties_find( this, name );
572         return value == NULL ? 0 : mlt_property_get_int( value );
573 }
574
575 /** Set a value associated to the name.
576 */
577
578 int mlt_properties_set_int( mlt_properties this, const char *name, int value )
579 {
580         int error = 1;
581
582         // Fetch the property to work with
583         mlt_property property = mlt_properties_fetch( this, name );
584
585         // Set it if not NULL
586         if ( property != NULL )
587         {
588                 error = mlt_property_set_int( property, value );
589                 mlt_properties_do_mirror( this, name );
590         }
591
592         mlt_events_fire( this, "property-changed", name, NULL );
593
594         return error;
595 }
596
597 /** Get a value associated to the name.
598 */
599
600 int64_t mlt_properties_get_int64( mlt_properties this, const char *name )
601 {
602         mlt_property value = mlt_properties_find( this, name );
603         return value == NULL ? 0 : mlt_property_get_int64( value );
604 }
605
606 /** Set a value associated to the name.
607 */
608
609 int mlt_properties_set_int64( mlt_properties this, const char *name, int64_t value )
610 {
611         int error = 1;
612
613         // Fetch the property to work with
614         mlt_property property = mlt_properties_fetch( this, name );
615
616         // Set it if not NULL
617         if ( property != NULL )
618         {
619                 error = mlt_property_set_int64( property, value );
620                 mlt_properties_do_mirror( this, name );
621         }
622
623         mlt_events_fire( this, "property-changed", name, NULL );
624
625         return error;
626 }
627
628 /** Get a value associated to the name.
629 */
630
631 double mlt_properties_get_double( mlt_properties this, const char *name )
632 {
633         mlt_property value = mlt_properties_find( this, name );
634         return value == NULL ? 0 : mlt_property_get_double( value );
635 }
636
637 /** Set a value associated to the name.
638 */
639
640 int mlt_properties_set_double( mlt_properties this, const char *name, double value )
641 {
642         int error = 1;
643
644         // Fetch the property to work with
645         mlt_property property = mlt_properties_fetch( this, name );
646
647         // Set it if not NULL
648         if ( property != NULL )
649         {
650                 error = mlt_property_set_double( property, value );
651                 mlt_properties_do_mirror( this, name );
652         }
653
654         mlt_events_fire( this, "property-changed", name, NULL );
655
656         return error;
657 }
658
659 /** Get a value associated to the name.
660 */
661
662 mlt_position mlt_properties_get_position( mlt_properties this, const char *name )
663 {
664         mlt_property value = mlt_properties_find( this, name );
665         return value == NULL ? 0 : mlt_property_get_position( value );
666 }
667
668 /** Set a value associated to the name.
669 */
670
671 int mlt_properties_set_position( mlt_properties this, const char *name, mlt_position value )
672 {
673         int error = 1;
674
675         // Fetch the property to work with
676         mlt_property property = mlt_properties_fetch( this, name );
677
678         // Set it if not NULL
679         if ( property != NULL )
680         {
681                 error = mlt_property_set_position( property, value );
682                 mlt_properties_do_mirror( this, name );
683         }
684
685         mlt_events_fire( this, "property-changed", name, NULL );
686
687         return error;
688 }
689
690 /** Get a value associated to the name.
691 */
692
693 void *mlt_properties_get_data( mlt_properties this, const char *name, int *length )
694 {
695         mlt_property value = mlt_properties_find( this, name );
696         return value == NULL ? NULL : mlt_property_get_data( value, length );
697 }
698
699 /** Set a value associated to the name.
700 */
701
702 int mlt_properties_set_data( mlt_properties this, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise )
703 {
704         int error = 1;
705
706         // Fetch the property to work with
707         mlt_property property = mlt_properties_fetch( this, name );
708
709         // Set it if not NULL
710         if ( property != NULL )
711                 error = mlt_property_set_data( property, value, length, destroy, serialise );
712
713         mlt_events_fire( this, "property-changed", name, NULL );
714
715         return error;
716 }
717
718 /** Rename a property.
719 */
720
721 int mlt_properties_rename( mlt_properties this, const char *source, const char *dest )
722 {
723         mlt_property value = mlt_properties_find( this, dest );
724
725         if ( value == NULL )
726         {
727                 property_list *list = this->local;
728                 int i = 0;
729
730                 // Locate the item 
731                 for ( i = 0; i < list->count; i ++ )
732                 {
733                         if ( !strcmp( list->name[ i ], source ) )
734                         {
735                                 free( list->name[ i ] );
736                                 list->name[ i ] = strdup( dest );
737                                 list->hash[ generate_hash( dest ) ] = i + 1;
738                                 break;
739                         }
740                 }
741         }
742
743         return value != NULL;
744 }
745
746 /** Dump the properties.
747 */
748
749 void mlt_properties_dump( mlt_properties this, FILE *output )
750 {
751         property_list *list = this->local;
752         int i = 0;
753         for ( i = 0; i < list->count; i ++ )
754                 if ( mlt_properties_get( this, list->name[ i ] ) != NULL )
755                         fprintf( output, "%s=%s\n", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) );
756 }
757
758 void mlt_properties_debug( mlt_properties this, const char *title, FILE *output )
759 {
760         if ( output == NULL ) output = stderr;
761         fprintf( output, "%s: ", title );
762         if ( this != NULL )
763         {
764                 property_list *list = this->local;
765                 int i = 0;
766                 fprintf( output, "[ ref=%d", list->ref_count );
767                 for ( i = 0; i < list->count; i ++ )
768                         if ( mlt_properties_get( this, list->name[ i ] ) != NULL )
769                                 fprintf( output, ", %s=%s", list->name[ i ], mlt_properties_get( this, list->name[ i ] ) );
770                         else
771                                 fprintf( output, ", %s=%p", list->name[ i ], mlt_properties_get_data( this, list->name[ i ], NULL ) );
772                 fprintf( output, " ]" );
773         }
774         fprintf( output, "\n" );
775 }
776
777 int mlt_properties_save( mlt_properties this, const char *filename )
778 {
779         int error = 1;
780         FILE *f = fopen( filename, "w" );
781         if ( f != NULL )
782         {
783                 mlt_properties_dump( this, f );
784                 fclose( f );
785                 error = 0;
786         }
787         return error;
788 }
789
790 /* This is a very basic cross platform fnmatch replacement - it will fail in
791 ** many cases, but for the basic *.XXX and YYY*.XXX, it will work ok.
792 */
793
794 static int mlt_fnmatch( const char *wild, const char *file )
795 {
796         int f = 0;
797         int w = 0;
798
799         while( f < strlen( file ) && w < strlen( wild ) )
800         {
801                 if ( wild[ w ] == '*' )
802                 {
803                         w ++;
804                         if ( w == strlen( wild ) )
805                                 f = strlen( file );
806                         while ( f != strlen( file ) && tolower( file[ f ] ) != tolower( wild[ w ] ) )
807                                 f ++;
808                 }
809                 else if ( wild[ w ] == '?' || tolower( file[ f ] ) == tolower( wild[ w ] ) )
810                 {
811                         f ++;
812                         w ++;
813                 }
814                 else if ( wild[ 0 ] == '*' )
815                 {
816                         w = 0;
817                 }
818                 else
819                 {
820                         return 0;
821                 }
822         }
823
824         return strlen( file ) == f &&  strlen( wild ) == w;
825 }
826
827 static int mlt_compare( const void *this, const void *that )
828 {
829         return strcmp( mlt_property_get_string( *( mlt_property * )this ), mlt_property_get_string( *( mlt_property * )that ) );
830 }
831
832 /* Obtains an optionally sorted list of the files found in a directory with a specific wild card.
833  * Entries in the list have a numeric name (running from 0 to count - 1). Only values change
834  * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc).
835  */
836
837 int mlt_properties_dir_list( mlt_properties this, const char *dirname, const char *pattern, int sort )
838 {
839         DIR *dir = opendir( dirname );
840
841         if ( dir )
842         {
843                 char key[ 20 ];
844                 struct dirent *de = readdir( dir );
845                 char fullname[ 1024 ];
846                 while( de != NULL )
847                 {
848                         sprintf( key, "%d", mlt_properties_count( this ) );
849                         snprintf( fullname, 1024, "%s/%s", dirname, de->d_name );
850                         if ( pattern == NULL )
851                                 mlt_properties_set( this, key, fullname );
852                         else if ( de->d_name[ 0 ] != '.' && mlt_fnmatch( pattern, de->d_name ) )
853                                 mlt_properties_set( this, key, fullname );
854                         de = readdir( dir );
855                 }
856
857                 closedir( dir );
858         }
859
860         if ( sort && mlt_properties_count( this ) )
861         {
862                 property_list *list = this->local;
863                 qsort( list->value, mlt_properties_count( this ), sizeof( mlt_property ), mlt_compare );
864         }
865
866         return mlt_properties_count( this );
867 }
868
869 /** Close the list.
870 */
871
872 void mlt_properties_close( mlt_properties this )
873 {
874         if ( this != NULL && mlt_properties_dec_ref( this ) <= 0 )
875         {
876                 if ( this->close != NULL )
877                 {
878                         this->close( this->close_object );
879                 }
880                 else
881                 {
882                         property_list *list = this->local;
883                         int index = 0;
884
885 #if _MLT_PROPERTY_CHECKS_ == 1
886                         // Show debug info
887                         mlt_properties_debug( this, "Closing", stderr );
888 #endif
889
890 #ifdef _MLT_PROPERTY_CHECKS_
891                         // Increment destroyed count
892                         properties_destroyed ++;
893
894                         // Show current stats - these should match when the app is closed
895                         fprintf( stderr, "Created %d, destroyed %d\n", properties_created, properties_destroyed );
896 #endif
897
898                         // Clean up names and values
899                         for ( index = list->count - 1; index >= 0; index -- )
900                         {
901                                 free( list->name[ index ] );
902                                 mlt_property_close( list->value[ index ] );
903                         }
904         
905                         // Clear up the list
906                         free( list->name );
907                         free( list->value );
908                         free( list );
909         
910                         // Free this now if this has no child
911                         if ( this->child == NULL )
912                                 free( this );
913                 }
914         }
915 }
916
917 /** Determine if the properties list is really just a sequence or ordered list.
918     Returns 0 if false or 1 if true.
919 */
920 int mlt_properties_is_sequence( mlt_properties properties )
921 {
922         int i;
923         int n = mlt_properties_count( properties );
924         for ( i = 0; i < n; i++ )
925                 if ( ! isdigit( mlt_properties_get_name( properties, i )[0] ) )
926                         return 0;
927         return 1;
928 }
929
930 /** YAML Tiny Parser
931  * YAML is a nifty text format popular in the Ruby world as a cleaner,
932  * less verbose alternative to XML. See this Wikipedia topic for an overview:
933  * http://en.wikipedia.org/wiki/YAML
934  * The YAML specification is at:
935  * http://yaml.org/
936  * YAML::Tiny is a Perl module that specifies a subset of YAML that we are
937  * using here (for the same reasons):
938  * http://search.cpan.org/~adamk/YAML-Tiny-1.25/lib/YAML/Tiny.pm
939  */
940
941 struct yaml_parser_context
942 {
943         mlt_deque stack;
944         unsigned int level;
945         unsigned int index;
946         char block;
947         char *block_name;
948         unsigned int block_indent;
949         
950 };
951 typedef struct yaml_parser_context *yaml_parser;
952
953 static unsigned int ltrim( char **s )
954 {
955         unsigned int i = 0;
956         char *c = *s;
957         int n = strlen( c );
958         for ( i = 0; i < n && *c == ' '; i++, c++ );
959         *s = c;
960         return i;
961 }
962
963 static unsigned int rtrim( char *s )
964 {
965         int n = strlen( s );
966         int i;
967         for ( i = n; i > 0 && s[i - 1] == ' '; --i )
968                 s[i - 1] = 0;
969         return n - i;
970 }
971
972 static int parse_yaml( yaml_parser context, const char *namevalue )
973 {
974         char *name_ = strdup( namevalue );
975         char *name = name_;
976         char *value = NULL;
977         int error = 0;
978         char *ptr = strchr( name, ':' );
979         unsigned int indent = ltrim( &name );
980         mlt_properties properties = mlt_deque_peek_front( context->stack );
981         
982         // Ascending one more levels in the tree
983         if ( indent < context->level )
984         {
985                 unsigned int i;
986                 unsigned int n = ( context->level - indent ) / 2;
987                 for ( i = 0; i < n; i++ )
988                         mlt_deque_pop_front( context->stack );
989                 properties = mlt_deque_peek_front( context->stack );
990                 context->level = indent;
991         }
992         
993         // Descending a level in the tree
994         else if ( indent > context->level && context->block == 0 )
995         {
996                 context->level = indent;
997         }
998         
999         // If there is a colon that is not part of a block
1000         if ( ptr && ( indent == context->level ) )
1001         {
1002                 // Reset block processing
1003                 if ( context->block_name )
1004                 {
1005                         free( context->block_name );
1006                         context->block_name = NULL;
1007                         context->block = 0;
1008                 }
1009                 
1010                 // Terminate the name and setup the value pointer
1011                 *( ptr ++ ) = 0;
1012                 
1013                 // Trim comment
1014                 char *comment = strchr( ptr, '#' );
1015                 if ( comment )
1016                 {
1017                         *comment = 0;
1018                 }
1019                 
1020                 // Trim leading and trailing spaces from bare value 
1021                 ltrim( &ptr );
1022                 rtrim( ptr );
1023                 
1024                 // No value means a child
1025                 if ( strcmp( ptr, "" ) == 0 )
1026                 {
1027                         mlt_properties child = mlt_properties_new();
1028                         mlt_properties_set_data( properties, name, child, 0, 
1029                                 ( mlt_destructor )mlt_properties_close, NULL );
1030                         mlt_deque_push_front( context->stack, child );
1031                         context->index = 0;
1032                         free( name_ );
1033                         return error;
1034                 }
1035                 
1036                 // A dash indicates a sequence item
1037                 if ( name[0] == '-' )
1038                 {
1039                         mlt_properties child = mlt_properties_new();
1040                         char key[20];
1041                         
1042                         snprintf( key, sizeof(key), "%d", context->index++ );
1043                         mlt_properties_set_data( properties, key, child, 0, 
1044                                 ( mlt_destructor )mlt_properties_close, NULL );
1045                         mlt_deque_push_front( context->stack, child );
1046
1047                         name ++;
1048                         context->level += ltrim( &name ) + 1;
1049                         properties = child;
1050                 }
1051                 
1052                 // Value is quoted
1053                 if ( *ptr == '\"' )
1054                 {
1055                         ptr ++;
1056                         value = strdup( ptr );
1057                         if ( value && value[ strlen( value ) - 1 ] == '\"' )
1058                                 value[ strlen( value ) - 1 ] = 0;
1059                 }
1060                 
1061                 // Value is folded or unfolded block
1062                 else if ( *ptr == '|' || *ptr == '>' )
1063                 {
1064                         context->block = *ptr;
1065                         context->block_name = strdup( name );
1066                         context->block_indent = 0;
1067                         value = strdup( "" );
1068                 }
1069                 
1070                 // Bare value
1071                 else
1072                 {
1073                         value = strdup( ptr );
1074                 }
1075         }
1076         
1077         // A list of scalars
1078         else if ( name[0] == '-' )
1079         {
1080                 // Reset block processing
1081                 if ( context->block_name )
1082                 {
1083                         free( context->block_name );
1084                         context->block_name = NULL;
1085                         context->block = 0;
1086                 }
1087                 
1088                 char key[20];
1089                 
1090                 snprintf( key, sizeof(key), "%d", context->index++ );
1091                 ptr = name + 1;
1092                 
1093                 // Trim comment
1094                 char *comment = strchr( ptr, '#' );
1095                 if ( comment )
1096                         *comment = 0;
1097                 
1098                 // Trim leading and trailing spaces from bare value 
1099                 ltrim( &ptr );
1100                 rtrim( ptr );
1101                 
1102                 // Value is quoted
1103                 if ( *ptr == '\"' )
1104                 {
1105                         ptr ++;
1106                         value = strdup( ptr );
1107                         if ( value && value[ strlen( value ) - 1 ] == '\"' )
1108                                 value[ strlen( value ) - 1 ] = 0;
1109                 }
1110                 
1111                 // Value is folded or unfolded block
1112                 else if ( *ptr == '|' || *ptr == '>' )
1113                 {
1114                         context->block = *ptr;
1115                         context->block_name = strdup( key );
1116                         context->block_indent = 0;
1117                         value = strdup( "" );
1118                 }
1119                 
1120                 // Bare value
1121                 else
1122                 {
1123                         value = strdup( ptr );
1124                 }
1125                 
1126                 free( name_ );
1127                 name = name_ = strdup( key );
1128         }
1129
1130         // Non-folded block
1131         else if ( context->block == '|' )
1132         {
1133                 if ( context->block_indent == 0 )
1134                         context->block_indent = indent;
1135                 if ( indent > context->block_indent )
1136                         name = &name_[ context->block_indent ];
1137                 rtrim( name );
1138                 char *old_value = mlt_properties_get( properties, context->block_name );
1139                 value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1140                 strcpy( value, old_value );
1141                 if ( strcmp( old_value, "" ) )
1142                         strcat( value, "\n" );
1143                 strcat( value, name );
1144                 name = context->block_name;
1145         }
1146         
1147         // Folded block
1148         else if ( context->block == '>' )
1149         {
1150                 ltrim( &name );
1151                 rtrim( name );
1152                 char *old_value = mlt_properties_get( properties, context->block_name );
1153                 
1154                 // Blank line (prepended with spaces) is new line
1155                 if ( strcmp( name, "" ) == 0 )
1156                 {
1157                         value = calloc( 1, strlen( old_value ) + 2 );
1158                         strcat( value, old_value );
1159                         strcat( value, "\n" );
1160                 }
1161                 // Concatenate with space
1162                 else
1163                 {
1164                         value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1165                         strcat( value, old_value );
1166                         if ( strcmp( old_value, "" ) && old_value[ strlen( old_value ) - 1 ] != '\n' )
1167                                 strcat( value, " " );
1168                         strcat( value, name );
1169                 }
1170                 name = context->block_name;
1171         }
1172         
1173         else
1174         {
1175                 value = strdup( "" );
1176         }
1177
1178         error = mlt_properties_set( properties, name, value );
1179
1180         free( name_ );
1181         free( value );
1182
1183         return error;
1184 }
1185
1186 mlt_properties mlt_properties_parse_yaml( const char *filename )
1187 {
1188         // Construct a standalone properties object
1189         mlt_properties this = mlt_properties_new( );
1190
1191         if ( this )
1192         {
1193                 // Open the file
1194                 FILE *file = fopen( filename, "r" );
1195
1196                 // Load contents of file
1197                 if ( file )
1198                 {
1199                         // Temp string
1200                         char temp[ 1024 ];
1201                         char *ptemp = &temp[ 0 ];
1202                         
1203                         // Parser context
1204                         yaml_parser context = calloc( 1, sizeof( struct yaml_parser_context ) );
1205                         context->stack = mlt_deque_init();
1206                         mlt_deque_push_front( context->stack, this );
1207
1208                         // Read each string from the file
1209                         while( fgets( temp, 1024, file ) )
1210                         {
1211                                 // Check for end-of-stream
1212                                 if ( strncmp( ptemp, "...", 3 ) == 0 )
1213                                         break;
1214                                         
1215                                 // Chomp the string
1216                                 temp[ strlen( temp ) - 1 ] = '\0';
1217
1218                                 // Skip blank lines, comment lines, and document separator
1219                                 if ( strcmp( ptemp, "" ) && ptemp[ 0 ] != '#' && strncmp( ptemp, "---", 3 ) 
1220                                      && strncmp( ptemp, "%YAML", 5 ) && strncmp( ptemp, "% YAML", 6 ) )
1221                                         parse_yaml( context, temp );
1222                         }
1223
1224                         // Close the file
1225                         fclose( file );
1226                         mlt_deque_close( context->stack );
1227                         if ( context->block_name )
1228                                 free( context->block_name );
1229                         free( context );
1230                 }
1231         }
1232
1233         // Return the pointer
1234         return this;
1235 }
1236
1237 /** YAML Tiny Serialiser
1238 */
1239
1240 /* strbuf is a self-growing string buffer */
1241
1242 #define STRBUF_GROWTH (1024)
1243
1244 struct strbuf_s
1245 {
1246         size_t size;
1247         char *string;
1248 };
1249
1250 typedef struct strbuf_s *strbuf;
1251
1252 static strbuf strbuf_new( )
1253 {
1254         strbuf buffer = calloc( 1, sizeof( struct strbuf_s ) );
1255         buffer->size = STRBUF_GROWTH;
1256         buffer->string = calloc( 1, buffer->size );
1257         return buffer;
1258 }
1259
1260 static void strbuf_close( strbuf buffer )
1261 {
1262         // We do not free buffer->string; strbuf user must save that pointer
1263         // and free it.
1264         if ( buffer )
1265                 free( buffer );
1266 }
1267
1268 static char *strbuf_printf( strbuf buffer, const char *format, ... )
1269 {
1270         while ( buffer->string )
1271         {
1272                 va_list ap;
1273                 va_start( ap, format );
1274                 size_t len = strlen( buffer->string );
1275                 size_t remain = buffer->size - len - 1;
1276                 int need = vsnprintf( buffer->string + len, remain, format, ap );
1277                 va_end( ap );
1278                 if ( need > -1 && need < remain )
1279                         break;
1280                 buffer->string[ len ] = 0;
1281                 buffer->size += need + STRBUF_GROWTH;
1282                 buffer->string = realloc( buffer->string, buffer->size );
1283         }
1284         return buffer->string;
1285 }
1286
1287 static inline void indent_yaml( strbuf output, int indent )
1288 {
1289         int j;
1290         for ( j = 0; j < indent; j++ )
1291                 strbuf_printf( output, " " );
1292 }
1293
1294 static void output_yaml_block_literal( strbuf output, const char *value, int indent )
1295 {
1296         char *v = strdup( value );
1297         char *sol = v;
1298         char *eol = strchr( sol, '\n' );
1299         
1300         while ( eol )
1301         {
1302                 indent_yaml( output, indent );
1303                 *eol = '\0';
1304                 strbuf_printf( output, "%s\n", sol );
1305                 sol = eol + 1;
1306                 eol = strchr( sol, '\n' );
1307         }       
1308         indent_yaml( output, indent );
1309         strbuf_printf( output, "%s\n", sol );
1310 }
1311
1312 static void serialise_yaml( mlt_properties this, strbuf output, int indent, int is_parent_sequence )
1313 {
1314         property_list *list = this->local;
1315         int i = 0;
1316         
1317         for ( i = 0; i < list->count; i ++ )
1318         {
1319                 // This implementation assumes that all data elements are property lists.
1320                 // Unfortunately, we do not have run time type identification.
1321                 mlt_properties child = mlt_property_get_data( list->value[ i ], NULL );
1322                 
1323                 if ( mlt_properties_is_sequence( this ) )
1324                 {
1325                         // Ignore hidden/non-serialisable items
1326                         if ( list->name[ i ][ 0 ] != '_' )
1327                         {
1328                                 // Indicate a sequence item
1329                                 indent_yaml( output, indent );
1330                                 strbuf_printf( output, "- " );
1331                                 
1332                                 // If the value can be represented as a string
1333                                 const char *value = mlt_properties_get( this, list->name[ i ] );
1334                                 if ( value && strcmp( value, "" ) )
1335                                 {
1336                                         // Determine if this is an unfolded block literal
1337                                         if ( strchr( value, '\n' ) )
1338                                         {
1339                                                 strbuf_printf( output, "|\n" );
1340                                                 output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( "|" ) );
1341                                         }
1342                                         else
1343                                         {
1344                                                 strbuf_printf( output, "%s\n", value );
1345                                         }
1346                                 }
1347                         }
1348                         // Recurse on child
1349                         if ( child )
1350                                 serialise_yaml( child, output, indent + 2, 1 );
1351                 }
1352                 else 
1353                 {
1354                         // Assume this is a normal map-oriented properties list
1355                         const char *value = mlt_properties_get( this, list->name[ i ] );
1356                         
1357                         // Ignore hidden/non-serialisable items
1358                         // If the value can be represented as a string
1359                         if ( list->name[ i ][ 0 ] != '_' && value && strcmp( value, "" ) )
1360                         {
1361                                 if ( is_parent_sequence == 0 )
1362                                         indent_yaml( output, indent );
1363                                 else
1364                                         is_parent_sequence = 0;
1365                                 
1366                                 // Determine if this is an unfolded block literal
1367                                 if ( strchr( value, '\n' ) )
1368                                 {
1369                                         strbuf_printf( output, "%s: |\n", list->name[ i ] );
1370                                         output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( ": " ) );
1371                                 }
1372                                 else
1373                                 {
1374                                         strbuf_printf( output, "%s: %s\n", list->name[ i ], value );
1375                                 }
1376                         }
1377                         
1378                         // Output a child as a map item
1379                         if ( child )
1380                         {
1381                                 indent_yaml( output, indent );
1382                                 strbuf_printf( output, "%s:\n", list->name[ i ] );
1383
1384                                 // Recurse on child
1385                                 serialise_yaml( child, output, indent + 2, 0 );
1386                         }
1387                 }
1388         }
1389 }
1390
1391 /** Serialise the properties as a string of YAML Tiny.
1392     The caller must free the returned string.
1393 */
1394
1395 char *mlt_properties_serialise_yaml( mlt_properties this )
1396 {
1397         strbuf b = strbuf_new();
1398         strbuf_printf( b, "---\n" );
1399         serialise_yaml( this, b, 0, 0 );
1400         strbuf_printf( b, "...\n" );
1401         char *ret = b->string;
1402         strbuf_close( b );
1403         return ret;
1404 }