From c9e76a22200def0d71eeb9dbc3963cfe49143f26 Mon Sep 17 00:00:00 2001 From: Dan Dennedy Date: Sat, 31 Mar 2012 16:39:05 -0700 Subject: [PATCH] add support for timecode and clock time strings to the framework --- configure | 2 +- src/framework/mlt_properties.c | 37 +++- src/framework/mlt_properties.h | 1 + src/framework/mlt_property.c | 335 +++++++++++++++++++++++++++++---- src/framework/mlt_property.h | 9 +- src/framework/mlt_types.h | 10 + src/mlt++/MltProperties.cpp | 5 + src/mlt++/MltProperties.h | 1 + 8 files changed, 359 insertions(+), 41 deletions(-) diff --git a/configure b/configure index 33b8d8e6..89307f34 100755 --- a/configure +++ b/configure @@ -1,7 +1,7 @@ #!/bin/sh export version=0.7.9 -export soversion=4 +export soversion=5 show_help() { diff --git a/src/framework/mlt_properties.c b/src/framework/mlt_properties.c index c381e239..4f691e1d 100644 --- a/src/framework/mlt_properties.c +++ b/src/framework/mlt_properties.c @@ -871,8 +871,11 @@ int mlt_properties_parse( mlt_properties self, const char *namevalue ) int mlt_properties_get_int( mlt_properties self, const char *name ) { + mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL ); + double fps = mlt_profile_fps( profile ); + property_list *list = self->local; mlt_property value = mlt_properties_find( self, name ); - return value == NULL ? 0 : mlt_property_get_int( value ); + return value == NULL ? 0 : mlt_property_get_int( value, fps, list->locale ); } /** Set a property to an integer value. @@ -955,9 +958,11 @@ int mlt_properties_set_int64( mlt_properties self, const char *name, int64_t val double mlt_properties_get_double( mlt_properties self, const char *name ) { + mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL ); + double fps = mlt_profile_fps( profile ); mlt_property value = mlt_properties_find( self, name ); property_list *list = self->local; - return value == NULL ? 0 : mlt_property_get_double_l( value, list->locale ); + return value == NULL ? 0 : mlt_property_get_double( value, fps, list->locale ); } /** Set a property to a floating point value. @@ -998,8 +1003,11 @@ int mlt_properties_set_double( mlt_properties self, const char *name, double val mlt_position mlt_properties_get_position( mlt_properties self, const char *name ) { + mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL ); + double fps = mlt_profile_fps( profile ); + property_list *list = self->local; mlt_property value = mlt_properties_find( self, name ); - return value == NULL ? 0 : mlt_property_get_position( value ); + return value == NULL ? 0 : mlt_property_get_position( value, fps, list->locale ); } /** Set a property to a position value. @@ -1952,3 +1960,26 @@ void mlt_properties_unlock( mlt_properties self ) if ( self ) pthread_mutex_unlock( &( ( property_list* )( self->local ) )->mutex ); } + +/** Get a time string associated to the name. + * + * Do not free the returned string. It's lifetime is controlled by the property. + * \public \memberof mlt_properties_s + * \param self a properties list + * \param name the property to get + * \param format the time format that you want + * \return the property's time value or NULL if \p name does not exist or there is no profile + */ + +char *mlt_properties_get_time( mlt_properties self, const char* name, mlt_time_format format ) +{ + mlt_profile profile = mlt_properties_get_data( self, "_profile", NULL ); + if ( profile ) + { + double fps = mlt_profile_fps( profile ); + mlt_property value = mlt_properties_find( self, name ); + property_list *list = self->local; + return value == NULL ? NULL : mlt_property_get_time( value, format, fps, list->locale ); + } + return NULL; +} diff --git a/src/framework/mlt_properties.h b/src/framework/mlt_properties.h index 57c653ff..449f3630 100644 --- a/src/framework/mlt_properties.h +++ b/src/framework/mlt_properties.h @@ -88,5 +88,6 @@ extern mlt_properties mlt_properties_parse_yaml( const char *file ); extern char *mlt_properties_serialise_yaml( mlt_properties self ); extern void mlt_properties_lock( mlt_properties self ); extern void mlt_properties_unlock( mlt_properties self ); +extern char *mlt_properties_get_time( mlt_properties, const char* name, mlt_time_format ); #endif diff --git a/src/framework/mlt_property.c b/src/framework/mlt_property.c index 3cc9f8b8..d3863abe 100644 --- a/src/framework/mlt_property.c +++ b/src/framework/mlt_property.c @@ -264,10 +264,105 @@ int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destr 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 fps * ( (hours * 3600) + (minutes * 60) + seconds ) + 0.5; +} + +/** 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 frames + ( fps * ( (hours * 3600) + (minutes * 60) + seconds ) + 0.5 ); +} + +/** 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. @@ -277,14 +372,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] == '#' ) { @@ -298,6 +396,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 ); @@ -308,10 +413,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; @@ -322,41 +429,50 @@ int mlt_property_get_int( mlt_property self ) else if ( self->types & mlt_prop_int64 ) return ( int )self->prop_int64; 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; } -/** Get the property as a floating point. +/** Convert a string to a floating point number. * - * \public \memberof mlt_property_s - * \param self a property - * \return a floating point value + * 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 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 */ - -double mlt_property_get_double( mlt_property self ) +static double mlt_property_atof( const char *value, double fps, locale_t locale ) { - if ( self->types & mlt_prop_double ) - return self->prop_double; - else if ( self->types & mlt_prop_int ) - return ( double )self->prop_int; - else if ( self->types & mlt_prop_position ) - return ( double )self->prop_position; - else if ( self->types & mlt_prop_int64 ) - return ( double )self->prop_int64; - else if ( ( self->types & mlt_prop_string ) && self->prop_string ) - return atof( self->prop_string ); - return 0; + 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 + { +#if defined(__GLIBC__) || defined(__DARWIN__) + if ( locale ) + return strtod_l( value, NULL, locale ); +#endif + return strtod( value, NULL ); + } } -/** Get the property (with locale) as a floating point. +/** 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_l( mlt_property self, locale_t locale ) +double mlt_property_get_double( mlt_property self, double fps, locale_t locale ) { if ( self->types & mlt_prop_double ) return self->prop_double; @@ -366,12 +482,8 @@ double mlt_property_get_double_l( mlt_property self, locale_t locale ) return ( double )self->prop_position; else if ( self->types & mlt_prop_int64 ) return ( double )self->prop_int64; -#if defined(__GLIBC__) || defined(__DARWIN__) - else if ( locale && ( self->types & mlt_prop_string ) && self->prop_string ) - return strtod_l( self->prop_string, NULL, locale ); -#endif else if ( ( self->types & mlt_prop_string ) && self->prop_string ) - return strtod( self->prop_string, NULL ); + return mlt_property_atof( self->prop_string, fps, locale ); return 0; } @@ -380,10 +492,12 @@ double mlt_property_get_double_l( mlt_property self, locale_t locale ) * 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; @@ -394,7 +508,7 @@ mlt_position mlt_property_get_position( mlt_property self ) else if ( self->types & mlt_prop_int64 ) return ( mlt_position )self->prop_int64; 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; } @@ -643,3 +757,160 @@ void mlt_property_pass( mlt_property self, mlt_property that ) } 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 = ""; + + // 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__) + localname = 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 ); + } + + // 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 ); + } + + // Return the string (may be NULL) + return self->prop_string; +} diff --git a/src/framework/mlt_property.h b/src/framework/mlt_property.h index c50302f7..cad70a36 100644 --- a/src/framework/mlt_property.h +++ b/src/framework/mlt_property.h @@ -43,16 +43,15 @@ extern int mlt_property_set_position( mlt_property self, mlt_position value ); extern int mlt_property_set_int64( mlt_property self, int64_t value ); extern int mlt_property_set_string( mlt_property self, const char *value ); extern int mlt_property_set_data( mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser ); -extern int mlt_property_get_int( mlt_property self ); -extern double mlt_property_get_double( mlt_property self ); -extern double mlt_property_get_double_l( mlt_property self, locale_t ); -extern mlt_position mlt_property_get_position( mlt_property self ); +extern int mlt_property_get_int( mlt_property self, double fps, locale_t ); +extern double mlt_property_get_double( mlt_property self, double fps, locale_t ); +extern mlt_position mlt_property_get_position( mlt_property self, double fps, locale_t ); extern int64_t mlt_property_get_int64( mlt_property self ); extern char *mlt_property_get_string( mlt_property self ); extern char *mlt_property_get_string_l( mlt_property self, locale_t ); extern void *mlt_property_get_data( mlt_property self, int *length ); extern void mlt_property_close( mlt_property self ); - extern void mlt_property_pass( mlt_property self, mlt_property that ); +extern char *mlt_property_get_time( mlt_property self, mlt_time_format, double fps, locale_t ); #endif diff --git a/src/framework/mlt_types.h b/src/framework/mlt_types.h index 99fd4a3e..f242f12c 100644 --- a/src/framework/mlt_types.h +++ b/src/framework/mlt_types.h @@ -62,6 +62,16 @@ typedef enum } mlt_audio_format; +/** The time string formats */ + +typedef enum +{ + mlt_time_frames = 0, /**< frame count */ + mlt_time_clock, /**< SMIL clock-value as [[hh:]mm:]ss[.fraction] */ + mlt_time_smpte /**< SMPTE timecode as [[[hh:]mm:]ss:]frames */ +} +mlt_time_format; + /** The relative time qualifiers */ typedef enum diff --git a/src/mlt++/MltProperties.cpp b/src/mlt++/MltProperties.cpp index f37f777a..c80756eb 100644 --- a/src/mlt++/MltProperties.cpp +++ b/src/mlt++/MltProperties.cpp @@ -331,3 +331,8 @@ const char *Properties::get_lcnumeric( ) { return mlt_properties_get_lcnumeric( get_properties() ); } + +char *Properties::get_time( const char *name, mlt_time_format format ) +{ + return mlt_properties_get_time( get_properties(), name, format ); +} diff --git a/src/mlt++/MltProperties.h b/src/mlt++/MltProperties.h index 5a8718c6..dfbc5eec 100644 --- a/src/mlt++/MltProperties.h +++ b/src/mlt++/MltProperties.h @@ -96,6 +96,7 @@ namespace Mlt int preset( const char *name ); int set_lcnumeric( const char *locale ); const char *get_lcnumeric( ); + char *get_time( const char *name, mlt_time_format = mlt_time_smpte ); }; } -- 2.39.2