]> git.sesse.net Git - mlt/blobdiff - src/framework/mlt_property.c
If LC_NUMERIC unsupported do not inadvertently change locale.
[mlt] / src / framework / mlt_property.c
index aea00e72bdfda1766bb66a114cb827b88ac04542..251cc33585bfd52d020d6554b1d62aed35bace4e 100644 (file)
@@ -3,8 +3,9 @@
  * \brief Property class definition
  * \see mlt_property_s
  *
- * Copyright (C) 2003-2009 Ushodaya Enterprises Limited
+ * Copyright (C) 2003-2013 Ushodaya Enterprises Limited
  * \author Charles Yates <charles.yates@pandora.be>
+ * \author Dan Dennedy <dan@dennedy.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+// For strtod_l
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
 #include "mlt_property.h"
+#include "mlt_animation.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <locale.h>
+#include <pthread.h>
+#include <float.h>
+#include <math.h>
 
 
 /** Bit pattern used internally to indicated representations available.
@@ -39,7 +50,8 @@ typedef enum
        mlt_prop_position = 4,//!< set as a position
        mlt_prop_double = 8,  //!< set as a floating point
        mlt_prop_data = 16,   //!< set as opaque binary
-       mlt_prop_int64 = 32   //!< set as a 64-bit integer
+       mlt_prop_int64 = 32,  //!< set as a 64-bit integer
+       mlt_prop_rect = 64    //!< set as a mlt_rect
 }
 mlt_property_type;
 
@@ -68,6 +80,9 @@ struct mlt_property_s
        int length;
        mlt_destructor destructor;
        mlt_serialiser serialiser;
+
+       pthread_mutex_t mutex;
+       mlt_animation animation;
 };
 
 /** Construct a property and initialize it
@@ -76,20 +91,9 @@ struct mlt_property_s
 
 mlt_property mlt_property_init( )
 {
-       mlt_property self = malloc( sizeof( struct mlt_property_s ) );
-       if ( self != NULL )
-       {
-               self->types = 0;
-               self->prop_int = 0;
-               self->prop_position = 0;
-               self->prop_double = 0;
-               self->prop_int64 = 0;
-               self->prop_string = NULL;
-               self->data = NULL;
-               self->length = 0;
-               self->destructor = NULL;
-               self->serialiser = NULL;
-       }
+       mlt_property self = calloc( 1, sizeof( *self ) );
+       if ( self )
+               pthread_mutex_init( &self->mutex, NULL );
        return self;
 }
 
@@ -110,6 +114,9 @@ static inline void mlt_property_clear( mlt_property self )
        if ( self->types & mlt_prop_string )
                free( self->prop_string );
 
+       if ( self->animation )
+               mlt_animation_close( self->animation );
+
        // Wipe stuff
        self->types = 0;
        self->prop_int = 0;
@@ -121,6 +128,7 @@ static inline void mlt_property_clear( mlt_property self )
        self->length = 0;
        self->destructor = NULL;
        self->serialiser = NULL;
+       self->animation = NULL;
 }
 
 /** Set the property to an integer value.
@@ -133,9 +141,11 @@ static inline void mlt_property_clear( mlt_property self )
 
 int mlt_property_set_int( mlt_property self, int value )
 {
+       pthread_mutex_lock( &self->mutex );
        mlt_property_clear( self );
        self->types = mlt_prop_int;
        self->prop_int = value;
+       pthread_mutex_unlock( &self->mutex );
        return 0;
 }
 
@@ -149,9 +159,11 @@ int mlt_property_set_int( mlt_property self, int value )
 
 int mlt_property_set_double( mlt_property self, double value )
 {
+       pthread_mutex_lock( &self->mutex );
        mlt_property_clear( self );
        self->types = mlt_prop_double;
        self->prop_double = value;
+       pthread_mutex_unlock( &self->mutex );
        return 0;
 }
 
@@ -166,9 +178,11 @@ int mlt_property_set_double( mlt_property self, double value )
 
 int mlt_property_set_position( mlt_property self, mlt_position value )
 {
+       pthread_mutex_lock( &self->mutex );
        mlt_property_clear( self );
        self->types = mlt_prop_position;
        self->prop_position = value;
+       pthread_mutex_unlock( &self->mutex );
        return 0;
 }
 
@@ -184,6 +198,7 @@ int mlt_property_set_position( mlt_property self, mlt_position value )
 
 int mlt_property_set_string( mlt_property self, const char *value )
 {
+       pthread_mutex_lock( &self->mutex );
        if ( value != self->prop_string )
        {
                mlt_property_clear( self );
@@ -195,6 +210,7 @@ int mlt_property_set_string( mlt_property self, const char *value )
        {
                self->types = mlt_prop_string;
        }
+       pthread_mutex_unlock( &self->mutex );
        return self->prop_string == NULL;
 }
 
@@ -208,9 +224,11 @@ int mlt_property_set_string( mlt_property self, const char *value )
 
 int mlt_property_set_int64( mlt_property self, int64_t value )
 {
+       pthread_mutex_lock( &self->mutex );
        mlt_property_clear( self );
        self->types = mlt_prop_int64;
        self->prop_int64 = value;
+       pthread_mutex_unlock( &self->mutex );
        return 0;
 }
 
@@ -231,6 +249,7 @@ int mlt_property_set_int64( mlt_property self, int64_t value )
 
 int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser )
 {
+       pthread_mutex_lock( &self->mutex );
        if ( self->data == value )
                self->destructor = NULL;
        mlt_property_clear( self );
@@ -239,13 +258,109 @@ int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destr
        self->length = length;
        self->destructor = destructor;
        self->serialiser = serialiser;
+       pthread_mutex_unlock( &self->mutex );
        return 0;
 }
 
-/** Convert a base 10 or base 16 string to an integer.
+/** Parse a SMIL clock value.
+ *
+ * \private \memberof mlt_property_s
+ * \param s the string to parse
+ * \param fps frames per second
+ * \param locale the locale to use for parsing a real number value
+ * \return position in frames
+ */
+
+static int time_clock_to_frames( const char *s, double fps, locale_t locale )
+{
+       char *pos, *copy = strdup( s );
+       int hours = 0, minutes = 0;
+       double seconds;
+
+       s = copy;
+       pos = strrchr( s, ':' );
+       if ( pos ) {
+#if defined(__GLIBC__) || defined(__DARWIN__)
+               if ( locale )
+                       seconds = strtod_l( pos + 1, NULL, locale );
+               else
+#endif
+                       seconds = strtod( pos + 1, NULL );
+               *pos = 0;
+               pos = strrchr( s, ':' );
+               if ( pos ) {
+                       minutes = atoi( pos + 1 );
+                       *pos = 0;
+                       hours = atoi( s );
+               }
+               else {
+                       minutes = atoi( s );
+               }
+       }
+       else {
+#if defined(__GLIBC__) || defined(__DARWIN__)
+               if ( locale )
+                       seconds = strtod_l( s, NULL, locale );
+               else
+#endif
+                       seconds = strtod( s, NULL );
+       }
+       free( copy );
+
+       return lrint( fps * ( (hours * 3600) + (minutes * 60) + seconds ) );
+}
+
+/** Parse a SMPTE timecode string.
+ *
+ * \private \memberof mlt_property_s
+ * \param s the string to parse
+ * \param fps frames per second
+ * \return position in frames
+ */
+
+static int time_code_to_frames( const char *s, double fps )
+{
+       char *pos, *copy = strdup( s );
+       int hours = 0, minutes = 0, seconds = 0, frames;
+
+       s = copy;
+       pos = strrchr( s, ';' );
+       if ( !pos )
+               pos = strrchr( s, ':' );
+       if ( pos ) {
+               frames = atoi( pos + 1 );
+               *pos = 0;
+               pos = strrchr( s, ':' );
+               if ( pos ) {
+                       seconds = atoi( pos + 1 );
+                       *pos = 0;
+                       pos = strrchr( s, ':' );
+                       if ( pos ) {
+                               minutes = atoi( pos + 1 );
+                               *pos = 0;
+                               hours = atoi( s );
+                       }
+                       else {
+                               minutes = atoi( s );
+                       }
+               }
+               else {
+                       seconds = atoi( s );
+               }
+       }
+       else {
+               frames = atoi( s );
+       }
+       free( copy );
+
+       return lrint( fps * ( (hours * 3600) + (minutes * 60) + seconds ) + frames );
+}
+
+/** Convert a string to an integer.
  *
  * The string must begin with '0x' to be interpreted as hexadecimal.
  * Otherwise, it is interpreted as base 10.
+ *
  * If the string begins with '#' it is interpreted as a hexadecimal color value
  * in the form RRGGBB or AARRGGBB. Color values that begin with '0x' are
  * always in the form RRGGBBAA where the alpha components are not optional.
@@ -255,14 +370,17 @@ int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destr
  * right to obtain RGB without alpha in order to make it do a logical instead
  * of arithmetic shift.
  *
+ * If the string contains a colon it is interpreted as a time value. If it also
+ * contains a period or comma character, the string is parsed as a clock value:
+ * HH:MM:SS. Otherwise, the time value is parsed as a SMPTE timecode: HH:MM:SS:FF.
  * \private \memberof mlt_property_s
  * \param value a string to convert
+ * \param fps frames per second, used when converting from time value
+ * \param locale the locale to use when converting from time clock value
  * \return the resultant integer
  */
