]> git.sesse.net Git - x264/blobdiff - input/timecode.c
cli: Use memory-mapped input frames for yuv and y4m
[x264] / input / timecode.c
index 0dddb27b391c918933317df6bc47d3328b0699f4..a9b69b17729ab438d53c9de3e7f55a97bbaab5ae 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
- * timecode.c: x264 timecode format file input module
+ * timecode.c: timecode file input
  *****************************************************************************
- * Copyright (C) 2010 x264 project
+ * Copyright (C) 2010-2015 x264 project
  *
  * Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
+ *
+ * This program is also available under a commercial proprietary license.
+ * For more information, contact us at licensing@x264.com.
  *****************************************************************************/
 
-#include "muxers.h"
-#include <math.h>
-
-extern cli_input_t input;
+#include "input.h"
+#define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, "timecode", __VA_ARGS__ )
 
 typedef struct
 {
     cli_input_t input;
     hnd_t p_handle;
-    int frame_total;
     int auto_timebase_num;
     int auto_timebase_den;
-    int timebase_num;
-    int timebase_den;
-    int seek;
+    uint64_t timebase_num;
+    uint64_t timebase_den;
     int stored_pts_num;
     int64_t *pts;
     double assume_fps;
     double last_timecode;
 } timecode_hnd_t;
 
-static inline double sigexp10( double value, double *exp )
+static inline double sigexp10( double value, double *exponent )
 {
     /* This function separates significand and exp10 from double floating point. */
-    *exp = pow( 10, floor( log10( value ) ) );
-    return value / *exp;
+    *exponent = pow( 10, floor( log10( value ) ) );
+    return value / *exponent;
 }
 
 #define DOUBLE_EPSILON 5e-6
