]> git.sesse.net Git - mlt/blob - src/framework/mlt_properties.c
a8cef7d163a88e7c5196664e3af52a8786dc95b0
[mlt] / src / framework / mlt_properties.c
1 /**
2  * \file mlt_properties.c
3  * \brief Properties class definition
4  * \see mlt_properties_s
5  *
6  * Copyright (C) 2003-2009 Ushodaya Enterprises Limited
7  * \author Charles Yates <charles.yates@pandora.be>
8  * \author Dan Dennedy <dan@dennedy.org>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25 #include "mlt_properties.h"
26 #include "mlt_property.h"
27 #include "mlt_deque.h"
28 #include "mlt_log.h"
29 #include "mlt_factory.h"
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <stdarg.h>
36 #include <pthread.h>
37 #include <sys/types.h>
38 #include <dirent.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <locale.h>
42 #include <float.h>
43
44 #define PRESETS_DIR "/presets"
45
46 /** \brief private implementation of the property list */
47
48 typedef struct
49 {
50         int hash[ 199 ];
51         char **name;
52         mlt_property *value;
53         int count;
54         int size;
55         mlt_properties mirror;
56         int ref_count;
57         pthread_mutex_t mutex;
58         locale_t locale;
59 }
60 property_list;
61
62 /* Memory leak checks */
63
64 //#define _MLT_PROPERTY_CHECKS_ 2
65 #ifdef _MLT_PROPERTY_CHECKS_
66 static int properties_created = 0;
67 static int properties_destroyed = 0;
68 #endif
69
70 /** Initialize a properties object that was already allocated.
71  *
72  * This does allocate its ::property_list, and it adds a reference count.
73  * \public \memberof mlt_properties_s
74  * \param self the properties structure to initialize
75  * \param child an opaque pointer to a subclass object
76  * \return true if failed
77  */
78
79 int mlt_properties_init( mlt_properties self, void *child )
80 {
81         if ( self != NULL )
82         {
83 #ifdef _MLT_PROPERTY_CHECKS_
84                 // Increment number of properties created
85                 properties_created ++;
86 #endif
87
88                 // NULL all methods
89                 memset( self, 0, sizeof( struct mlt_properties_s ) );
90
91                 // Assign the child of the object
92                 self->child = child;
93
94                 // Allocate the local structure
95                 self->local = calloc( 1, sizeof( property_list ) );
96
97                 // Increment the ref count
98                 ( ( property_list * )self->local )->ref_count = 1;
99                 pthread_mutex_init( &( ( property_list * )self->local )->mutex, NULL );;
100         }
101
102         // Check that initialisation was successful
103         return self != NULL && self->local == NULL;
104 }
105
106 /** Create a properties object.
107  *
108  * This allocates the properties structure and calls mlt_properties_init() on it.
109  * Free the properties object with mlt_properties_close().
110  * \public \memberof mlt_properties_s
111  * \return a new properties object
112  */
113
114 mlt_properties mlt_properties_new( )
115 {
116         // Construct a standalone properties object
117         mlt_properties self = calloc( 1, sizeof( struct mlt_properties_s ) );
118
119         // Initialise self
120         mlt_properties_init( self, NULL );
121
122         // Return the pointer
123         return self;
124 }
125
126 /** Set the numeric locale used for string/double conversions.
127  *
128  * \public \memberof mlt_properties_s
129  * \param self a properties list
130  * \param locale the locale name
131  * \return true if error
132  */
133
134 int mlt_properties_set_lcnumeric( mlt_properties self, const char *locale )
135 {
136         int error = 0;
137
138         if ( self && locale )
139         {
140                 property_list *list = self->local;
141
142 #if defined(__linux__) || defined(__DARWIN__)
143                 if ( list->locale )
144                         freelocale( list->locale );
145                 list->locale = newlocale( LC_NUMERIC_MASK, locale, NULL );
146 #endif
147                 error = list->locale == NULL;
148         }
149         else
150                 error = 1;
151
152         return error;
153 }
154
155 /** Get the numeric locale for this properties object.
156  *
157  * Do not free the result.
158  * \public \memberof mlt_properties_s
159  * \param self a properties list
160  * \return the locale name if this properties has a specific locale it is using, NULL otherwise
161  */
162
163 const char* mlt_properties_get_lcnumeric( mlt_properties self )
164 {
165         property_list *list = self->local;
166         const char *result = NULL;
167
168         if ( list->locale )
169         {
170 #if defined(__DARWIN__)
171                 result = querylocale( LC_NUMERIC, list->locale );
172 #elif defined(__linux__)
173                 result = list->locale->__names[ LC_NUMERIC ];
174 #else
175                 // TODO: not yet sure what to do on other platforms
176 #endif
177         }
178         return result;
179 }
180
181 static int load_properties( mlt_properties self, const char *filename )
182 {
183         // Open the file
184         FILE *file = fopen( filename, "r" );
185
186         // Load contents of file
187         if ( file != NULL )
188         {
189                 // Temp string
190                 char temp[ 1024 ];
191                 char last[ 1024 ] = "";
192
193                 // Read each string from the file
194                 while( fgets( temp, 1024, file ) )
195                 {
196                         // Chomp the new line character from the string
197                         int x = strlen( temp ) - 1;
198                         if ( temp[x] == '\n' || temp[x] == '\r' )
199                                 temp[x] = '\0';
200
201                         // Check if the line starts with a .
202                         if ( temp[ 0 ] == '.' )
203                         {
204                                 char temp2[ 1024 ];
205                                 sprintf( temp2, "%s%s", last, temp );
206                                 strcpy( temp, temp2 );
207                         }
208                         else if ( strchr( temp, '=' ) )
209                         {
210                                 strcpy( last, temp );
211                                 *( strchr( last, '=' ) ) = '\0';
212                         }
213
214                         // Parse and set the property
215                         if ( strcmp( temp, "" ) && temp[ 0 ] != '#' )
216                                 mlt_properties_parse( self, temp );
217                 }
218
219                 // Close the file
220                 fclose( file );
221         }
222         return file? 0 : errno;
223 }
224
225 /** Create a properties object by reading a .properties text file.
226  *
227  * Free the properties object with mlt_properties_close().
228  * \deprecated Please start using mlt_properties_parse_yaml().
229  * \public \memberof mlt_properties_s
230  * \param filename the absolute file name
231  * \return a new properties object
232  */
233
234 mlt_properties mlt_properties_load( const char *filename )
235 {
236         // Construct a standalone properties object
237         mlt_properties self = mlt_properties_new( );
238
239         if ( self != NULL )
240                 load_properties( self, filename );
241
242         // Return the pointer
243         return self;
244 }
245
246 /** Set properties from a preset.
247  *
248  * Presets are typically installed to $prefix/share/mlt/presets/{type}/{service}/[{profile}/]{name}.
249  * For example, "/usr/share/mlt/presets/consumer/avformat/dv_ntsc_wide/DVD"
250  * could be an encoding preset for a widescreen NTSC DVD Video.
251  * Do not specify the type and service in the preset name parameter; these are
252  * inferred automatically from the service to which you are applying the preset.
253  * Using the example above and assuming you are calling this function on the
254  * avformat consumer, the name passed to the function should simply be DVD.
255  * Note that the profile portion of the path is optional, but a profile-specific
256  * preset with the same name as a more generic one is given a higher priority.
257  * \todo Look in a user-specific location - somewhere in the home directory.
258  *
259  * \public \memberof mlt_properties_s
260  * \param self a properties list
261  * \param name the name of a preset in a well-known location or the explicit path
262  * \return true if error
263  */
264
265 int mlt_properties_preset( mlt_properties self, const char *name )
266 {
267         struct stat stat_buff;
268
269         // validate input
270         if ( !( self && name && strlen( name ) ) )
271                 return 1;
272
273         // See if name is an explicit file
274         if ( ! stat( name, &stat_buff ) )
275         {
276                 return load_properties( self, name );
277         }
278         else
279         {
280                 // Look for profile-specific preset before a generic one.
281                 char *data          = getenv( "MLT_PRESETS_PATH" );
282                 const char *type    = mlt_properties_get( self, "mlt_type" );
283                 const char *service = mlt_properties_get( self, "mlt_service" );
284                 const char *profile = mlt_environment( "MLT_PROFILE" );
285                 int error = 0;
286
287                 if ( data )
288                 {
289                         data = strdup( data );
290                 }
291                 else
292                 {
293                         data = malloc( strlen( mlt_environment( "MLT_DATA" ) ) + strlen( PRESETS_DIR ) + 1 );
294                         strcpy( data, mlt_environment( "MLT_DATA" ) );
295                         strcat( data, PRESETS_DIR );
296                 }
297                 if ( data && type && service )
298                 {
299                         char *path = malloc( 5 + strlen(name) + strlen(data) + strlen(type) + strlen(service) + ( profile? strlen(profile) : 0 ) );
300                         sprintf( path, "%s/%s/%s/%s/%s", data, type, service, profile, name );
301                         if ( load_properties( self, path ) )
302                         {
303                                 sprintf( path, "%s/%s/%s/%s", data, type, service, name );
304                                 error = load_properties( self, path );
305                         }
306                         free( path );
307                 }
308                 else
309                 {
310                         error = 1;
311                 }
312                 free( data );
313                 return error;
314         }
315 }
316
317 /** Generate a hash key.
318  *
319  * \private \memberof mlt_properties_s
320  * \param name a string
321  * \return an integer
322  */
323
324 static inline int generate_hash( const char *name )
325 {
326         int hash = 0;
327         int i = 1;
328         while ( *name )
329                 hash = ( hash + ( i ++ * ( *name ++ & 31 ) ) ) % 199;
330         return hash;
331 }
332
333 /** Copy a serializable property to a properties list that is mirroring this one.
334  *
335  * Special case - when a container (such as loader) is protecting another
336  * producer, we need to ensure that properties are passed through to the
337  * real producer.
338  * \private \memberof mlt_properties_s
339  * \param self a properties list
340  * \param name the name of the property to copy
341  */
342
343 static inline void mlt_properties_do_mirror( mlt_properties self, const char *name )
344 {
345         if ( !self ) return;
346         property_list *list = self->local;
347         if ( list->mirror != NULL )
348         {
349                 char *value = mlt_properties_get( self, name );
350                 if ( value != NULL )
351                         mlt_properties_set( list->mirror, name, value );
352         }
353 }
354
355 /** Increment the reference count.
356  *
357  * \public \memberof mlt_properties_s
358  * \param self a properties list
359  * \return the new reference count
360  */
361
362 int mlt_properties_inc_ref( mlt_properties self )
363 {
364         int result = 0;
365         if ( self != NULL )
366         {
367                 property_list *list = self->local;
368                 pthread_mutex_lock( &list->mutex );
369                 result = ++ list->ref_count;
370                 pthread_mutex_unlock( &list->mutex );
371         }
372         return result;
373 }
374
375 /** Decrement the reference count.
376  *
377  * \public \memberof mlt_properties_s
378  * \param self a properties list
379  * \return the new reference count
380  */
381
382 int mlt_properties_dec_ref( mlt_properties self )
383 {
384         int result = 0;
385         if ( self != NULL )
386         {
387                 property_list *list = self->local;
388                 pthread_mutex_lock( &list->mutex );
389                 result = -- list->ref_count;
390                 pthread_mutex_unlock( &list->mutex );
391         }
392         return result;
393 }
394
395 /** Get the reference count.
396  *
397  * \public \memberof mlt_properties_s
398  * \param self a properties list
399  * \return the current reference count
400  */
401
402 int mlt_properties_ref_count( mlt_properties self )
403 {
404         if ( self != NULL )
405         {
406                 property_list *list = self->local;
407                 return list->ref_count;
408         }
409         return 0;
410 }
411
412 /** Set a properties list to be a mirror copy of another.
413  *
414  * Note that this does not copy all existing properties. Rather, you must
415  * call this before setting the properties that you wish to copy.
416  * \public \memberof mlt_properties_s
417  * \param that the properties which will receive copies of the properties as they are set.
418  * \param self the properties to mirror
419  */
420
421 void mlt_properties_mirror( mlt_properties self, mlt_properties that )
422 {
423         if ( !self ) return;
424         property_list *list = self->local;
425         list->mirror = that;
426 }
427
428 /** Copy all serializable properties to another properties list.
429  *
430  * \public \memberof mlt_properties_s
431  * \param self The properties to copy to
432  * \param that The properties to copy from
433  * \return true if error
434  */
435
436 int mlt_properties_inherit( mlt_properties self, mlt_properties that )
437 {
438         if ( !self || !that ) return 1;
439         int count = mlt_properties_count( that );
440         int i = 0;
441         for ( i = 0; i < count; i ++ )
442         {
443                 char *value = mlt_properties_get_value( that, i );
444                 if ( value != NULL )
445                 {
446                         char *name = mlt_properties_get_name( that, i );
447                         mlt_properties_set( self, name, value );
448                 }
449         }
450         return 0;
451 }
452
453 /** Pass all serializable properties that match a prefix to another properties object
454  *
455  * \warning The prefix is stripped from the name when it is set on the \p self properties list!
456  * For example a property named "foo.bar" will match prefix "foo.", but the property
457  * will be named simply "bar" on the receiving properties object.
458  * \public \memberof mlt_properties_s
459  * \param self the properties to copy to
460  * \param that The properties to copy from
461  * \param prefix the property names to match (required)
462  * \return true if error
463  */
464
465 int mlt_properties_pass( mlt_properties self, mlt_properties that, const char *prefix )
466 {
467         if ( !self || !that ) return 1;
468         int count = mlt_properties_count( that );
469         int length = strlen( prefix );
470         int i = 0;
471         for ( i = 0; i < count; i ++ )
472         {
473                 char *name = mlt_properties_get_name( that, i );
474                 if ( !strncmp( name, prefix, length ) )
475                 {
476                         char *value = mlt_properties_get_value( that, i );
477                         if ( value != NULL )
478                                 mlt_properties_set( self, name + length, value );
479                 }
480         }
481         return 0;
482 }
483
484 /** Locate a property by name.
485  *
486  * \private \memberof mlt_properties_s
487  * \param self a properties list
488  * \param name the property to lookup by name
489  * \return the property or NULL for failure
490  */
491
492 static inline mlt_property mlt_properties_find( mlt_properties self, const char *name )
493 {
494         if ( !self || !name ) return NULL;
495         property_list *list = self->local;
496         mlt_property value = NULL;
497         int key = generate_hash( name );
498
499         mlt_properties_lock( self );
500
501         int i = list->hash[ key ] - 1;
502         if ( i >= 0 )
503         {
504                 // Check if we're hashed
505                 if ( list->count > 0 &&
506                         name[ 0 ] == list->name[ i ][ 0 ] &&
507                         !strcmp( list->name[ i ], name ) )
508                         value = list->value[ i ];
509
510                 // Locate the item
511                 for ( i = list->count - 1; value == NULL && i >= 0; i -- )
512                         if ( name[ 0 ] == list->name[ i ][ 0 ] && !strcmp( list->name[ i ], name ) )
513                                 value = list->value[ i ];
514         }
515         mlt_properties_unlock( self );
516
517         return value;
518 }
519
520 /** Add a new property.
521  *
522  * \private \memberof mlt_properties_s
523  * \param self a properties list
524  * \param name the name of the new property
525  * \return the new property
526  */
527
528 static mlt_property mlt_properties_add( mlt_properties self, const char *name )
529 {
530         property_list *list = self->local;
531         int key = generate_hash( name );
532         mlt_property result;
533
534         mlt_properties_lock( self );
535
536         // Check that we have space and resize if necessary
537         if ( list->count == list->size )
538         {
539                 list->size += 50;
540                 list->name = realloc( list->name, list->size * sizeof( const char * ) );
541                 list->value = realloc( list->value, list->size * sizeof( mlt_property ) );
542         }
543
544         // Assign name/value pair
545         list->name[ list->count ] = strdup( name );
546         list->value[ list->count ] = mlt_property_init( );
547
548         // Assign to hash table
549         if ( list->hash[ key ] == 0 )
550                 list->hash[ key ] = list->count + 1;
551
552         // Return and increment count accordingly
553         result = list->value[ list->count ++ ];
554
555         mlt_properties_unlock( self );
556
557         return result;
558 }
559
560 /** Fetch a property by name and add one if not found.
561  *
562  * \private \memberof mlt_properties_s
563  * \param self a properties list
564  * \param name the property to lookup or add
565  * \return the property
566  */
567
568 static mlt_property mlt_properties_fetch( mlt_properties self, const char *name )
569 {
570         // Try to find an existing property first
571         mlt_property property = mlt_properties_find( self, name );
572
573         // If it wasn't found, create one
574         if ( property == NULL )
575                 property = mlt_properties_add( self, name );
576
577         // Return the property
578         return property;
579 }
580
581 /** Copy a property to another properties list.
582  *
583  * \public \memberof mlt_properties_s
584  * \author Zach <zachary.drew@gmail.com>
585  * \param self the properties to copy to
586  * \param that the properties to copy from
587  * \param name the name of the property to copy
588  */
589
590 void mlt_properties_pass_property( mlt_properties self, mlt_properties that, const char *name )
591 {
592         // Make sure the source property isn't null.
593         mlt_property that_prop = mlt_properties_find( that, name );
594         if( that_prop == NULL )
595                 return;
596
597         mlt_property_pass( mlt_properties_fetch( self, name ), that_prop );
598 }
599
600 /** Copy all properties specified in a comma-separated list to another properties list.
601  *
602  * White space is also a delimiter.
603  * \public \memberof mlt_properties_s
604  * \author Zach <zachary.drew@gmail.com>
605  * \param self the properties to copy to
606  * \param that the properties to copy from
607  * \param list a delimited list of property names
608  * \return true if error
609  */
610
611
612 int mlt_properties_pass_list( mlt_properties self, mlt_properties that, const char *list )
613 {
614         if ( !self || !that || !list ) return 1;
615         char *props = strdup( list );
616         char *ptr = props;
617         const char *delim = " ,\t\n";   // Any combination of spaces, commas, tabs, and newlines
618         int count, done = 0;
619
620         while( !done )
621         {
622                 count = strcspn( ptr, delim );
623
624                 if( ptr[count] == '\0' )
625                         done = 1;
626                 else
627                         ptr[count] = '\0';      // Make it a real string
628
629                 mlt_properties_pass_property( self, that, ptr );
630
631                 ptr += count + 1;
632                 if ( !done )
633                         ptr += strspn( ptr, delim );
634         }
635
636         free( props );
637
638         return 0;
639 }
640
641
642 /** Set a property to a string.
643  *
644  * The property name "properties" is reserved to load the preset in \p value.
645  * When the value begins with '@' then it is interpreted as a very simple math
646  * expression containing only the +, -, *, and / operators.
647  * The event "property-changed" is fired after the property has been set.
648  *
649  * This makes a copy of the string value you supply.
650  * \public \memberof mlt_properties_s
651  * \param self a properties list
652  * \param name the property to set
653  * \param value the property's new value
654  * \return true if error
655  */
656
657 int mlt_properties_set( mlt_properties self, const char *name, const char *value )
658 {
659         int error = 1;
660
661         if ( !self || !name ) return error;
662
663         // Fetch the property to work with
664         mlt_property property = mlt_properties_fetch( self, name );
665
666         // Set it if not NULL
667         if ( property == NULL )
668         {
669                 mlt_log( NULL, MLT_LOG_FATAL, "Whoops - %s not found (should never occur)\n", name );
670         }
671         else if ( value == NULL )
672         {
673                 error = mlt_property_set_string( property, value );
674                 mlt_properties_do_mirror( self, name );
675         }
676         else if ( *value != '@' )
677         {
678                 error = mlt_property_set_string( property, value );
679                 mlt_properties_do_mirror( self, name );
680                 if ( !strcmp( name, "properties" ) )
681                         mlt_properties_preset( self, value );
682         }
683         else if ( value[ 0 ] == '@' )
684         {
685                 double total = 0;
686                 double current = 0;
687                 char id[ 255 ];
688                 char op = '+';
689
690                 value ++;
691
692                 while ( *value != '\0' )
693                 {
694                         int length = strcspn( value, "+-*/" );
695
696                         // Get the identifier
697                         strncpy( id, value, length );
698                         id[ length ] = '\0';
699                         value += length;
700
701                         // Determine the value
702                         if ( isdigit( id[ 0 ] ) )
703                         {
704 #if defined(__GLIBC__) || defined(__DARWIN__)
705                                 property_list *list = self->local;
706                                 if ( list->locale )
707                                         current = strtod_l( id, NULL, list->locale );
708 #endif
709                                 else
710                                         current = strtod( id, NULL );
711                         }
712                         else
713                         {
714                                 current = mlt_properties_get_double( self, id );
715                         }
716
717                         // Apply the operation
718                         switch( op )
719                         {
720                                 case '+':
721                                         total += current;
722                                         break;
723                                 case '-':
724                                         total -= current;
725                                         break;
726                                 case '*':
727                                         total *= current;
728                                         break;
729                                 case '/':
730                                         total = total / current;
731                                         break;
732                         }
733
734                         // Get the next op
735                         op = *value != '\0' ? *value ++ : ' ';
736                 }
737
738                 error = mlt_property_set_double( property, total );
739                 mlt_properties_do_mirror( self, name );
740         }
741
742         mlt_events_fire( self, "property-changed", name, NULL );
743
744         return error;
745 }
746
747 /** Set or default a property to a string.
748  *
749  * This makes a copy of the string value you supply.
750  * \public \memberof mlt_properties_s
751  * \param self a properties list
752  * \param name the property to set
753  * \param value the string value to set or NULL to use the default
754  * \param def the default string if value is NULL
755  * \return true if error
756  */
757
758 int mlt_properties_set_or_default( mlt_properties self, const char *name, const char *value, const char *def )
759 {
760         return mlt_properties_set( self, name, value == NULL ? def : value );
761 }
762
763 /** Get a string value by name.
764  *
765  * Do not free the returned string. It's lifetime is controlled by the property
766  * and this properties object.
767  * \public \memberof mlt_properties_s
768  * \param self a properties list
769  * \param name the property to get
770  * \return the property's string value or NULL if it does not exist
771  */
772
773 char *mlt_properties_get( mlt_properties self, const char *name )
774 {
775         mlt_property value = mlt_properties_find( self, name );
776         property_list *list = self->local;
777         return value == NULL ? NULL : mlt_property_get_string_l( value, list->locale );
778 }
779
780 /** Get a property name by index.
781  *
782  * Do not free the returned string.
783  * \public \memberof mlt_properties_s
784  * \param self a properties list
785  * \param index the numeric index of the property
786  * \return the name of the property or NULL if index is out of range
787  */
788
789 char *mlt_properties_get_name( mlt_properties self, int index )
790 {
791         if ( !self ) return NULL;
792         property_list *list = self->local;
793         if ( index >= 0 && index < list->count )
794                 return list->name[ index ];
795         return NULL;
796 }
797
798 /** Get a property's string value by index.
799  *
800  * Do not free the returned string.
801  * \public \memberof mlt_properties_s
802  * \param self a properties list
803  * \param index the numeric index of the property
804  * \return the property value as a string or NULL if the index is out of range
805  */
806
807 char *mlt_properties_get_value( mlt_properties self, int index )
808 {
809         if ( !self ) return NULL;
810         property_list *list = self->local;
811         if ( index >= 0 && index < list->count )
812                 return mlt_property_get_string_l( list->value[ index ], list->locale );
813         return NULL;
814 }
815
816 /** Get a data value by index.
817  *
818  * Do not free the returned pointer if you supplied a destructor function when you
819  * set this property.
820  * \public \memberof mlt_properties_s
821  * \param self a properties list
822  * \param index the numeric index of the property
823  * \param[out] size the size of the binary data in bytes or NULL if the index is out of range
824  */
825
826 void *mlt_properties_get_data_at( mlt_properties self, int index, int *size )
827 {
828         if ( !self ) return NULL;
829         property_list *list = self->local;
830         if ( index >= 0 && index < list->count )
831                 return mlt_property_get_data( list->value[ index ], size );
832         return NULL;
833 }
834
835 /** Return the number of items in the list.
836  *
837  * \public \memberof mlt_properties_s
838  * \param self a properties list
839  * \return the number of property objects or -1 if error
840  */
841
842 int mlt_properties_count( mlt_properties self )
843 {
844         if ( !self ) return -1;
845         property_list *list = self->local;
846         return list->count;
847 }
848
849 /** Set a value by parsing a name=value string.
850  *
851  * \public \memberof mlt_properties_s
852  * \param self a properties list
853  * \param namevalue a string containing name and value delimited by '='
854  * \return true if there was an error
855  */
856
857 int mlt_properties_parse( mlt_properties self, const char *namevalue )
858 {
859         if ( !self ) return 1;
860         char *name = strdup( namevalue );
861         char *value = NULL;
862         int error = 0;
863         char *ptr = strchr( name, '=' );
864
865         if ( ptr )
866         {
867                 *( ptr ++ ) = '\0';
868
869                 if ( *ptr != '\"' )
870                 {
871                         value = strdup( ptr );
872                 }
873                 else
874                 {
875                         ptr ++;
876                         value = strdup( ptr );
877                         if ( value != NULL && value[ strlen( value ) - 1 ] == '\"' )
878                                 value[ strlen( value ) - 1 ] = '\0';
879                 }
880         }
881         else
882         {
883                 value = strdup( "" );
884         }
885
886         error = mlt_properties_set( self, name, value );
887
888         free( name );
889         free( value );
890
891         return error;
892 }
893
894 /** Get an integer associated to the name.
895  *
896  * \public \memberof mlt_properties_s
897  * \param self a properties list
898  * \param name the property to get
899  * \return The integer value, 0 if not found (which may also be a legitimate value)
900  */
901
902 int mlt_properties_get_int( mlt_properties self, const char *name )
903 {
904         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
905         double fps = mlt_profile_fps( profile );
906         property_list *list = self->local;
907         mlt_property value = mlt_properties_find( self, name );
908         return value == NULL ? 0 : mlt_property_get_int( value, fps, list->locale );
909 }
910
911 /** Set a property to an integer value.
912  *
913  * \public \memberof mlt_properties_s
914  * \param self a properties list
915  * \param name the property to set
916  * \param value the integer
917  * \return true if error
918  */
919
920 int mlt_properties_set_int( mlt_properties self, const char *name, int value )
921 {
922         int error = 1;
923
924         if ( !self || !name ) return error;
925
926         // Fetch the property to work with
927         mlt_property property = mlt_properties_fetch( self, name );
928
929         // Set it if not NULL
930         if ( property != NULL )
931         {
932                 error = mlt_property_set_int( property, value );
933                 mlt_properties_do_mirror( self, name );
934         }
935
936         mlt_events_fire( self, "property-changed", name, NULL );
937
938         return error;
939 }
940
941 /** Get a 64-bit integer associated to the name.
942  *
943  * \public \memberof mlt_properties_s
944  * \param self a properties list
945  * \param name the property to get
946  * \return the integer value, 0 if not found (which may also be a legitimate value)
947  */
948
949 int64_t mlt_properties_get_int64( mlt_properties self, const char *name )
950 {
951         mlt_property value = mlt_properties_find( self, name );
952         return value == NULL ? 0 : mlt_property_get_int64( value );
953 }
954
955 /** Set a property to a 64-bit integer value.
956  *
957  * \public \memberof mlt_properties_s
958  * \param self a properties list
959  * \param name the property to set
960  * \param value the integer
961  * \return true if error
962  */
963
964 int mlt_properties_set_int64( mlt_properties self, const char *name, int64_t value )
965 {
966         int error = 1;
967
968         if ( !self || !name ) return error;
969
970         // Fetch the property to work with
971         mlt_property property = mlt_properties_fetch( self, name );
972
973         // Set it if not NULL
974         if ( property != NULL )
975         {
976                 error = mlt_property_set_int64( property, value );
977                 mlt_properties_do_mirror( self, name );
978         }
979
980         mlt_events_fire( self, "property-changed", name, NULL );
981
982         return error;
983 }
984
985 /** Get a floating point value associated to the name.
986  *
987  * \public \memberof mlt_properties_s
988  * \param self a properties list
989  * \param name the property to get
990  * \return the floating point, 0 if not found (which may also be a legitimate value)
991  */
992
993 double mlt_properties_get_double( mlt_properties self, const char *name )
994 {
995         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
996         double fps = mlt_profile_fps( profile );
997         mlt_property value = mlt_properties_find( self, name );
998         property_list *list = self->local;
999         return value == NULL ? 0 : mlt_property_get_double( value, fps, list->locale );
1000 }
1001
1002 /** Set a property to a floating point value.
1003  *
1004  * \public \memberof mlt_properties_s
1005  * \param self a properties list
1006  * \param name the property to set
1007  * \param value the floating point value
1008  * \return true if error
1009  */
1010
1011 int mlt_properties_set_double( mlt_properties self, const char *name, double value )
1012 {
1013         int error = 1;
1014
1015         if ( !self || !name ) return error;
1016
1017         // Fetch the property to work with
1018         mlt_property property = mlt_properties_fetch( self, name );
1019
1020         // Set it if not NULL
1021         if ( property != NULL )
1022         {
1023                 error = mlt_property_set_double( property, value );
1024                 mlt_properties_do_mirror( self, name );
1025         }
1026
1027         mlt_events_fire( self, "property-changed", name, NULL );
1028
1029         return error;
1030 }
1031
1032 /** Get a position value associated to the name.
1033  *
1034  * \public \memberof mlt_properties_s
1035  * \param self a properties list
1036  * \param name the property to get
1037  * \return the position, 0 if not found (which may also be a legitimate value)
1038  */
1039
1040 mlt_position mlt_properties_get_position( mlt_properties self, const char *name )
1041 {
1042         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
1043         double fps = mlt_profile_fps( profile );
1044         property_list *list = self->local;
1045         mlt_property value = mlt_properties_find( self, name );
1046         return value == NULL ? 0 : mlt_property_get_position( value, fps, list->locale );
1047 }
1048
1049 /** Set a property to a position value.
1050  *
1051  * \public \memberof mlt_properties_s
1052  * \param self a properties list
1053  * \param name the property to get
1054  * \param value the position
1055  * \return true if error
1056  */
1057
1058 int mlt_properties_set_position( mlt_properties self, const char *name, mlt_position value )
1059 {
1060         int error = 1;
1061
1062         if ( !self || !name ) return error;
1063
1064         // Fetch the property to work with
1065         mlt_property property = mlt_properties_fetch( self, name );
1066
1067         // Set it if not NULL
1068         if ( property != NULL )
1069         {
1070                 error = mlt_property_set_position( property, value );
1071                 mlt_properties_do_mirror( self, name );
1072         }
1073
1074         mlt_events_fire( self, "property-changed", name, NULL );
1075
1076         return error;
1077 }
1078
1079 /** Get a binary data value associated to the name.
1080  *
1081  * Do not free the returned pointer if you supplied a destructor function
1082  * when you set this property.
1083  * \public \memberof mlt_properties_s
1084  * \param self a properties list
1085  * \param name the property to get
1086  * \param[out] length The size of the binary data in bytes, if available (often it is not, you should know)
1087  */
1088
1089 void *mlt_properties_get_data( mlt_properties self, const char *name, int *length )
1090 {
1091         mlt_property value = mlt_properties_find( self, name );
1092         return value == NULL ? NULL : mlt_property_get_data( value, length );
1093 }
1094
1095 /** Store binary data as a property.
1096  *
1097  * \public \memberof mlt_properties_s
1098  * \param self a properties list
1099  * \param name the property to set
1100  * \param value an opaque pointer to binary data
1101  * \param length the size of the binary data in bytes (optional)
1102  * \param destroy a function to deallocate the binary data when the property is closed (optional)
1103  * \param serialise a function that can serialize the binary data as text (optional)
1104  * \return true if error
1105  */
1106
1107 int mlt_properties_set_data( mlt_properties self, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise )
1108 {
1109         int error = 1;
1110
1111         if ( !self || !name ) return error;
1112
1113         // Fetch the property to work with
1114         mlt_property property = mlt_properties_fetch( self, name );
1115
1116         // Set it if not NULL
1117         if ( property != NULL )
1118                 error = mlt_property_set_data( property, value, length, destroy, serialise );
1119
1120         mlt_events_fire( self, "property-changed", name, NULL );
1121
1122         return error;
1123 }
1124
1125 /** Rename a property.
1126  *
1127  * \public \memberof mlt_properties_s
1128  * \param self a properties list
1129  * \param source the property to rename
1130  * \param dest the new name
1131  * \return true if the name is already in use
1132  */
1133
1134 int mlt_properties_rename( mlt_properties self, const char *source, const char *dest )
1135 {
1136         mlt_property value = mlt_properties_find( self, dest );
1137
1138         if ( value == NULL )
1139         {
1140                 property_list *list = self->local;
1141                 int i = 0;
1142
1143                 // Locate the item
1144                 mlt_properties_lock( self );
1145                 for ( i = 0; i < list->count; i ++ )
1146                 {
1147                         if ( !strcmp( list->name[ i ], source ) )
1148                         {
1149                                 free( list->name[ i ] );
1150                                 list->name[ i ] = strdup( dest );
1151                                 list->hash[ generate_hash( dest ) ] = i + 1;
1152                                 break;
1153                         }
1154                 }
1155                 mlt_properties_unlock( self );
1156         }
1157
1158         return value != NULL;
1159 }
1160
1161 /** Dump the properties to a file handle.
1162  *
1163  * \public \memberof mlt_properties_s
1164  * \param self a properties list
1165  * \param output a file handle
1166  */
1167
1168 void mlt_properties_dump( mlt_properties self, FILE *output )
1169 {
1170         if ( !self || !output ) return;
1171         property_list *list = self->local;
1172         int i = 0;
1173         for ( i = 0; i < list->count; i ++ )
1174                 if ( mlt_properties_get( self, list->name[ i ] ) != NULL )
1175                         fprintf( output, "%s=%s\n", list->name[ i ], mlt_properties_get( self, list->name[ i ] ) );
1176 }
1177
1178 /** Output the properties to a file handle.
1179  *
1180  * This version includes reference counts and does not put each property on a new line.
1181  * \public \memberof mlt_properties_s
1182  * \param self a properties pointer
1183  * \param title a string to preface the output
1184  * \param output a file handle
1185  */
1186 void mlt_properties_debug( mlt_properties self, const char *title, FILE *output )
1187 {
1188         if ( !self || !output ) return;
1189         if ( output == NULL ) output = stderr;
1190         fprintf( output, "%s: ", title );
1191         if ( self != NULL )
1192         {
1193                 property_list *list = self->local;
1194                 int i = 0;
1195                 fprintf( output, "[ ref=%d", list->ref_count );
1196                 for ( i = 0; i < list->count; i ++ )
1197                         if ( mlt_properties_get( self, list->name[ i ] ) != NULL )
1198                                 fprintf( output, ", %s=%s", list->name[ i ], mlt_properties_get( self, list->name[ i ] ) );
1199                         else
1200                                 fprintf( output, ", %s=%p", list->name[ i ], mlt_properties_get_data( self, list->name[ i ], NULL ) );
1201                 fprintf( output, " ]" );
1202         }
1203         fprintf( output, "\n" );
1204 }
1205
1206 /** Save the properties to a file by name.
1207  *
1208  * This uses the dump format - one line per property.
1209  * \public \memberof mlt_properties_s
1210  * \param self a properties list
1211  * \param filename the name of a file to create or overwrite
1212  * \return true if there was an error
1213  */
1214
1215 int mlt_properties_save( mlt_properties self, const char *filename )
1216 {
1217         int error = 1;
1218         if ( !self || !filename ) return error;
1219         FILE *f = fopen( filename, "w" );
1220         if ( f != NULL )
1221         {
1222                 mlt_properties_dump( self, f );
1223                 fclose( f );
1224                 error = 0;
1225         }
1226         return error;
1227 }
1228
1229 /* This is a very basic cross platform fnmatch replacement - it will fail in
1230  * many cases, but for the basic *.XXX and YYY*.XXX, it will work ok.
1231  */
1232
1233 /** Test whether a filename or pathname matches a shell-style pattern.
1234  *
1235  * \private \memberof mlt_properties_s
1236  * \param wild a string containing a wildcard pattern
1237  * \param file the name of a file to test against
1238  * \return true if the file name matches the wildcard pattern
1239  */
1240
1241 static int mlt_fnmatch( const char *wild, const char *file )
1242 {
1243         int f = 0;
1244         int w = 0;
1245
1246         while( f < strlen( file ) && w < strlen( wild ) )
1247         {
1248                 if ( wild[ w ] == '*' )
1249                 {
1250                         w ++;
1251                         if ( w == strlen( wild ) )
1252                                 f = strlen( file );
1253                         while ( f != strlen( file ) && tolower( file[ f ] ) != tolower( wild[ w ] ) )
1254                                 f ++;
1255                 }
1256                 else if ( wild[ w ] == '?' || tolower( file[ f ] ) == tolower( wild[ w ] ) )
1257                 {
1258                         f ++;
1259                         w ++;
1260                 }
1261                 else if ( wild[ 0 ] == '*' )
1262                 {
1263                         w = 0;
1264                 }
1265                 else
1266                 {
1267                         return 0;
1268                 }
1269         }
1270
1271         return strlen( file ) == f &&  strlen( wild ) == w;
1272 }
1273
1274 /** Compare the string or serialized value of two properties.
1275  *
1276  * \private \memberof mlt_properties_s
1277  * \param self a property
1278  * \param that a property
1279  * \return < 0 if \p self less than \p that, 0 if equal, or > 0 if \p self is greater than \p that
1280  */
1281
1282 static int mlt_compare( const void *self, const void *that )
1283 {
1284     return strcmp( mlt_property_get_string( *( const mlt_property * )self ), mlt_property_get_string( *( const mlt_property * )that ) );
1285 }
1286
1287 /** Get the contents of a directory.
1288  *
1289  * Obtains an optionally sorted list of the files found in a directory with a specific wild card.
1290  * Entries in the list have a numeric name (running from 0 to count - 1). Only values change
1291  * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc).
1292  * \public \memberof mlt_properties_s
1293  * \param self a properties list
1294  * \param dirname the name of the directory
1295  * \param pattern a wildcard pattern to filter the directory listing
1296  * \param sort Do you want to sort the directory listing?
1297  * \return the number of items in the directory listing
1298  */
1299
1300 int mlt_properties_dir_list( mlt_properties self, const char *dirname, const char *pattern, int sort )
1301 {
1302         DIR *dir = opendir( dirname );
1303
1304         if ( dir )
1305         {
1306                 char key[ 20 ];
1307                 struct dirent *de = readdir( dir );
1308                 char fullname[ 1024 ];
1309                 while( de != NULL )
1310                 {
1311                         sprintf( key, "%d", mlt_properties_count( self ) );
1312                         snprintf( fullname, 1024, "%s/%s", dirname, de->d_name );
1313                         if ( pattern == NULL )
1314                                 mlt_properties_set( self, key, fullname );
1315                         else if ( de->d_name[ 0 ] != '.' && mlt_fnmatch( pattern, de->d_name ) )
1316                                 mlt_properties_set( self, key, fullname );
1317                         de = readdir( dir );
1318                 }
1319
1320                 closedir( dir );
1321         }
1322
1323         if ( sort && mlt_properties_count( self ) )
1324         {
1325                 property_list *list = self->local;
1326                 mlt_properties_lock( self );
1327                 qsort( list->value, mlt_properties_count( self ), sizeof( mlt_property ), mlt_compare );
1328                 mlt_properties_unlock( self );
1329         }
1330
1331         return mlt_properties_count( self );
1332 }
1333
1334 /** Close a properties object.
1335  *
1336  * Deallocates the properties object and everything it contains.
1337  * \public \memberof mlt_properties_s
1338  * \param self a properties object
1339  */
1340
1341 void mlt_properties_close( mlt_properties self )
1342 {
1343         if ( self != NULL && mlt_properties_dec_ref( self ) <= 0 )
1344         {
1345                 if ( self->close != NULL )
1346                 {
1347                         self->close( self->close_object );
1348                 }
1349                 else
1350                 {
1351                         property_list *list = self->local;
1352                         int index = 0;
1353
1354 #if _MLT_PROPERTY_CHECKS_ == 1
1355                         // Show debug info
1356                         mlt_properties_debug( self, "Closing", stderr );
1357 #endif
1358
1359 #ifdef _MLT_PROPERTY_CHECKS_
1360                         // Increment destroyed count
1361                         properties_destroyed ++;
1362
1363                         // Show current stats - these should match when the app is closed
1364                         mlt_log( NULL, MLT_LOG_DEBUG, "Created %d, destroyed %d\n", properties_created, properties_destroyed );
1365 #endif
1366
1367                         // Clean up names and values
1368                         for ( index = list->count - 1; index >= 0; index -- )
1369                         {
1370                                 mlt_property_close( list->value[ index ] );
1371                                 free( list->name[ index ] );
1372                         }
1373
1374 #if defined(__linux__) || defined(__DARWIN__)
1375                         // Cleanup locale
1376                         if ( list->locale )
1377                                 freelocale( list->locale );
1378 #endif
1379
1380                         // Clear up the list
1381                         pthread_mutex_destroy( &list->mutex );
1382                         free( list->name );
1383                         free( list->value );
1384                         free( list );
1385
1386                         // Free self now if self has no child
1387                         if ( self->child == NULL )
1388                                 free( self );
1389                 }
1390         }
1391 }
1392
1393 /** Determine if the properties list is really just a sequence or ordered list.
1394  *
1395  * \public \memberof mlt_properties_s
1396  * \param properties a properties list
1397  * \return true if all of the property names are numeric (a sequence)
1398  */
1399
1400 int mlt_properties_is_sequence( mlt_properties properties )
1401 {
1402         int i;
1403         int n = mlt_properties_count( properties );
1404         for ( i = 0; i < n; i++ )
1405                 if ( ! isdigit( mlt_properties_get_name( properties, i )[0] ) )
1406                         return 0;
1407         return 1;
1408 }
1409
1410 /** \brief YAML Tiny Parser context structure
1411  *
1412  * YAML is a nifty text format popular in the Ruby world as a cleaner,
1413  * less verbose alternative to XML. See this Wikipedia topic for an overview:
1414  * http://en.wikipedia.org/wiki/YAML
1415  * The YAML specification is at:
1416  * http://yaml.org/
1417  * YAML::Tiny is a Perl module that specifies a subset of YAML that we are
1418  * using here (for the same reasons):
1419  * http://search.cpan.org/~adamk/YAML-Tiny-1.25/lib/YAML/Tiny.pm
1420  * \private
1421  */
1422
1423 struct yaml_parser_context
1424 {
1425         mlt_deque stack;
1426         unsigned int level;
1427         int index;
1428         mlt_deque index_stack;
1429         char block;
1430         char *block_name;
1431         unsigned int block_indent;
1432
1433 };
1434 typedef struct yaml_parser_context *yaml_parser;
1435
1436 /** Remove spaces from the left side of a string.
1437  *
1438  * \param s the string to trim
1439  * \return the number of characters removed
1440  */
1441
1442 static unsigned int ltrim( char **s )
1443 {
1444         unsigned int i = 0;
1445         char *c = *s;
1446         int n = strlen( c );
1447         for ( i = 0; i < n && *c == ' '; i++, c++ );
1448         *s = c;
1449         return i;
1450 }
1451
1452 /** Remove spaces from the right side of a string.
1453  *
1454  * \param s the string to trim
1455  * \return the number of characters removed
1456  */
1457
1458 static unsigned int rtrim( char *s )
1459 {
1460         int n = strlen( s );
1461         int i;
1462         for ( i = n; i > 0 && s[i - 1] == ' '; --i )
1463                 s[i - 1] = 0;
1464         return n - i;
1465 }
1466
1467 /** Parse a line of YAML Tiny.
1468  *
1469  * Adds a property if needed.
1470  * \private \memberof yaml_parser_context
1471  * \param context a YAML Tiny Parser context
1472  * \param namevalue a line of YAML Tiny
1473  * \return true if there was an error
1474  */
1475
1476 static int parse_yaml( yaml_parser context, const char *namevalue )
1477 {
1478         char *name_ = strdup( namevalue );
1479         char *name = name_;
1480         char *value = NULL;
1481         int error = 0;
1482         char *ptr = strchr( name, ':' );
1483         unsigned int indent = ltrim( &name );
1484         mlt_properties properties = mlt_deque_peek_back( context->stack );
1485
1486         // Ascending one more levels in the tree
1487         if ( indent < context->level )
1488         {
1489                 unsigned int i;
1490                 unsigned int n = ( context->level - indent ) / 2;
1491                 for ( i = 0; i < n; i++ )
1492                 {
1493                         mlt_deque_pop_back( context->stack );
1494                         context->index = mlt_deque_pop_back_int( context->index_stack );
1495                 }
1496                 properties = mlt_deque_peek_back( context->stack );
1497                 context->level = indent;
1498         }
1499
1500         // Descending a level in the tree
1501         else if ( indent > context->level && context->block == 0 )
1502         {
1503                 context->level = indent;
1504         }
1505
1506         // If there is a colon that is not part of a block
1507         if ( ptr && ( indent == context->level ) )
1508         {
1509                 // Reset block processing
1510                 if ( context->block_name )
1511                 {
1512                         free( context->block_name );
1513                         context->block_name = NULL;
1514                         context->block = 0;
1515                 }
1516
1517                 // Terminate the name and setup the value pointer
1518                 *( ptr ++ ) = 0;
1519
1520                 // Trim comment
1521                 char *comment = strchr( ptr, '#' );
1522                 if ( comment )
1523                 {
1524                         *comment = 0;
1525                 }
1526
1527                 // Trim leading and trailing spaces from bare value
1528                 ltrim( &ptr );
1529                 rtrim( ptr );
1530
1531                 // No value means a child
1532                 if ( strcmp( ptr, "" ) == 0 )
1533                 {
1534                         mlt_properties child = mlt_properties_new();
1535                         mlt_properties_set_lcnumeric( child, mlt_properties_get_lcnumeric( properties ) );
1536                         mlt_properties_set_data( properties, name, child, 0,
1537                                 ( mlt_destructor )mlt_properties_close, NULL );
1538                         mlt_deque_push_back( context->stack, child );
1539                         mlt_deque_push_back_int( context->index_stack, context->index );
1540                         context->index = 0;
1541                         free( name_ );
1542                         return error;
1543                 }
1544
1545                 // A dash indicates a sequence item
1546                 if ( name[0] == '-' )
1547                 {
1548                         mlt_properties child = mlt_properties_new();
1549                         char key[20];
1550
1551                         mlt_properties_set_lcnumeric( child, mlt_properties_get_lcnumeric( properties ) );
1552                         snprintf( key, sizeof(key), "%d", context->index++ );
1553                         mlt_properties_set_data( properties, key, child, 0,
1554                                 ( mlt_destructor )mlt_properties_close, NULL );
1555                         mlt_deque_push_back( context->stack, child );
1556                         mlt_deque_push_back_int( context->index_stack, context->index );
1557
1558                         name ++;
1559                         context->level += ltrim( &name ) + 1;
1560                         properties = child;
1561                 }
1562
1563                 // Value is quoted
1564                 if ( *ptr == '\"' )
1565                 {
1566                         ptr ++;
1567                         value = strdup( ptr );
1568                         if ( value && value[ strlen( value ) - 1 ] == '\"' )
1569                                 value[ strlen( value ) - 1 ] = 0;
1570                 }
1571
1572                 // Value is folded or unfolded block
1573                 else if ( *ptr == '|' || *ptr == '>' )
1574                 {
1575                         context->block = *ptr;
1576                         context->block_name = strdup( name );
1577                         context->block_indent = 0;
1578                         value = strdup( "" );
1579                 }
1580
1581                 // Bare value
1582                 else
1583                 {
1584                         value = strdup( ptr );
1585                 }
1586         }
1587
1588         // A list of scalars
1589         else if ( name[0] == '-' )
1590         {
1591                 // Reset block processing
1592                 if ( context->block_name )
1593                 {
1594                         free( context->block_name );
1595                         context->block_name = NULL;
1596                         context->block = 0;
1597                 }
1598
1599                 char key[20];
1600
1601                 snprintf( key, sizeof(key), "%d", context->index++ );
1602                 ptr = name + 1;
1603
1604                 // Trim comment
1605                 char *comment = strchr( ptr, '#' );
1606                 if ( comment )
1607                         *comment = 0;
1608
1609                 // Trim leading and trailing spaces from bare value
1610                 ltrim( &ptr );
1611                 rtrim( ptr );
1612
1613                 // Value is quoted
1614                 if ( *ptr == '\"' )
1615                 {
1616                         ptr ++;
1617                         value = strdup( ptr );
1618                         if ( value && value[ strlen( value ) - 1 ] == '\"' )
1619                                 value[ strlen( value ) - 1 ] = 0;
1620                 }
1621
1622                 // Value is folded or unfolded block
1623                 else if ( *ptr == '|' || *ptr == '>' )
1624                 {
1625                         context->block = *ptr;
1626                         context->block_name = strdup( key );
1627                         context->block_indent = 0;
1628                         value = strdup( "" );
1629                 }
1630
1631                 // Bare value
1632                 else
1633                 {
1634                         value = strdup( ptr );
1635                 }
1636
1637                 free( name_ );
1638                 name = name_ = strdup( key );
1639         }
1640
1641         // Non-folded block
1642         else if ( context->block == '|' )
1643         {
1644                 if ( context->block_indent == 0 )
1645                         context->block_indent = indent;
1646                 if ( indent > context->block_indent )
1647                         name = &name_[ context->block_indent ];
1648                 rtrim( name );
1649                 char *old_value = mlt_properties_get( properties, context->block_name );
1650                 value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1651                 strcpy( value, old_value );
1652                 if ( strcmp( old_value, "" ) )
1653                         strcat( value, "\n" );
1654                 strcat( value, name );
1655                 name = context->block_name;
1656         }
1657
1658         // Folded block
1659         else if ( context->block == '>' )
1660         {
1661                 ltrim( &name );
1662                 rtrim( name );
1663                 char *old_value = mlt_properties_get( properties, context->block_name );
1664
1665                 // Blank line (prepended with spaces) is new line
1666                 if ( strcmp( name, "" ) == 0 )
1667                 {
1668                         value = calloc( 1, strlen( old_value ) + 2 );
1669                         strcat( value, old_value );
1670                         strcat( value, "\n" );
1671                 }
1672                 // Concatenate with space
1673                 else
1674                 {
1675                         value = calloc( 1, strlen( old_value ) + strlen( name ) + 2 );
1676                         strcat( value, old_value );
1677                         if ( strcmp( old_value, "" ) && old_value[ strlen( old_value ) - 1 ] != '\n' )
1678                                 strcat( value, " " );
1679                         strcat( value, name );
1680                 }
1681                 name = context->block_name;
1682         }
1683
1684         else
1685         {
1686                 value = strdup( "" );
1687         }
1688
1689         error = mlt_properties_set( properties, name, value );
1690
1691         if ( !strcmp( name, "LC_NUMERIC" ) )
1692                 mlt_properties_set_lcnumeric( properties, value );
1693
1694         free( name_ );
1695         free( value );
1696
1697         return error;
1698 }
1699
1700 /** Parse a YAML Tiny file by name.
1701  *
1702  * \public \memberof mlt_properties_s
1703  * \param filename the name of a text file containing YAML Tiny
1704  * \return a new properties list
1705  */
1706
1707 mlt_properties mlt_properties_parse_yaml( const char *filename )
1708 {
1709         // Construct a standalone properties object
1710         mlt_properties self = mlt_properties_new( );
1711
1712         if ( self )
1713         {
1714                 // Open the file
1715                 FILE *file = fopen( filename, "r" );
1716
1717                 // Load contents of file
1718                 if ( file )
1719                 {
1720                         // Temp string
1721                         char temp[ 1024 ];
1722                         char *ptemp = &temp[ 0 ];
1723
1724                         // Default to LC_NUMERIC = C
1725                         mlt_properties_set_lcnumeric( self, "C" );
1726
1727                         // Parser context
1728                         yaml_parser context = calloc( 1, sizeof( struct yaml_parser_context ) );
1729                         context->stack = mlt_deque_init();
1730                         context->index_stack = mlt_deque_init();
1731                         mlt_deque_push_back( context->stack, self );
1732                         mlt_deque_push_back_int( context->index_stack, 0 );
1733
1734                         // Read each string from the file
1735                         while( fgets( temp, 1024, file ) )
1736                         {
1737                                 // Check for end-of-stream
1738                                 if ( strncmp( ptemp, "...", 3 ) == 0 )
1739                                         break;
1740
1741                                 // Chomp the string
1742                                 temp[ strlen( temp ) - 1 ] = '\0';
1743
1744                                 // Skip blank lines, comment lines, and document separator
1745                                 if ( strcmp( ptemp, "" ) && ptemp[ 0 ] != '#' && strncmp( ptemp, "---", 3 )
1746                                      && strncmp( ptemp, "%YAML", 5 ) && strncmp( ptemp, "% YAML", 6 ) )
1747                                         parse_yaml( context, temp );
1748                         }
1749
1750                         // Close the file
1751                         fclose( file );
1752                         mlt_deque_close( context->stack );
1753                         mlt_deque_close( context->index_stack );
1754                         if ( context->block_name )
1755                                 free( context->block_name );
1756                         free( context );
1757                 }
1758         }
1759
1760         // Return the pointer
1761         return self;
1762 }
1763
1764 /*
1765  * YAML Tiny Serializer
1766  */
1767
1768 /** How many bytes to grow at a time */
1769 #define STRBUF_GROWTH (1024)
1770
1771 /** \brief Private to mlt_properties_s, a self-growing buffer for building strings
1772  * \private
1773  */
1774
1775 struct strbuf_s
1776 {
1777         size_t size;
1778         char *string;
1779 };
1780
1781 typedef struct strbuf_s *strbuf;
1782
1783 /** Create a new string buffer
1784  *
1785  * \private \memberof strbuf_s
1786  * \return a new string buffer
1787  */
1788
1789 static strbuf strbuf_new( )
1790 {
1791         strbuf buffer = calloc( 1, sizeof( struct strbuf_s ) );
1792         buffer->size = STRBUF_GROWTH;
1793         buffer->string = calloc( 1, buffer->size );
1794         return buffer;
1795 }
1796
1797 /** Destroy a string buffer
1798  *
1799  * \private \memberof strbuf_s
1800  * \param buffer the string buffer to close
1801  */
1802
1803 static void strbuf_close( strbuf buffer )
1804 {
1805         // We do not free buffer->string; strbuf user must save that pointer
1806         // and free it.
1807         if ( buffer )
1808                 free( buffer );
1809 }
1810
1811 /** Format a string into a string buffer
1812  *
1813  * A variable number of arguments follows the format string - one for each
1814  * format specifier.
1815  * \private \memberof strbuf_s
1816  * \param buffer the string buffer to write into
1817  * \param format a string that contains text and formatting instructions
1818  * \return the formatted string
1819  */
1820
1821 static char *strbuf_printf( strbuf buffer, const char *format, ... )
1822 {
1823         while ( buffer->string )
1824         {
1825                 va_list ap;
1826                 va_start( ap, format );
1827                 size_t len = strlen( buffer->string );
1828                 size_t remain = buffer->size - len - 1;
1829                 int need = vsnprintf( buffer->string + len, remain, format, ap );
1830                 va_end( ap );
1831                 if ( need > -1 && need < remain )
1832                         break;
1833                 buffer->string[ len ] = 0;
1834                 buffer->size += need + STRBUF_GROWTH;
1835                 buffer->string = realloc( buffer->string, buffer->size );
1836         }
1837         return buffer->string;
1838 }
1839
1840 /** Indent a line of YAML Tiny.
1841  *
1842  * \private \memberof strbuf_s
1843  * \param output a string buffer
1844  * \param indent the number of spaces to indent
1845  */
1846
1847 static inline void indent_yaml( strbuf output, int indent )
1848 {
1849         int j;
1850         for ( j = 0; j < indent; j++ )
1851                 strbuf_printf( output, " " );
1852 }
1853
1854 static void strbuf_escape( strbuf output, const char *value, char c )
1855 {
1856         char *v = strdup( value );
1857         char *s = v;
1858         char *found = strchr( s, c );
1859
1860         while ( found )
1861         {
1862                 *found = '\0';
1863                 strbuf_printf( output, "%s\\%c", s, c );
1864                 s = found + 1;
1865                 found = strchr( s, c );
1866         }
1867         strbuf_printf( output, "%s", s );
1868         free( v );
1869 }
1870
1871 /** Convert a line string into a YAML block literal.
1872  *
1873  * \private \memberof strbuf_s
1874  * \param output a string buffer
1875  * \param value the string to format as a block literal
1876  * \param indent the number of spaces to indent
1877  */
1878
1879 static void output_yaml_block_literal( strbuf output, const char *value, int indent )
1880 {
1881         char *v = strdup( value );
1882         char *sol = v;
1883         char *eol = strchr( sol, '\n' );
1884
1885         while ( eol )
1886         {
1887                 indent_yaml( output, indent );
1888                 *eol = '\0';
1889                 strbuf_printf( output, "%s\n", sol );
1890                 sol = eol + 1;
1891                 eol = strchr( sol, '\n' );
1892         }
1893         indent_yaml( output, indent );
1894         strbuf_printf( output, "%s\n", sol );
1895         free( v );
1896 }
1897
1898 /** Recursively serialize a properties list into a string buffer as YAML Tiny.
1899  *
1900  * \private \memberof mlt_properties_s
1901  * \param self a properties list
1902  * \param output a string buffer to hold the serialized YAML Tiny
1903  * \param indent the number of spaces to indent (for recursion, initialize to 0)
1904  * \param is_parent_sequence Is this properties list really just a sequence (for recursion, initialize to 0)?
1905  */
1906
1907 static void serialise_yaml( mlt_properties self, strbuf output, int indent, int is_parent_sequence )
1908 {
1909         property_list *list = self->local;
1910         int i = 0;
1911
1912         for ( i = 0; i < list->count; i ++ )
1913         {
1914                 // This implementation assumes that all data elements are property lists.
1915                 // Unfortunately, we do not have run time type identification.
1916                 mlt_properties child = mlt_property_get_data( list->value[ i ], NULL );
1917
1918                 if ( mlt_properties_is_sequence( self ) )
1919                 {
1920                         // Ignore hidden/non-serialisable items
1921                         if ( list->name[ i ][ 0 ] != '_' )
1922                         {
1923                                 // Indicate a sequence item
1924                                 indent_yaml( output, indent );
1925                                 strbuf_printf( output, "- " );
1926
1927                                 // If the value can be represented as a string
1928                                 const char *value = mlt_properties_get( self, list->name[ i ] );
1929                                 if ( value && strcmp( value, "" ) )
1930                                 {
1931                                         // Determine if this is an unfolded block literal
1932                                         if ( strchr( value, '\n' ) )
1933                                         {
1934                                                 strbuf_printf( output, "|\n" );
1935                                                 output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( "|" ) );
1936                                         }
1937                                         else if ( strchr( value, ':' ) || strchr( value, '[' ) )
1938                                         {
1939                                                 strbuf_printf( output, "\"" );
1940                                                 strbuf_escape( output, value, '"' );
1941                                                 strbuf_printf( output, "\"\n", value );
1942                                         }
1943                                         else
1944                                         {
1945                                                 strbuf_printf( output, "%s\n", value );
1946                                         }
1947                                 }
1948                         }
1949                         // Recurse on child
1950                         if ( child )
1951                                 serialise_yaml( child, output, indent + 2, 1 );
1952                 }
1953                 else
1954                 {
1955                         // Assume this is a normal map-oriented properties list
1956                         const char *value = mlt_properties_get( self, list->name[ i ] );
1957
1958                         // Ignore hidden/non-serialisable items
1959                         // If the value can be represented as a string
1960                         if ( list->name[ i ][ 0 ] != '_' && value && strcmp( value, "" ) )
1961                         {
1962                                 if ( is_parent_sequence == 0 )
1963                                         indent_yaml( output, indent );
1964                                 else
1965                                         is_parent_sequence = 0;
1966
1967                                 // Determine if this is an unfolded block literal
1968                                 if ( strchr( value, '\n' ) )
1969                                 {
1970                                         strbuf_printf( output, "%s: |\n", list->name[ i ] );
1971                                         output_yaml_block_literal( output, value, indent + strlen( list->name[ i ] ) + strlen( ": " ) );
1972                                 }
1973                                 else if ( strchr( value, ':' ) || strchr( value, '[' ) )
1974                                 {
1975                                         strbuf_printf( output, "%s: \"", list->name[ i ] );
1976                                         strbuf_escape( output, value, '"' );
1977                                         strbuf_printf( output, "\"\n" );
1978                                 }
1979                                 else
1980                                 {
1981                                         strbuf_printf( output, "%s: %s\n", list->name[ i ], value );
1982                                 }
1983                         }
1984
1985                         // Output a child as a map item
1986                         if ( child )
1987                         {
1988                                 indent_yaml( output, indent );
1989                                 strbuf_printf( output, "%s:\n", list->name[ i ] );
1990
1991                                 // Recurse on child
1992                                 serialise_yaml( child, output, indent + 2, 0 );
1993                         }
1994                 }
1995         }
1996 }
1997
1998 /** Serialize a properties list as a string of YAML Tiny.
1999  *
2000  * The caller MUST free the returned string!
2001  * This operates on properties containing properties as a hierarchical data
2002  * structure.
2003  * \public \memberof mlt_properties_s
2004  * \param self a properties list
2005  * \return a string containing YAML Tiny that represents the properties list
2006  */
2007
2008 char *mlt_properties_serialise_yaml( mlt_properties self )
2009 {
2010         if ( !self ) return NULL;
2011         const char *lc_numeric = mlt_properties_get_lcnumeric( self );
2012         strbuf b = strbuf_new();
2013         strbuf_printf( b, "---\n" );
2014         mlt_properties_set_lcnumeric( self, "C" );
2015         serialise_yaml( self, b, 0, 0 );
2016         mlt_properties_set_lcnumeric( self, lc_numeric );
2017         strbuf_printf( b, "...\n" );
2018         char *ret = b->string;
2019         strbuf_close( b );
2020         return ret;
2021 }
2022
2023 /** Protect a properties list against concurrent access.
2024  *
2025  * \public \memberof mlt_properties_s
2026  * \param self a properties list
2027  */
2028
2029 void mlt_properties_lock( mlt_properties self )
2030 {
2031         if ( self )
2032                 pthread_mutex_lock( &( ( property_list* )( self->local ) )->mutex );
2033 }
2034
2035 /** End protecting a properties list against concurrent access.
2036  *
2037  * \public \memberof mlt_properties_s
2038  * \param self a properties list
2039  */
2040
2041 void mlt_properties_unlock( mlt_properties self )
2042 {
2043         if ( self )
2044                 pthread_mutex_unlock( &( ( property_list* )( self->local ) )->mutex );
2045 }
2046
2047 /** Get a time string associated to the name.
2048  *
2049  * Do not free the returned string. It's lifetime is controlled by the property.
2050  * \public \memberof mlt_properties_s
2051  * \param self a properties list
2052  * \param name the property to get
2053  * \param format the time format that you want
2054  * \return the property's time value or NULL if \p name does not exist or there is no profile
2055  */
2056
2057 char *mlt_properties_get_time( mlt_properties self, const char* name, mlt_time_format format )
2058 {
2059         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2060         if ( profile )
2061         {
2062                 double fps = mlt_profile_fps( profile );
2063                 mlt_property value = mlt_properties_find( self, name );
2064                 property_list *list = self->local;
2065                 return value == NULL ? NULL : mlt_property_get_time( value, format, fps, list->locale );
2066         }
2067         return NULL;
2068 }
2069
2070 /** Get a string value by name.
2071  *
2072  * Do not free the returned string. It's lifetime is controlled by the property
2073  * and this properties object.
2074  * \public \memberof mlt_properties_s
2075  * \param self a properties list
2076  * \param name the property to get
2077  * \return the property's string value or NULL if it does not exist
2078  */
2079
2080 char* mlt_properties_anim_get( mlt_properties self, const char *name, int position, int length )
2081 {
2082         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2083         double fps = mlt_profile_fps( profile );
2084         mlt_property value = mlt_properties_find( self, name );
2085         property_list *list = self->local;
2086         return value == NULL ? NULL : mlt_property_anim_get_string( value, fps, list->locale, position, length );
2087 }
2088
2089 /** Set a property to a string.
2090  *
2091  * The event "property-changed" is fired after the property has been set.
2092  *
2093  * This makes a copy of the string value you supply.
2094  * \public \memberof mlt_properties_s
2095  * \param self a properties list
2096  * \param name the property to set
2097  * \param value the property's new value
2098  * \return true if error
2099  */
2100
2101 int mlt_properties_anim_set( mlt_properties self, const char *name, const char *value, int position, int length )
2102 {
2103         int error = 1;
2104
2105         if ( !self || !name ) return error;
2106
2107         // Fetch the property to work with
2108         mlt_property property = mlt_properties_fetch( self, name );
2109
2110         // Set it if not NULL
2111         if ( property )
2112         {
2113                 mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2114                 double fps = mlt_profile_fps( profile );
2115                 property_list *list = self->local;
2116                 error = mlt_property_anim_set_string( property, value,
2117                         fps, list->locale, position, length );
2118                 mlt_properties_do_mirror( self, name );
2119         }
2120
2121         mlt_events_fire( self, "property-changed", name, NULL );
2122
2123         return error;
2124 }
2125
2126 /** Get an integer associated to the name at a frame position.
2127  *
2128  * \public \memberof mlt_properties_s
2129  * \param self a properties list
2130  * \param name the property to get
2131  * \return The integer value, 0 if not found (which may also be a legitimate value)
2132  */
2133
2134 int mlt_properties_anim_get_int( mlt_properties self, const char *name, int position, int length )
2135 {
2136         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2137         double fps = mlt_profile_fps( profile );
2138         property_list *list = self->local;
2139         mlt_property value = mlt_properties_find( self, name );
2140         return value == NULL ? 0 : mlt_property_anim_get_int( value, fps, list->locale, position, length );
2141 }
2142
2143 /** Set a property to an integer value at a frame position.
2144  *
2145  * \public \memberof mlt_properties_s
2146  * \param self a properties list
2147  * \param name the property to set
2148  * \param value the integer
2149  * \return true if error
2150  */
2151
2152 int mlt_properties_anim_set_int( mlt_properties self, const char *name, int value,
2153         mlt_keyframe_type keyframe_type, int position, int length )
2154 {
2155         int error = 1;
2156
2157         if ( !self || !name ) return error;
2158
2159         // Fetch the property to work with
2160         mlt_property property = mlt_properties_fetch( self, name );
2161
2162         // Set it if not NULL
2163         if ( property != NULL )
2164         {
2165                 mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2166                 double fps = mlt_profile_fps( profile );
2167                 property_list *list = self->local;
2168                 error = mlt_property_anim_set_int( property, value, fps, list->locale, keyframe_type, position, length );
2169                 mlt_properties_do_mirror( self, name );
2170         }
2171
2172         mlt_events_fire( self, "property-changed", name, NULL );
2173
2174         return error;
2175 }
2176
2177 /** Get a real number associated to the name at a frame position.
2178  *
2179  * \public \memberof mlt_properties_s
2180  * \param self a properties list
2181  * \param name the property to get
2182  * \return The real number, 0 if not found (which may also be a legitimate value)
2183  */
2184
2185 double mlt_properties_anim_get_double( mlt_properties self, const char *name, int position, int length )
2186 {
2187         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2188         double fps = mlt_profile_fps( profile );
2189         property_list *list = self->local;
2190         mlt_property value = mlt_properties_find( self, name );
2191         return value == NULL ? 0.0 : mlt_property_anim_get_double( value, fps, list->locale, position, length );
2192 }
2193
2194 /** Set a property to a real number at a frame position.
2195  *
2196  * \public \memberof mlt_properties_s
2197  * \param self a properties list
2198  * \param name the property to set
2199  * \param value the real number
2200  * \return true if error
2201  */
2202
2203 int mlt_properties_anim_set_double( mlt_properties self, const char *name, double value,
2204         mlt_keyframe_type keyframe_type, int position, int length )
2205 {
2206         int error = 1;
2207
2208         if ( !self || !name ) return error;
2209
2210         // Fetch the property to work with
2211         mlt_property property = mlt_properties_fetch( self, name );
2212
2213         // Set it if not NULL
2214         if ( property != NULL )
2215         {
2216                 mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2217                 double fps = mlt_profile_fps( profile );
2218                 property_list *list = self->local;
2219                 error = mlt_property_anim_set_double( property, value, fps, list->locale, keyframe_type, position, length );
2220                 mlt_properties_do_mirror( self, name );
2221         }
2222
2223         mlt_events_fire( self, "property-changed", name, NULL );
2224
2225         return error;
2226 }
2227
2228 /** Set a property to a rectangle value.
2229  *
2230  * \public \memberof mlt_properties_s
2231  * \param self a properties list
2232  * \param name the property to set
2233  * \param value the rectangle
2234  * \return true if error
2235  */
2236
2237 extern int mlt_properties_set_rect( mlt_properties self, const char *name, mlt_rect value )
2238 {
2239         int error = 1;
2240
2241         if ( !self || !name ) return error;
2242
2243         // Fetch the property to work with
2244         mlt_property property = mlt_properties_fetch( self, name );
2245
2246         // Set it if not NULL
2247         if ( property != NULL )
2248         {
2249                 error = mlt_property_set_rect( property, value );
2250                 mlt_properties_do_mirror( self, name );
2251         }
2252
2253         mlt_events_fire( self, "property-changed", name, NULL );
2254
2255         return error;
2256 }
2257
2258 /** Get a rectangle associated to the name.
2259  *
2260  * \public \memberof mlt_properties_s
2261  * \param self a properties list
2262  * \param name the property to get
2263  * \return The rectangle value, the rectangle fields will be DBL_MIN if not found
2264  */
2265
2266 extern mlt_rect mlt_properties_get_rect( mlt_properties self, const char* name )
2267 {
2268         property_list *list = self->local;
2269         mlt_property value = mlt_properties_find( self, name );
2270         mlt_rect rect = { DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN };
2271         return value == NULL ? rect : mlt_property_get_rect( value, list->locale );
2272 }
2273
2274 /** Set a property to a rectangle value at a frame position.
2275  *
2276  * \public \memberof mlt_properties_s
2277  * \param self a properties list
2278  * \param name the property to set
2279  * \param value the rectangle
2280  * \return true if error
2281  */
2282
2283 extern int mlt_properties_anim_set_rect( mlt_properties self, const char *name, mlt_rect value, mlt_keyframe_type keyframe_type, int position, int length )
2284 {
2285         int error = 1;
2286
2287         if ( !self || !name ) return error;
2288
2289         // Fetch the property to work with
2290         mlt_property property = mlt_properties_fetch( self, name );
2291
2292         // Set it if not NULL
2293         if ( property != NULL )
2294         {
2295                 mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2296                 double fps = mlt_profile_fps( profile );
2297                 property_list *list = self->local;
2298                 error = mlt_property_anim_set_rect( property, value, fps, list->locale, keyframe_type, position, length );
2299                 mlt_properties_do_mirror( self, name );
2300         }
2301
2302         mlt_events_fire( self, "property-changed", name, NULL );
2303
2304         return error;
2305 }
2306
2307 /** Get a rectangle associated to the name.
2308  *
2309  * \public \memberof mlt_properties_s
2310  * \param self a properties list
2311  * \param name the property to get
2312  * \return The rectangle value, the rectangle fields will be DBL_MIN if not found
2313  */
2314
2315 extern mlt_rect mlt_properties_anim_get_rect( mlt_properties self, const char *name, int position, int length )
2316 {
2317         mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL );
2318         double fps = mlt_profile_fps( profile );
2319         property_list *list = self->local;
2320         mlt_property value = mlt_properties_find( self, name );
2321         mlt_rect rect = { DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN };
2322         return value == NULL ? rect : mlt_property_anim_get_rect( value, fps, list->locale, position, length );
2323 }