-static inline int mlt_property_atoi( const char *value )
+static int mlt_property_atoi( const char *value, double fps, locale_t locale )
 {
-       if ( value == NULL )
-               return 0;
        // Parse a hex color value as #RRGGBB or #AARRGGBB.
        if ( value[0] == '#' )
        {
@@ -276,6 +394,13 @@ static inline int mlt_property_atoi( const char *value )
        {
                return strtoul( value + 2, NULL, 16 );
        }
+       else if ( fps > 0 && strchr( value, ':' ) )
+       {
+               if ( strchr( value, '.' ) || strchr( value, ',' ) )
+                       return time_clock_to_frames( value, fps, locale );
+               else
+                       return time_code_to_frames( value, fps );
+       }
        else
        {
                return strtol( value, NULL, 10 );
@@ -286,10 +411,12 @@ static inline int mlt_property_atoi( const char *value )
  *
  * \public \memberof mlt_property_s
  * \param self a property
+ * \param fps frames per second, used when converting from time value
+ * \param locale the locale to use when converting from time clock value
  * \return an integer value
  */
 
-int mlt_property_get_int( mlt_property self )
+int mlt_property_get_int( mlt_property self, double fps, locale_t locale )
 {
        if ( self->types & mlt_prop_int )
                return self->prop_int;
@@ -299,19 +426,61 @@ int mlt_property_get_int( mlt_property self )
                return ( int )self->prop_position;
        else if ( self->types & mlt_prop_int64 )
                return ( int )self->prop_int64;
+       else if ( self->types & mlt_prop_rect && self->data )
+               return ( int ) ( (mlt_rect*) self->data )->x;
        else if ( ( self->types & mlt_prop_string ) && self->prop_string )
-               return mlt_property_atoi( self->prop_string );
+               return mlt_property_atoi( self->prop_string, fps, locale );
        return 0;
 }
 
+/** Convert a string to a floating point number.
+ *
+ * If the string contains a colon it is interpreted as a time value. If it also
+ * contains a period or comma character, the string is parsed as a clock value:
+ * HH:MM:SS. Otherwise, the time value is parsed as a SMPTE timecode: HH:MM:SS:FF.
+ * If the numeric string ends with '%' then the value is divided by 100 to convert
+ * it into a ratio.
+ * \private \memberof mlt_property_s
+ * \param value the string to convert
+ * \param fps frames per second, used when converting from time value
+ * \param locale the locale to use when converting from time clock value
+ * \return the resultant real number
+ */
+static double mlt_property_atof( const char *value, double fps, locale_t locale )
+{
+       if ( fps > 0 && strchr( value, ':' ) )
+       {
+               if ( strchr( value, '.' ) || strchr( value, ',' ) )
+                       return time_clock_to_frames( value, fps, locale );
+               else
+                       return time_code_to_frames( value, fps );
+       }
+       else
+       {
+               char *end = NULL;
+               double result;
+#if defined(__GLIBC__) || defined(__DARWIN__)
+               if ( locale )
+                       result = strtod_l( value, &end, locale );
+        else
+#endif
+                       result = strtod( value, &end );
+               if ( end && end[0] == '%' )
+                       result /= 100.0;
+               return result;
+       }
+}
+
 /** Get the property as a floating point.
  *
  * \public \memberof mlt_property_s
  * \param self a property
+ * \param fps frames per second, used when converting from time value
+ * \param locale the locale to use for this conversion
  * \return a floating point value
  */
 
-double mlt_property_get_double( mlt_property self )
+double mlt_property_get_double( mlt_property self, double fps, locale_t locale )
 {
        if ( self->types & mlt_prop_double )
                return self->prop_double;
@@ -321,8 +490,10 @@ double mlt_property_get_double( mlt_property self )
                return ( double )self->prop_position;
        else if ( self->types & mlt_prop_int64 )
                return ( double )self->prop_int64;
+       else if ( self->types & mlt_prop_rect && self->data )
+               return ( (mlt_rect*) self->data )->x;
        else if ( ( self->types & mlt_prop_string ) && self->prop_string )
-               return atof( self->prop_string );
+               return mlt_property_atof( self->prop_string, fps, locale );
        return 0;
 }
 
@@ -331,10 +502,12 @@ double mlt_property_get_double( mlt_property self )
  * A position is an offset time in terms of frame units.
  * \public \memberof mlt_property_s
  * \param self a property
+ * \param fps frames per second, used when converting from time value
+ * \param locale the locale to use when converting from time clock value
  * \return the position in frames
  */
 
-mlt_position mlt_property_get_position( mlt_property self )
+mlt_position mlt_property_get_position( mlt_property self, double fps, locale_t locale )
 {
        if ( self->types & mlt_prop_position )
                return self->prop_position;
@@ -344,8 +517,10 @@ mlt_position mlt_property_get_position( mlt_property self )
                return ( mlt_position )self->prop_double;
        else if ( self->types & mlt_prop_int64 )
                return ( mlt_position )self->prop_int64;
+       else if ( self->types & mlt_prop_rect && self->data )
+               return ( mlt_position ) ( (mlt_rect*) self->data )->x;
        else if ( ( self->types & mlt_prop_string ) && self->prop_string )
-               return ( mlt_position )atol( self->prop_string );
+               return ( mlt_position )mlt_property_atoi( self->prop_string, fps, locale );
        return 0;
 }
 
@@ -384,6 +559,8 @@ int64_t mlt_property_get_int64( mlt_property self )
                return ( int64_t )self->prop_double;
        else if ( self->types & mlt_prop_position )
                return ( int64_t )self->prop_position;
+       else if ( self->types & mlt_prop_rect && self->data )
+               return ( int64_t ) ( (mlt_rect*) self->data )->x;
        else if ( ( self->types & mlt_prop_string ) && self->prop_string )
                return mlt_property_atoll( self->prop_string );
        return 0;
@@ -405,6 +582,7 @@ char *mlt_property_get_string( mlt_property self )
        // Construct a string if need be
        if ( ! ( self->types & mlt_prop_string ) )
        {
+               pthread_mutex_lock( &self->mutex );
                if ( self->types & mlt_prop_int )
                {
                        self->types |= mlt_prop_string;
@@ -415,7 +593,7 @@ char *mlt_property_get_string( mlt_property self )
                {
                        self->types |= mlt_prop_string;
                        self->prop_string = malloc( 32 );
-                       sprintf( self->prop_string, "%f", self->prop_double );
+                       sprintf( self->prop_string, "%g", self->prop_double );
                }
                else if ( self->types & mlt_prop_position )
                {
@@ -434,6 +612,86 @@ char *mlt_property_get_string( mlt_property self )
                        self->types |= mlt_prop_string;
                        self->prop_string = self->serialiser( self->data, self->length );
                }
+               pthread_mutex_unlock( &self->mutex );
+       }
+
+       // Return the string (may be NULL)
+       return self->prop_string;
+}
+
+/** Get the property as a string (with locale).
+ *
+ * The caller is not responsible for deallocating the returned string!
+ * The string is deallocated when the Property is closed.
+ * This tries its hardest to convert the property to string including using
+ * a serialization function for binary data, if supplied.
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param locale the locale to use for this conversion
+ * \return a string representation of the property or NULL if failed
+ */
+
+char *mlt_property_get_string_l( mlt_property self, locale_t locale )
+{
+       // Optimization for no locale
+       if ( !locale )
+               return mlt_property_get_string( self );
+
+       // Construct a string if need be
+       if ( ! ( self->types & mlt_prop_string ) )
+       {
+               // TODO: when glibc gets sprintf_l, start using it! For now, hack on setlocale.
+               // Save the current locale
+#if defined(__DARWIN__)
+               const char *localename = querylocale( LC_NUMERIC, locale );
+#elif defined(__GLIBC__)
+               const char *localename = locale->__names[ LC_NUMERIC ];
+#else
+               // TODO: not yet sure what to do on other platforms
+               const char *localename = NULL;
+#endif
+               // Protect damaging the global locale from a temporary locale on another thread.
+               pthread_mutex_lock( &self->mutex );
+
+               // Get the current locale
+               char *orig_localename = strdup( setlocale( LC_NUMERIC, NULL ) );
+
+               // Set the new locale
+               setlocale( LC_NUMERIC, localename );
+
+               if ( self->types & mlt_prop_int )
+               {
+                       self->types |= mlt_prop_string;
+                       self->prop_string = malloc( 32 );
+                       sprintf( self->prop_string, "%d", self->prop_int );
+               }
+               else if ( self->types & mlt_prop_double )
+               {
+                       self->types |= mlt_prop_string;
+                       self->prop_string = malloc( 32 );
+                       sprintf( self->prop_string, "%g", self->prop_double );
+               }
+               else if ( self->types & mlt_prop_position )
+               {
+                       self->types |= mlt_prop_string;
+                       self->prop_string = malloc( 32 );
+                       sprintf( self->prop_string, "%d", (int)self->prop_position );
+               }
+               else if ( self->types & mlt_prop_int64 )
+               {
+                       self->types |= mlt_prop_string;
+                       self->prop_string = malloc( 32 );
+                       sprintf( self->prop_string, "%"PRId64, self->prop_int64 );
+               }
+               else if ( self->types & mlt_prop_data && self->serialiser != NULL )
+               {
+                       self->types |= mlt_prop_string;
+                       self->prop_string = self->serialiser( self->data, self->length );
+               }
+               // Restore the current locale
+               setlocale( LC_NUMERIC, orig_localename );
+               free( orig_localename );
+               pthread_mutex_unlock( &self->mutex );
        }
 
        // Return the string (may be NULL)
@@ -473,6 +731,7 @@ void *mlt_property_get_data( mlt_property self, int *length )
 void mlt_property_close( mlt_property self )
 {
        mlt_property_clear( self );
+       pthread_mutex_destroy( &self->mutex );
        free( self );
 }
 
@@ -487,6 +746,7 @@ void mlt_property_close( mlt_property self )
  */
 void mlt_property_pass( mlt_property self, mlt_property that )
 {
+       pthread_mutex_lock( &self->mutex );
        mlt_property_clear( self );
 
        self->types = that->types;
@@ -499,14 +759,797 @@ void mlt_property_pass( mlt_property self, mlt_property that )
                self->prop_double = that->prop_double;
        else if ( self->types & mlt_prop_position )
                self->prop_position = that->prop_position;
-       else if ( self->types & mlt_prop_string )
+       if ( self->types & mlt_prop_string )
        {
                if ( that->prop_string != NULL )
                        self->prop_string = strdup( that->prop_string );
        }
-       else if ( self->types & mlt_prop_data && self->serialiser != NULL )
+       else if ( that->types & mlt_prop_rect )
+       {
+               mlt_property_clear( self );
+               self->types = mlt_prop_rect | mlt_prop_data;
+               self->length = that->length;
+               self->data = calloc( 1, self->length );
+               memcpy( self->data, that->data, self->length );
+               self->destructor = free;
+               self->serialiser = that->serialiser;
+       }
+       else if ( self->types & mlt_prop_data && that->serialiser != NULL )
        {
                self->types = mlt_prop_string;
-               self->prop_string = self->serialiser( self->data, self->length );
+               self->prop_string = that->serialiser( that->data, that->length );
+       }
+       pthread_mutex_unlock( &self->mutex );
+}
+
+/** Convert frame count to a SMPTE timecode string.
+ *
+ * \private \memberof mlt_property_s
+ * \param frames a frame count
+ * \param fps frames per second
+ * \param[out] s the string to write into - must have enough space to hold largest time string
+ */
+
+static void time_smpte_from_frames( int frames, double fps, char *s )
+{
+       int hours, mins, secs;
+       char frame_sep = ':';
+
+       if ( fps == 30000.0/1001.0 )
+       {
+               fps = 30.0;
+               int i, max_frames = frames;
+               for ( i = 1800; i <= max_frames; i += 1800 )
+               {
+                       if ( i % 18000 )
+                       {
+                               max_frames += 2;
+                               frames += 2;
+                       }
+               }
+               frame_sep = ';';
+       }
+       hours = frames / ( fps * 3600 );
+       frames -= hours * ( fps * 3600 );
+       mins = frames / ( fps * 60 );
+       frames -= mins * ( fps * 60 );
+       secs = frames / fps;
+       frames -= secs * fps;
+
+       sprintf( s, "%02d:%02d:%02d%c%02d", hours, mins, secs, frame_sep, frames );
+}
+
+/** Convert frame count to a SMIL clock value string.
+ *
+ * \private \memberof mlt_property_s
+ * \param frames a frame count
+ * \param fps frames per second
+ * \param[out] s the string to write into - must have enough space to hold largest time string
+ */
+
+static void time_clock_from_frames( int frames, double fps, char *s )
+{
+       int hours, mins;
+       double secs;
+
+       hours = frames / ( fps * 3600 );
+       frames -= hours * ( fps * 3600 );
+       mins = frames / ( fps * 60 );
+       frames -= mins * ( fps * 60 );
+       secs = (double) frames / fps;
+
+       sprintf( s, "%02d:%02d:%06.3f", hours, mins, secs );
+}
+
+/** Get the property as a time string.
+ *
+ * The time value can be either a SMPTE timecode or SMIL clock value.
+ * The caller is not responsible for deallocating the returned string!
+ * The string is deallocated when the property is closed.
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param format the time format that you want
+ * \param fps frames per second
+ * \param locale the locale to use for this conversion
+ * \return a string representation of the property or NULL if failed
+ */
+
+char *mlt_property_get_time( mlt_property self, mlt_time_format format, double fps, locale_t locale )
+{
+       char *orig_localename = NULL;
+       const char *localename = NULL;
+
+       // Optimization for mlt_time_frames
+       if ( format == mlt_time_frames )
+               return mlt_property_get_string_l( self, locale );
+
+       // Remove existing string
+       if ( self->prop_string )
+               mlt_property_set_int( self, mlt_property_get_int( self, fps, locale ) );
+
+       // Use the specified locale
+       if ( locale )
+       {
+               // TODO: when glibc gets sprintf_l, start using it! For now, hack on setlocale.
+               // Save the current locale
+#if defined(__DARWIN__)
+               localename = querylocale( LC_NUMERIC, locale );
+#elif defined(__GLIBC__)
+               localename = locale->__names[ LC_NUMERIC ];
+#else
+               // TODO: not yet sure what to do on other platforms
+#endif
+               // Protect damaging the global locale from a temporary locale on another thread.
+               pthread_mutex_lock( &self->mutex );
+
+               // Get the current locale
+               orig_localename = strdup( setlocale( LC_NUMERIC, NULL ) );
+
+               // Set the new locale
+               setlocale( LC_NUMERIC, localename );
+       }
+       else
+       {
+               // Make sure we have a lock before accessing self->types
+               pthread_mutex_lock( &self->mutex );
+       }
+
+       // Convert number to string
+       if ( self->types & mlt_prop_int )
+       {
+               self->types |= mlt_prop_string;
+               self->prop_string = malloc( 32 );
+               if ( format == mlt_time_clock )
+                       time_clock_from_frames( self->prop_int, fps, self->prop_string );
+               else
+                       time_smpte_from_frames( self->prop_int, fps, self->prop_string );
+       }
+       else if ( self->types & mlt_prop_position )
+       {
+               self->types |= mlt_prop_string;
+               self->prop_string = malloc( 32 );
+               if ( format == mlt_time_clock )
+                       time_clock_from_frames( (int) self->prop_position, fps, self->prop_string );
+               else
+                       time_smpte_from_frames( (int) self->prop_position, fps, self->prop_string );
+       }
+       else if ( self->types & mlt_prop_double )
+       {
+               self->types |= mlt_prop_string;
+               self->prop_string = malloc( 32 );
+               if ( format == mlt_time_clock )
+                       time_clock_from_frames( self->prop_double, fps, self->prop_string );
+               else
+                       time_smpte_from_frames( self->prop_double, fps, self->prop_string );
+       }
+       else if ( self->types & mlt_prop_int64 )
+       {
+               self->types |= mlt_prop_string;
+               self->prop_string = malloc( 32 );
+               if ( format == mlt_time_clock )
+                       time_clock_from_frames( (int) self->prop_int64, fps, self->prop_string );
+               else
+                       time_smpte_from_frames( (int) self->prop_int64, fps, self->prop_string );
+       }
+
+       // Restore the current locale
+       if ( locale )
+       {
+               setlocale( LC_NUMERIC, orig_localename );
+               free( orig_localename );
+               pthread_mutex_unlock( &self->mutex );
+       }
+       else
+       {
+               // Make sure we have a lock before accessing self->types
+               pthread_mutex_unlock( &self->mutex );
+       }
+
+       // Return the string (may be NULL)
+       return self->prop_string;
+}
+
+/** Determine if the property holds a numeric or numeric string value.
+ *
+ * \private \memberof mlt_property_s
+ * \param self a property
+ * \param locale the locale to use for string evaluation
+ * \return true if it is numeric
+ */
+
+static int is_property_numeric( mlt_property self, locale_t locale )
+{
+       int result = ( self->types & mlt_prop_int ) ||
+                       ( self->types & mlt_prop_int64 ) ||
+                       ( self->types & mlt_prop_double ) ||
+                       ( self->types & mlt_prop_position ) ||
+                       ( self->types & mlt_prop_rect );
+
+       // If not already numeric but string is numeric.
+       if ( ( !result && self->types & mlt_prop_string ) && self->prop_string )
+       {
+               double temp;
+               char *p = NULL;
+#if defined(__GLIBC__) || defined(__DARWIN__)
+               if ( locale )
+                       temp = strtod_l( self->prop_string, &p, locale );
+               else
+#endif
+               temp = strtod( self->prop_string, &p );
+               result = ( p != self->prop_string );
+       }
+       return result;
+}
+
+/** A linear interpolation function for animation.
+ *
+ * \private \memberof mlt_property_s
+ */
+
+static inline double linear_interpolate( double y1, double y2, double t )
+{
+       return y1 + ( y2 - y1 ) * t;
+}
+
+/** A smooth spline interpolation for animation.
+ *
+ * For non-closed curves, you need to also supply the tangent vector at the first and last control point.
+ * This is commonly done: T(P[0]) = P[1] - P[0] and T(P[n]) = P[n] - P[n-1].
+ * \private \memberof mlt_property_s
+ */
+
+static inline double catmull_rom_interpolate( double y0, double y1, double y2, double y3, double t )
+{
+       double t2 = t * t;
+       double a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;
+       double a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3;
+       double a2 = -0.5 * y0 + 0.5 * y2;
+       double a3 = y1;
+       return a0 * t * t2 + a1 * t2 + a2 * t + a3;
+}
+
+/** Interpolate a new property value given a set of other properties.
+ *
+ * \public \memberof mlt_property_s
+ * \param self the property onto which to set the computed value
+ * \param p an array of at least 1 value in p[1] if \p interp is discrete,
+ *  2 values in p[1] and p[2] if \p interp is linear, or
+ *  4 values in p[0] - p[3] if \p interp is smooth
+ * \param progress a ratio in the range [0, 1] to indicate how far between p[1] and p[2]
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param interp the interpolation method to use
+ * \return true if there was an error
+ */
+
+int mlt_property_interpolate( mlt_property self, mlt_property p[],
+       double progress, double fps, locale_t locale, mlt_keyframe_type interp )
+{
+       int error = 0;
+       if ( interp != mlt_keyframe_discrete &&
+               is_property_numeric( p[1], locale ) && is_property_numeric( p[2], locale ) )
+       {
+               if ( self->types & mlt_prop_rect )
+               {
+                       mlt_rect value = { DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN };
+                       if ( interp == mlt_keyframe_linear )
+                       {
+                               mlt_rect points[2];
+                               mlt_rect zero = {0, 0, 0, 0, 0};
+                               points[0] = p[1]? mlt_property_get_rect( p[1], locale ) : zero;
+                               if ( p[2] )
+                               {
+                                       points[1] = mlt_property_get_rect( p[2], locale );
+                                       value.x = linear_interpolate( points[0].x, points[1].x, progress );
+                                       value.y = linear_interpolate( points[0].y, points[1].y, progress );
+                                       value.w = linear_interpolate( points[0].w, points[1].w, progress );
+                                       value.h = linear_interpolate( points[0].h, points[1].h, progress );
+                                       value.o = linear_interpolate( points[0].o, points[1].o, progress );
+                               }
+                               else
+                               {
+                                       value = points[0];
+                               }
+                       }
+                       else if ( interp == mlt_keyframe_smooth )
+                       {
+                               mlt_rect points[4];
+                               mlt_rect zero = {0, 0, 0, 0, 0};
+                               points[1] = p[1]? mlt_property_get_rect( p[1], locale ) : zero;
+                               if ( p[2] )
+                               {
+                                       points[0] = p[0]? mlt_property_get_rect( p[0], locale ) : zero;
+                                       points[2] = p[2]? mlt_property_get_rect( p[2], locale ) : zero;
+                                       points[3] = p[3]? mlt_property_get_rect( p[3], locale ) : zero;
+                                       value.x = catmull_rom_interpolate( points[0].x, points[1].x, points[2].x, points[3].x, progress );
+                                       value.y = catmull_rom_interpolate( points[0].y, points[1].y, points[2].y, points[3].y, progress );
+                                       value.w = catmull_rom_interpolate( points[0].w, points[1].w, points[2].w, points[3].w, progress );
+                                       value.h = catmull_rom_interpolate( points[0].h, points[1].h, points[2].h, points[3].h, progress );
+                                       value.o = catmull_rom_interpolate( points[0].o, points[1].o, points[2].o, points[3].o, progress );
+                               }
+                               else
+                               {
+                                       value = points[1];
+                               }
+                       }
+                       error = mlt_property_set_rect( self, value );
+               }
+               else
+               {
+                       double value;
+                       if ( interp == mlt_keyframe_linear )
+                       {
+                               double points[2];
+                               points[0] = p[1]? mlt_property_get_double( p[1], fps, locale ) : 0;
+                               points[1] = p[2]? mlt_property_get_double( p[2], fps, locale ) : 0;
+                               value = p[2]? linear_interpolate( points[0], points[1], progress ) : points[0];
+                       }
+                       else if ( interp == mlt_keyframe_smooth )
+                       {
+                               double points[4];
+                               points[0] = p[0]? mlt_property_get_double( p[0], fps, locale ) : 0;
+                               points[1] = p[1]? mlt_property_get_double( p[1], fps, locale ) : 0;
+                               points[2] = p[2]? mlt_property_get_double( p[2], fps, locale ) : 0;
+                               points[3] = p[3]? mlt_property_get_double( p[3], fps, locale ) : 0;
+                               value = p[2]? catmull_rom_interpolate( points[0], points[1], points[2], points[3], progress ) : points[1];
+                       }
+                       error = mlt_property_set_double( self, value );
+               }
+       }
+       else
+       {
+               mlt_property_pass( self, p[1] );
+       }
+       return error;
+}
+
+/** Create a new animation or refresh an existing one.
+ *
+ * \private \memberof mlt_property_s
+ * \param self a property
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ */
+
+static void refresh_animation( mlt_property self, double fps, locale_t locale, int length  )
+{
+       if ( !self->animation )
+       {
+               self->animation = mlt_animation_new();
+               if ( self->prop_string )
+               {
+                       mlt_animation_parse( self->animation, self->prop_string, length, fps, locale );
+               }
+               else
+               {
+                       mlt_animation_set_length( self->animation, length );
+                       pthread_mutex_lock( &self->mutex );
+                       self->types |= mlt_prop_data;
+                       self->data = self->animation;
+                       self->serialiser = (mlt_serialiser) mlt_animation_serialize;
+                       pthread_mutex_unlock( &self->mutex );
+               }
+       }
+       else if ( self->prop_string )
+       {
+               mlt_animation_refresh( self->animation, self->prop_string, length );
+       }
+}
+
+/** Get the real number at a frame position.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \return the real number
+ */
+
+double mlt_property_anim_get_double( mlt_property self, double fps, locale_t locale, int position, int length )
+{
+       pthread_mutex_lock( &self->mutex );
+       double result;
+       if ( self->animation || ( ( self->types & mlt_prop_string ) && self->prop_string ) )
+       {
+               struct mlt_animation_item_s item;
+               item.property = mlt_property_init();
+
+               refresh_animation( self, fps, locale, length );
+               mlt_animation_get_item( self->animation, &item, position );
+               result = mlt_property_get_double( item.property, fps, locale );
+
+               mlt_property_close( item.property );
+       }
+       else
+       {
+               result = mlt_property_get_double( self, fps, locale );
+       }
+       pthread_mutex_unlock( &self->mutex );
+       return result;
+}
+
+/** Get the property as an integer number at a frame position.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \return an integer value
+ */
+
+int mlt_property_anim_get_int( mlt_property self, double fps, locale_t locale, int position, int length )
+{
+       pthread_mutex_lock( &self->mutex );
+       int result;
+       if ( self->animation || ( ( self->types & mlt_prop_string ) && self->prop_string ) )
+       {
+               struct mlt_animation_item_s item;
+               item.property = mlt_property_init();
+
+               refresh_animation( self, fps, locale, length );
+               mlt_animation_get_item( self->animation, &item, position );
+               result = mlt_property_get_int( item.property, fps, locale );
+
+               mlt_property_close( item.property );
+       }
+       else
+       {
+               result = mlt_property_get_int( self, fps, locale );
+       }
+       pthread_mutex_unlock( &self->mutex );
+       return result;
+}
+
+/** Get the string at certain a frame position.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \return the string representation of the property or NULL if failed
+ */
+
+char* mlt_property_anim_get_string( mlt_property self, double fps, locale_t locale, int position, int length )
+{
+       pthread_mutex_lock( &self->mutex );
+       char *result;
+       if ( self->animation || ( ( self->types & mlt_prop_string ) && self->prop_string ) )
+       {
+               struct mlt_animation_item_s item;
+               item.property = mlt_property_init();
+
+               if ( !self->animation )
+                       refresh_animation( self, fps, locale, length );
+               mlt_animation_get_item( self->animation, &item, position );
+
+               if ( self->prop_string )
+                       free( self->prop_string );
+               self->prop_string = mlt_property_get_string_l( item.property, locale );
+               if ( self->prop_string )
+                       self->prop_string = strdup( self->prop_string );
+               self->types |= mlt_prop_string;
+
+               result = self->prop_string;
+               mlt_property_close( item.property );
+       }
+       else
+       {
+               result = mlt_property_get_string_l( self, locale );
+       }
+       pthread_mutex_unlock( &self->mutex );
+       return result;
+}
+
+/** Set a property animation keyframe to a real number.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param value a double precision floating point value
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \param keyframe_type the interpolation method for this keyframe
+ * \return false if successful, true to indicate error
+ */
+
+int mlt_property_anim_set_double( mlt_property self, double value, double fps, locale_t locale,
+       int position, int length, mlt_keyframe_type keyframe_type )
+{
+       int result;
+       struct mlt_animation_item_s item;
+
+       item.property = mlt_property_init();
+       item.frame = position;
+       item.keyframe_type = keyframe_type;
+       mlt_property_set_double( item.property, value );
+
+       refresh_animation( self, fps, locale, length );
+       result = mlt_animation_insert( self->animation, &item );
+       mlt_animation_interpolate( self->animation );
+       mlt_property_close( item.property );
+
+       return result;
+}
+
+/** Set a property animation keyframe to an integer value.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param value an integer
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \param keyframe_type the interpolation method for this keyframe
+ * \return false if successful, true to indicate error
+ */
+
+int mlt_property_anim_set_int( mlt_property self, int value, double fps, locale_t locale,
+       int position, int length, mlt_keyframe_type keyframe_type )
+{
+       int result;
+       struct mlt_animation_item_s item;
+
+       item.property = mlt_property_init();
+       item.frame = position;
+       item.keyframe_type = keyframe_type;
+       mlt_property_set_int( item.property, value );
+
+       refresh_animation( self, fps, locale, length );
+       result = mlt_animation_insert( self->animation, &item );
+       mlt_animation_interpolate( self->animation );
+       mlt_property_close( item.property );
+
+       return result;
+}
+
+/** Set a property animation keyframe to a string.
+ *
+ * Strings only support discrete animation. Do not use this to set a property's
+ * animation string that contains a semicolon-delimited set of values; use
+ * mlt_property_set() for that.
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param value a string
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \return false if successful, true to indicate error
+ */
+
+int mlt_property_anim_set_string( mlt_property self, const char *value, double fps, locale_t locale, int position, int length )
+{
+       int result;
+       struct mlt_animation_item_s item;
+
+       item.property = mlt_property_init();
+       item.frame = position;
+       item.keyframe_type = mlt_keyframe_discrete;
+       mlt_property_set_string( item.property, value );
+
+       refresh_animation( self, fps, locale, length );
+       result = mlt_animation_insert( self->animation, &item );
+       mlt_animation_interpolate( self->animation );
+       mlt_property_close( item.property );
+
+       return result;
+}
+
+/** Get an object's animation object.
+ *
+ * You might need to call another mlt_property_anim_ function to actually construct
+ * the animation, as this is a simple accessor function.
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \return the animation object or NULL if there is no animation
+ */
+
+mlt_animation mlt_property_get_animation( mlt_property self )
+{
+    return self->animation;
+}
+
+/** Convert a rectangle value into a string.
+ *
+ * Unlike the deprecated mlt_geometry API, the canonical form of a mlt_rect
+ * is a space delimited "x y w h o" even though many kinds of field delimiters
+ * may be used to convert a string to a rectangle.
+ * \private \memberof mlt_property_s
+ * \param rect the rectangle to convert
+ * \param length not used
+ * \return the string representation of a rectangle
+ */
+
+static char* serialise_mlt_rect( mlt_rect *rect, int length )
+{
+       char* result = calloc( 1, 100 );
+       if ( rect->x != DBL_MIN )
+               sprintf( result + strlen( result ), "%g", rect->x );
+       if ( rect->y != DBL_MIN )
+               sprintf( result + strlen( result ), " %g", rect->y );
+       if ( rect->w != DBL_MIN )
+               sprintf( result + strlen( result ), " %g", rect->w );
+       if ( rect->h != DBL_MIN )
+               sprintf( result + strlen( result ), " %g", rect->h );
+       if ( rect->o != DBL_MIN )
+               sprintf( result + strlen( result ), " %g", rect->o );
+       return result;
+}
+
+/** Set a property to a mlt_rect rectangle.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param value a rectangle
+ * \return false
+ */
+
+int mlt_property_set_rect( mlt_property self, mlt_rect value )
+{
+       pthread_mutex_lock( &self->mutex );
+       mlt_property_clear( self );
+       self->types = mlt_prop_rect | mlt_prop_data;
+       self->length = sizeof(value);
+       self->data = calloc( 1, self->length );
+       memcpy( self->data, &value, self->length );
+       self->destructor = free;
+       self->serialiser = (mlt_serialiser) serialise_mlt_rect;
+       pthread_mutex_unlock( &self->mutex );
+       return 0;
+}
+
+/** Get the property as a rectangle.
+ *
+ * You can use any non-numeric character(s) as a field delimiter.
+ * If the number has a '%' immediately following it, the number is divided by
+ * 100 to convert it into a real number.
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param locale the locale to use for when converting from a string
+ * \return a rectangle value
+ */
+
+mlt_rect mlt_property_get_rect( mlt_property self, locale_t locale )
+{
+       mlt_rect rect = { DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN };
+       if ( self->types & mlt_prop_rect )
+               rect = *( (mlt_rect*) self->data );
+       else if ( self->types & mlt_prop_double )
+               rect.x = self->prop_double;
+       else if ( self->types & mlt_prop_int )
+               rect.x = ( double )self->prop_int;
+       else if ( self->types & mlt_prop_position )
+               rect.x = ( double )self->prop_position;
+       else if ( self->types & mlt_prop_int64 )
+               rect.x = ( double )self->prop_int64;
+       else if ( ( self->types & mlt_prop_string ) && self->prop_string )
+       {
+               //return mlt_property_atof( self->prop_string, fps, locale );
+               char *value = self->prop_string;
+               char *p = NULL;
+               int count = 0;
+               while ( *value )
+               {
+                       double temp;
+#if defined(__GLIBC__) || defined(__DARWIN__)
+                       if ( locale )
+                               temp = strtod_l( value, &p, locale );
+            else
+#endif
+                               temp = strtod( value, &p );
+                       if ( p != value )
+                       {
+                               if ( p[0] == '%' )
+                               {
+                                       temp /= 100.0;
+                                       p ++;
+                               }
+
+                               // Chomp the delimiter.
+                               if ( *p ) p ++;
+
+                               // Assign the value to appropriate field.
+                               switch( count )
+                               {
+                                       case 0: rect.x = temp; break;
+                                       case 1: rect.y = temp; break;
+                                       case 2: rect.w = temp; break;
+                                       case 3: rect.h = temp; break;
+                                       case 4: rect.o = temp; break;
+                               }
+                       }
+                       else
+                       {
+                               p++;
+                       }
+                       value = p;
+                       count ++;
+               }
+       }
+       return rect;
+}
+
+/** Set a property animation keyframe to a rectangle.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param value a rectangle
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \param keyframe_type the interpolation method for this keyframe
+ * \return false if successful, true to indicate error
+ */
+
+int mlt_property_anim_set_rect( mlt_property self, mlt_rect value, double fps, locale_t locale,
+       int position, int length, mlt_keyframe_type keyframe_type )
+{
+       int result;
+       struct mlt_animation_item_s item;
+
+       item.property = mlt_property_init();
+       item.frame = position;
+       item.keyframe_type = keyframe_type;
+       mlt_property_set_rect( item.property, value );
+
+       refresh_animation( self, fps, locale, length );
+       result = mlt_animation_insert( self->animation, &item );
+       mlt_animation_interpolate( self->animation );
+       mlt_property_close( item.property );
+
+       return result;
+}
+
+/** Get a rectangle at a frame position.
+ *
+ * \public \memberof mlt_property_s
+ * \param self a property
+ * \param fps the frame rate, which may be needed for converting a time string to frame units
+ * \param locale the locale, which may be needed for converting a string to a real number
+ * \param position the frame number
+ * \param length the maximum number of frames when interpreting negative keyframe times,
+ *  <=0 if you don't care or need that
+ * \return the rectangle
+ */
+
+mlt_rect mlt_property_anim_get_rect( mlt_property self, double fps, locale_t locale, int position, int length )
+{
+       pthread_mutex_lock( &self->mutex );
+       mlt_rect result;
+       if ( self->animation || ( ( self->types & mlt_prop_string ) && self->prop_string ) )
+       {
+               struct mlt_animation_item_s item;
+               item.property = mlt_property_init();
+               item.property->types = mlt_prop_rect;
+
+               refresh_animation( self, fps, locale, length );
+               mlt_animation_get_item( self->animation, &item, position );
+               result = mlt_property_get_rect( item.property, locale );
+
+               mlt_property_close( item.property );
+       }
+       else
+       {
+               result = mlt_property_get_rect( self, locale );
        }
+       pthread_mutex_unlock( &self->mutex );
+       return result;
 }