@@ -53,20 +52,16 @@ static inline double sigexp10( double value, double *exp )
 
 static double correct_fps( double fps, timecode_hnd_t *h )
 {
-    int64_t i = 1;
-    int64_t fps_num, fps_den;
+    int i = 1;
+    uint64_t fps_num, fps_den;
     double exponent;
     double fps_sig = sigexp10( fps, &exponent );
     while( 1 )
     {
         fps_den = i * h->timebase_num;
         fps_num = round( fps_den * fps_sig ) * exponent;
-        if( fps_num < 0 )
-        {
-            fprintf( stderr, "timecode [error]: tcfile fps correction failed.\n"
-                             "                  Specify an appropriate timebase manually or remake tcfile.\n" );
-            return -1;
-        }
+        FAIL_IF_ERROR( fps_num > UINT32_MAX, "tcfile fps correction failed.\n"
+                       "                  Specify an appropriate timebase manually or remake tcfile.\n" )
         if( fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
             break;
         ++i;
@@ -74,7 +69,7 @@ static double correct_fps( double fps, timecode_hnd_t *h )
     if( h->auto_timebase_den )
     {
         h->timebase_den = h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
-        if( h->timebase_den < 0 )
+        if( h->timebase_den > UINT32_MAX )
             h->auto_timebase_den = 0;
     }
     return (double)fps_num / fps_den;
@@ -82,22 +77,17 @@ static double correct_fps( double fps, timecode_hnd_t *h )
 
 static int try_mkv_timebase_den( double *fpss, timecode_hnd_t *h, int loop_num )
 {
-    int num;
     h->timebase_num = 0;
     h->timebase_den = MKV_TIMEBASE_DEN;
-    for( num = 0; num < loop_num; num++ )
+    for( int num = 0; num < loop_num; num++ )
     {
-        int fps_den;
+        uint64_t fps_den;
         double exponent;
         double fps_sig = sigexp10( fpss[num], &exponent );
         fps_den = round( MKV_TIMEBASE_DEN / fps_sig ) / exponent;
-        h->timebase_num = fps_den > 0 && h->timebase_num ? gcd( h->timebase_num, fps_den ) : fps_den;
-        if( h->timebase_num <= 0 )
-        {
-            fprintf( stderr, "timecode [error]: automatic timebase generation failed.\n"
-                             "                  Specify timebase manually.\n" );
-            return -1;
-        }
+        h->timebase_num = fps_den && h->timebase_num ? gcd( h->timebase_num, fps_den ) : fps_den;
+        FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || !h->timebase_num, "automatic timebase generation failed.\n"
+                       "                  Specify timebase manually.\n" )
     }
     return 0;
 }
@@ -106,71 +96,52 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
 {
     char buff[256];
     int ret, tcfv, num, seq_num, timecodes_num;
-    int64_t pts_seek_offset;
     double *timecodes = NULL;
     double *fpss = NULL;
 
     ret = fscanf( tcfile_in, "# timecode format v%d", &tcfv );
-    if( ret != 1 || (tcfv != 1 && tcfv != 2) )
-    {
-        fprintf( stderr, "timecode [error]: unsupported timecode format\n" );
-        return -1;
-    }
-
+    FAIL_IF_ERROR( ret != 1 || (tcfv != 1 && tcfv != 2), "unsupported timecode format\n" )
+#define NO_TIMECODE_LINE (buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r')
     if( tcfv == 1 )
     {
         uint64_t file_pos;
         double assume_fps, seq_fps;
-        int start, end = h->seek;
+        int start, end = -1;
         int prev_start = -1, prev_end = -1;
 
         h->assume_fps = 0;
         for( num = 2; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
         {
-            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            if( NO_TIMECODE_LINE )
                 continue;
-            if( sscanf( buff, "assume %lf", &h->assume_fps ) != 1 && sscanf( buff, "Assume %lf", &h->assume_fps ) != 1 )
-            {
-                fprintf( stderr, "timecode [error]: tcfile parsing error: assumed fps not found\n" );
-                return -1;
-            }
+            FAIL_IF_ERROR( sscanf( buff, "assume %lf", &h->assume_fps ) != 1 && sscanf( buff, "Assume %lf", &h->assume_fps ) != 1,
+                           "tcfile parsing error: assumed fps not found\n" )
             break;
         }
-        if( h->assume_fps <= 0 )
-        {
-            fprintf( stderr, "timecode [error]: invalid assumed fps %.6f\n", h->assume_fps );
-            return -1;
-        }
+        FAIL_IF_ERROR( h->assume_fps <= 0, "invalid assumed fps %.6f\n", h->assume_fps )
 
         file_pos = ftell( tcfile_in );
         h->stored_pts_num = 0;
         for( seq_num = 0; fgets( buff, sizeof(buff), tcfile_in ) != NULL; num++ )
         {
-            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            if( NO_TIMECODE_LINE )
             {
                 if( sscanf( buff, "# TDecimate Mode 3:  Last Frame = %d", &end ) == 1 )
-                    h->stored_pts_num = end + 1 - h->seek;
+                    h->stored_pts_num = end + 1;
                 continue;
             }
             ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
-            if( ret != 3 && ret != EOF )
-            {
-                fprintf( stderr, "timecode [error]: invalid input tcfile\n" );
-                return -1;
-            }
-            if( start > end || start <= prev_start || end <= prev_end || seq_fps <= 0 )
-            {
-                fprintf( stderr, "timecode [error]: invalid input tcfile at line %d: %s\n", num, buff );
-                return -1;
-            }
+            FAIL_IF_ERROR( ret != 3 && ret != EOF, "invalid input tcfile\n" )
+            FAIL_IF_ERROR( start > end || start <= prev_start || end <= prev_end || seq_fps <= 0,
+                           "invalid input tcfile at line %d: %s\n", num, buff )
             prev_start = start;
             prev_end = end;
             if( h->auto_timebase_den || h->auto_timebase_num )
                 ++seq_num;
         }
         if( !h->stored_pts_num )
-            h->stored_pts_num = end + 1 - h->seek;
-        timecodes_num = h->stored_pts_num + h->seek;
+            h->stored_pts_num = end + 2;
+        timecodes_num = h->stored_pts_num;
         fseek( tcfile_in, file_pos, SEEK_SET );
 
         timecodes = malloc( timecodes_num * sizeof(double) );
@@ -187,24 +158,28 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
         if( assume_fps < 0 )
             goto fail;
         timecodes[0] = 0;
-        for( num = seq_num = 0; num < timecodes_num - 1; )
+        for( num = seq_num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
         {
-            fgets( buff, sizeof(buff), tcfile_in );
-            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            if( NO_TIMECODE_LINE )
                 continue;
             ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
             if( ret != 3 )
                 start = end = timecodes_num - 1;
-            if( h->auto_timebase_den || h->auto_timebase_num )
-                fpss[seq_num++] = seq_fps;
-            seq_fps = correct_fps( seq_fps, h );
-            if( seq_fps < 0 )
-                goto fail;
             for( ; num < start && num < timecodes_num - 1; num++ )
                 timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
-            for( num = start; num <= end && num < timecodes_num - 1; num++ )
-                timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
+            if( num < timecodes_num - 1 )
+            {
+                if( h->auto_timebase_den || h->auto_timebase_num )
+                    fpss[seq_num++] = seq_fps;
+                seq_fps = correct_fps( seq_fps, h );
+                if( seq_fps < 0 )
+                    goto fail;
+                for( num = start; num <= end && num < timecodes_num - 1; num++ )
+                    timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
+            }
         }
+        for( ; num < timecodes_num - 1; num++ )
+            timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
         if( h->auto_timebase_den || h->auto_timebase_num )
             fpss[seq_num] = h->assume_fps;
 
@@ -217,24 +192,28 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
             fseek( tcfile_in, file_pos, SEEK_SET );
             assume_fps_sig = sigexp10( h->assume_fps, &exponent );
             assume_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / assume_fps_sig ) / exponent );
-            for( num = 0; num < timecodes_num - 1; )
+            for( num = 0; num < timecodes_num - 1 && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
             {
-                fgets( buff, sizeof(buff), tcfile_in );
-                if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+                if( NO_TIMECODE_LINE )
                     continue;
                 ret = sscanf( buff, "%d,%d,%lf", &start, &end, &seq_fps );
                 if( ret != 3 )
                     start = end = timecodes_num - 1;
                 seq_fps_sig = sigexp10( seq_fps, &exponent );
                 seq_fps = MKV_TIMEBASE_DEN / ( round( MKV_TIMEBASE_DEN / seq_fps_sig ) / exponent );
-                for( ; num < start - h->seek && num < timecodes_num - 1; num++ )
+                for( ; num < start && num < timecodes_num - 1; num++ )
                     timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
                 for( num = start; num <= end && num < timecodes_num - 1; num++ )
                     timecodes[num + 1] = timecodes[num] + 1 / seq_fps;
             }
+            for( ; num < timecodes_num - 1; num++ )
+                timecodes[num + 1] = timecodes[num] + 1 / assume_fps;
         }
         if( fpss )
+        {
             free( fpss );
+            fpss = NULL;
+        }
 
         h->assume_fps = assume_fps;
         h->last_timecode = timecodes[timecodes_num - 1];
@@ -243,52 +222,43 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
     {
         uint64_t file_pos = ftell( tcfile_in );
 
-        num = h->stored_pts_num = 0;
+        h->stored_pts_num = 0;
         while( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
         {
-            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
+            if( NO_TIMECODE_LINE )
             {
-                if( !num )
+                if( !h->stored_pts_num )
                     file_pos = ftell( tcfile_in );
                 continue;
             }
-            if( num >= h->seek )
-                ++h->stored_pts_num;
-            ++num;
-        }
-        timecodes_num = h->stored_pts_num + h->seek;
-        if( !timecodes_num )
-        {
-            fprintf( stderr, "timecode [error]: input tcfile doesn't have any timecodes!\n" );
-            return -1;
+            h->stored_pts_num++;
         }
+        timecodes_num = h->stored_pts_num;
+        FAIL_IF_ERROR( !timecodes_num, "input tcfile doesn't have any timecodes!\n" )
         fseek( tcfile_in, file_pos, SEEK_SET );
 
         timecodes = malloc( timecodes_num * sizeof(double) );
         if( !timecodes )
             return -1;
 
-        fgets( buff, sizeof(buff), tcfile_in );
-        ret = sscanf( buff, "%lf", &timecodes[0] );
-        if( ret != 1 )
-        {
-            fprintf( stderr, "timecode [error]: invalid input tcfile for frame 0\n" );
-            goto fail;
-        }
-        for( num = 1; num < timecodes_num; )
+        num = 0;
+        if( fgets( buff, sizeof(buff), tcfile_in ) != NULL )
         {
-            fgets( buff, sizeof(buff), tcfile_in );
-            if( buff[0] == '#' || buff[0] == '\n' || buff[0] == '\r' )
-                continue;
-            ret = sscanf( buff, "%lf", &timecodes[num] );
-            timecodes[num] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
-            if( ret != 1 || timecodes[num] <= timecodes[num - 1] )
+            ret = sscanf( buff, "%lf", &timecodes[0] );
+            timecodes[0] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
+            FAIL_IF_ERROR( ret != 1, "invalid input tcfile for frame 0\n" )
+            for( num = 1; num < timecodes_num && fgets( buff, sizeof(buff), tcfile_in ) != NULL; )
             {
-                fprintf( stderr, "timecode [error]: invalid input tcfile for frame %d\n", num );
-                goto fail;
+                if( NO_TIMECODE_LINE )
+                    continue;
+                ret = sscanf( buff, "%lf", &timecodes[num] );
+                timecodes[num] *= 1e-3;         /* Timecode format v2 is expressed in milliseconds. */
+                FAIL_IF_ERROR( ret != 1 || timecodes[num] <= timecodes[num - 1],
+                               "invalid input tcfile for frame %d\n", num )
+                ++num;
             }
-            ++num;
         }
+        FAIL_IF_ERROR( num < timecodes_num, "failed to read input tcfile for frame %d", num )
 
         if( timecodes_num == 1 )
             h->timebase_den = info->fps_num;
@@ -300,22 +270,22 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
             for( num = 0; num < timecodes_num - 1; num++ )
             {
                 fpss[num] = 1 / (timecodes[num + 1] - timecodes[num]);
-                if( h->timebase_den >= 0 )
+                if( h->auto_timebase_den )
                 {
                     int i = 1;
-                    int fps_num, fps_den;
+                    uint64_t fps_num, fps_den;
                     double exponent;
                     double fps_sig = sigexp10( fpss[num], &exponent );
                     while( 1 )
                     {
                         fps_den = i * h->timebase_num;
                         fps_num = round( fps_den * fps_sig ) * exponent;
-                        if( fps_num < 0 || fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
+                        if( fps_num > UINT32_MAX || fabs( ((double)fps_num / fps_den) / exponent - fps_sig ) < DOUBLE_EPSILON )
                             break;
                         ++i;
                     }
-                    h->timebase_den = fps_num > 0 && h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
-                    if( h->timebase_den < 0 )
+                    h->timebase_den = fps_num && h->timebase_den ? lcm( h->timebase_den, fps_num ) : fps_num;
+                    if( h->timebase_den > UINT32_MAX )
                     {
                         h->auto_timebase_den = 0;
                         continue;
@@ -326,6 +296,7 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
                 if( try_mkv_timebase_den( fpss, h, timecodes_num - 1 ) < 0 )
                     goto fail;
             free( fpss );
+            fpss = NULL;
         }
 
         if( timecodes_num > 1 )
@@ -334,33 +305,24 @@ static int parse_tcfile( FILE *tcfile_in, timecode_hnd_t *h, video_info_t *info
             h->assume_fps = (double)info->fps_num / info->fps_den;
         h->last_timecode = timecodes[timecodes_num - 1];
     }
-
+#undef NO_TIMECODE_LINE
     if( h->auto_timebase_den || h->auto_timebase_num )
     {
-        x264_reduce_fraction( &h->timebase_num, &h->timebase_den );
-        fprintf( stderr, "timecode [info]: automatic timebase generation %d/%d\n", h->timebase_num, h->timebase_den );
-    }
-    else if( h->timebase_den <= 0 )
-    {
-        fprintf( stderr, "timecode [error]: automatic timebase generation failed.\n"
-                         "                  Specify an appropriate timebase manually.\n" );
-        goto fail;
+        uint64_t i = gcd( h->timebase_num, h->timebase_den );
+        h->timebase_num /= i;
+        h->timebase_den /= i;
+        x264_cli_log( "timecode", X264_LOG_INFO, "automatic timebase generation %"PRIu64"/%"PRIu64"\n", h->timebase_num, h->timebase_den );
     }
+    else FAIL_IF_ERROR( h->timebase_den > UINT32_MAX || !h->timebase_den, "automatic timebase generation failed.\n"
+                        "                  Specify an appropriate timebase manually.\n" )
 
     h->pts = malloc( h->stored_pts_num * sizeof(int64_t) );
     if( !h->pts )
         goto fail;
-    pts_seek_offset = (int64_t)( timecodes[h->seek] * ((double)h->timebase_den / h->timebase_num) + 0.5 );
-    h->pts[0] = 0;
-    for( num = 1; num < h->stored_pts_num; num++ )
+    for( num = 0; num < h->stored_pts_num; num++ )
     {
-        h->pts[num] = (int64_t)( timecodes[h->seek + num] * ((double)h->timebase_den / h->timebase_num) + 0.5 );
-        h->pts[num] -= pts_seek_offset;
-        if( h->pts[num] <= h->pts[num - 1] )
-        {
-            fprintf( stderr, "timecode [error]: invalid timebase or timecode for frame %d\n", num );
-            goto fail;
-        }
+        h->pts[num] = timecodes[num] * ((double)h->timebase_den / h->timebase_num) + 0.5;
+        FAIL_IF_ERROR( num > 0 && h->pts[num] <= h->pts[num - 1], "invalid timebase or timecode for frame %d\n", num )
     }
 
     free( timecodes );
@@ -382,39 +344,33 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
     int ret = 0;
     FILE *tcfile_in;
     timecode_hnd_t *h = malloc( sizeof(timecode_hnd_t) );
-    if( !h )
-    {
-        fprintf( stderr, "timecode [error]: malloc failed\n" );
-        return -1;
-    }
-    h->input = input;
+    FAIL_IF_ERROR( !h, "malloc failed\n" )
+    h->input = cli_input;
     h->p_handle = *p_handle;
-    h->frame_total = input.get_frame_total( h->p_handle );
-    h->seek = opt->seek;
+    h->pts = NULL;
     if( opt->timebase )
-        ret = sscanf( opt->timebase, "%d/%d", &h->timebase_num, &h->timebase_den );
-    if( ret == 1 )
-        h->timebase_num = atoi( opt->timebase );
+    {
+        ret = sscanf( opt->timebase, "%"SCNu64"/%"SCNu64, &h->timebase_num, &h->timebase_den );
+        if( ret == 1 )
+        {
+            h->timebase_num = strtoul( opt->timebase, NULL, 10 );
+            h->timebase_den = 0; /* set later by auto timebase generation */
+        }
+        FAIL_IF_ERROR( h->timebase_num > UINT32_MAX || h->timebase_den > UINT32_MAX,
+                       "timebase you specified exceeds H.264 maximum\n" )
+    }
     h->auto_timebase_num = !ret;
     h->auto_timebase_den = ret < 2;
     if( h->auto_timebase_num )
         h->timebase_num = info->fps_den; /* can be changed later by auto timebase generation */
     if( h->auto_timebase_den )
         h->timebase_den = 0;             /* set later by auto timebase generation */
-    timecode_input.picture_alloc = h->input.picture_alloc;
-    timecode_input.picture_clean = h->input.picture_clean;
-
-    *p_handle = h;
 
-    tcfile_in = fopen( psz_filename, "rb" );
-    if( !tcfile_in )
-    {
-        fprintf( stderr, "timecode [error]: can't open `%s'\n", psz_filename );
-        return -1;
-    }
+    tcfile_in = x264_fopen( psz_filename, "rb" );
+    FAIL_IF_ERROR( !tcfile_in, "can't open `%s'\n", psz_filename )
     else if( !x264_is_regular_file( tcfile_in ) )
     {
-        fprintf( stderr, "timecode [error]: tcfile input incompatible with non-regular file `%s'\n", psz_filename );
+        x264_cli_log( "timecode", X264_LOG_ERROR, "tcfile input incompatible with non-regular file `%s'\n", psz_filename );
         fclose( tcfile_in );
         return -1;
     }
@@ -432,42 +388,43 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c
     info->timebase_den = h->timebase_den;
     info->vfr = 1;
 
+    *p_handle = h;
     return 0;
 }
 
-static int get_frame_total( hnd_t handle )
-{
-    timecode_hnd_t *h = handle;
-    return h->frame_total;
-}
-
-static int read_frame( x264_picture_t *p_pic, hnd_t handle, int i_frame )
+static int64_t get_frame_pts( timecode_hnd_t *h, int frame, int real_frame )
 {
-    timecode_hnd_t *h = handle;
-    int ret = h->input.read_frame( p_pic, h->p_handle, i_frame );
-
-    if( i_frame - h->seek < h->stored_pts_num )
-    {
-        assert( i_frame >= h->seek );
-        p_pic->i_pts = h->pts[i_frame - h->seek];
-    }
+    if( frame < h->stored_pts_num )
+        return h->pts[frame];
     else
     {
-        if( h->pts )
+        if( h->pts && real_frame )
         {
-            fprintf( stderr, "timecode [info]: input timecode file missing data for frame %d and later\n"
-                             "                 assuming constant fps %.6f\n", i_frame, h->assume_fps );
+            x264_cli_log( "timecode", X264_LOG_INFO, "input timecode file missing data for frame %d and later\n"
+                          "                 assuming constant fps %.6f\n", frame, h->assume_fps );
             free( h->pts );
             h->pts = NULL;
         }
-        h->last_timecode += 1 / h->assume_fps;
-        p_pic->i_pts = (int64_t)( h->last_timecode * ((double)h->timebase_den / h->timebase_num) + 0.5 );
+        double timecode = h->last_timecode + 1 / h->assume_fps;
+        if( real_frame )
+            h->last_timecode = timecode;
+        return timecode * ((double)h->timebase_den / h->timebase_num) + 0.5;
     }
+}
+
+static int read_frame( cli_pic_t *pic, hnd_t handle, int frame )
+{
+    timecode_hnd_t *h = handle;
+    if( h->input.read_frame( pic, h->p_handle, frame ) )
+        return -1;
 
-    return ret;
+    pic->pts = get_frame_pts( h, frame, 1 );
+    pic->duration = get_frame_pts( h, frame + 1, 0 ) - pic->pts;
+
+    return 0;
 }
 
-static int release_frame( x264_picture_t *pic, hnd_t handle )
+static int release_frame( cli_pic_t *pic, hnd_t handle )
 {
     timecode_hnd_t *h = handle;
     if( h->input.release_frame )
@@ -475,6 +432,18 @@ static int release_frame( x264_picture_t *pic, hnd_t handle )
     return 0;
 }
 
+static int picture_alloc( cli_pic_t *pic, hnd_t handle, int csp, int width, int height )
+{
+    timecode_hnd_t *h = handle;
+    return h->input.picture_alloc( pic, h->p_handle, csp, width, height );
+}
+
+static void picture_clean( cli_pic_t *pic, hnd_t handle )
+{
+    timecode_hnd_t *h = handle;
+    h->input.picture_clean( pic, h->p_handle );
+}
+
 static int close_file( hnd_t handle )
 {
     timecode_hnd_t *h = handle;
@@ -485,4 +454,4 @@ static int close_file( hnd_t handle )
     return 0;
 }
 
-cli_input_t timecode_input = { open_file, get_frame_total, NULL, read_frame, release_frame, NULL, close_file };
+const cli_input_t timecode_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file };