1 /*****************************************************************************
2 * vdr.c: VDR recordings access plugin
3 *****************************************************************************
4 * Copyright (C) 2010 Tobias Güntner
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
22 VDR splits recordings into multiple files and stores each recording in a
23 separate directory. If VLC opens a normal directory, the filesystem module
24 will add all files to the playlist. If, however, VLC opens a VDR recording,
25 this module will join all files within that directory and provide a single
26 continuous stream instead.
28 VDR recordings have either of two directory layouts:
30 /path/to/0000-00-00.00.00.00.00.rec/
31 001.vdr, 002.vdr, 003.vdr, ...
32 index.vdr, info.vdr, marks.vdr, ...
34 /path/to/0000-00-00.00.00.0-0.rec/
35 00001.ts, 00002.ts, 00003.ts, ...
36 index, info, marks, ...
37 See http://www.vdr-wiki.de/ and http://www.tvdr.de/ for more information.
40 /*****************************************************************************
42 *****************************************************************************/
48 #include <sys/types.h>
57 #include <vlc_common.h>
58 #include <vlc_plugin.h>
59 #include <vlc_access.h>
60 #include <vlc_input.h>
62 #include <vlc_charset.h>
63 #include <vlc_dialog.h>
64 #include <vlc_configuration.h>
66 /*****************************************************************************
68 *****************************************************************************/
69 static int Open ( vlc_object_t * );
70 static void Close( vlc_object_t * );
72 #define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).")
74 #define CHAPTER_OFFSET_TEXT N_("Chapter offset in ms")
75 #define CHAPTER_OFFSET_LONGTEXT N_( \
76 "Move all chapters. This value should be set in milliseconds." )
78 #define FPS_TEXT N_("Frame rate")
79 #define FPS_LONGTEXT N_( \
80 "Default frame rate for chapter import." )
83 set_category( CAT_INPUT )
84 set_shortname( N_("VDR") )
86 set_subcategory( SUBCAT_INPUT_ACCESS )
87 set_description( N_("VDR recordings") )
88 add_integer( "vdr-chapter-offset", 0,
89 CHAPTER_OFFSET_TEXT, CHAPTER_OFFSET_LONGTEXT, true )
90 add_float_with_range( "vdr-fps", 25, 1, 1000,
91 FPS_TEXT, FPS_LONGTEXT, true )
92 set_capability( "access", 60 )
94 add_shortcut( "directory" )
96 add_shortcut( "file" )
97 set_callbacks( Open, Close )
100 /*****************************************************************************
101 * Local prototypes, constants, structures
102 *****************************************************************************/
104 /* minimum chapter size in seconds */
105 #define MIN_CHAPTER_SIZE 5
107 TYPEDEF_ARRAY( uint64_t, size_array_t );
111 /* file sizes of all parts */
112 size_array_t file_sizes;
113 uint64_t size; /* total size */
115 /* index and fd of current open file */
116 unsigned i_current_file;
123 input_title_t *p_marks;
124 unsigned cur_seekpoint;
127 /* file format: true=TS, false=PES */
131 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
132 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
133 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
135 static int Control( access_t *, int, va_list );
136 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len );
137 static int Seek( access_t *p_access, uint64_t i_pos);
138 static void FindSeekpoint( access_t *p_access );
139 static bool ScanDirectory( access_t *p_access );
140 static char *GetFilePath( access_t *p_access, unsigned i_file );
141 static bool ImportNextFile( access_t *p_access );
142 static bool SwitchFile( access_t *p_access, unsigned i_file );
143 static void OptimizeForRead( int fd );
144 static void UpdateFileSize( access_t *p_access );
145 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file );
146 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file );
147 static void ImportMeta( access_t *p_access );
148 static void ImportMarks( access_t *p_access );
149 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
150 uint64_t *pi_offset, uint16_t *pi_file_num );
151 static int64_t ParseFrameNumber( const char *psz_line, float fps );
152 static const char *BaseName( const char *psz_path );
154 /*****************************************************************************
156 *****************************************************************************/
157 static int Open( vlc_object_t *p_this )
159 access_t *p_access = (access_t*)p_this;
161 if( !p_access->psz_filepath )
164 /* Some tests can be skipped if this module was explicitly requested.
165 * That way, the user can play "corrupt" recordings if necessary
166 * and we can avoid false positives in the general case. */
167 bool b_strict = strcmp( p_access->psz_access, "vdr" );
169 /* Do a quick test based on the directory name to see if this
170 * directory might contain a VDR recording. We can be reasonably
171 * sure if ScanDirectory() actually finds files. */
174 char psz_extension[4];
176 const char *psz_name = BaseName( p_access->psz_filepath );
177 if( sscanf( psz_name, "%*u-%*u-%*u.%*u.%*u.%*u%*[-.]%*u.%3s%n",
178 psz_extension, &i_length ) != 1 || strcasecmp( psz_extension, "rec" ) ||
179 ( psz_name[i_length] != DIR_SEP_CHAR && psz_name[i_length] != '\0' ) )
183 /* Only directories can be recordings */
185 if( vlc_stat( p_access->psz_filepath, &st ) ||
186 !S_ISDIR( st.st_mode ) )
190 STANDARD_READ_ACCESS_INIT;
192 p_sys->cur_seekpoint = 0;
193 p_sys->fps = var_InheritFloat( p_access, "vdr-fps" );
194 ARRAY_INIT( p_sys->file_sizes );
196 /* Import all files and prepare playback. */
197 if( !ScanDirectory( p_access ) ||
198 !SwitchFile( p_access, 0 ) )
204 free( p_access->psz_demux );
205 p_access->psz_demux = strdup( p_sys->b_ts_format ? "ts" : "ps" );
209 /*****************************************************************************
210 * Close files and free resources
211 *****************************************************************************/
212 static void Close( vlc_object_t * p_this )
214 access_t *p_access = (access_t*)p_this;
215 access_sys_t *p_sys = p_access->p_sys;
217 if( p_sys->fd != -1 )
219 ARRAY_RESET( p_sys->file_sizes );
222 vlc_meta_Delete( p_sys->p_meta );
224 vlc_input_title_Delete( p_sys->p_marks );
228 /*****************************************************************************
229 * Determine format and import files
230 *****************************************************************************/
231 static bool ScanDirectory( access_t *p_access )
233 access_sys_t *p_sys = p_access->p_sys;
235 /* find first part and determine directory format */
236 p_sys->b_ts_format = true;
237 if( !ImportNextFile( p_access ) )
239 p_sys->b_ts_format = !p_sys->b_ts_format;
240 if( !ImportNextFile( p_access ) )
244 /* get all remaining parts */
245 while( ImportNextFile( p_access ) )
248 /* import meta data etc. */
249 ImportMeta( p_access );
251 /* cut marks depend on meta data and file sizes */
252 ImportMarks( p_access );
257 /*****************************************************************************
258 * Control input stream
259 *****************************************************************************/
260 static int Control( access_t *p_access, int i_query, va_list args )
262 access_sys_t *p_sys = p_access->p_sys;
263 input_title_t ***ppp_title;
270 case ACCESS_CAN_SEEK:
271 case ACCESS_CAN_FASTSEEK:
272 case ACCESS_CAN_PAUSE:
273 case ACCESS_CAN_CONTROL_PACE:
274 *va_arg( args, bool* ) = true;
277 case ACCESS_GET_SIZE:
278 *va_arg( args, uint64_t* ) = p_sys->size;
281 case ACCESS_GET_PTS_DELAY:
282 pi64 = va_arg( args, int64_t * );
283 *pi64 = INT64_C(1000)
284 * var_InheritInteger( p_access, "file-caching" );
287 case ACCESS_SET_PAUSE_STATE:
291 case ACCESS_GET_TITLE_INFO:
292 /* return a copy of our seek points */
293 if( !p_sys->p_marks )
295 ppp_title = va_arg( args, input_title_t*** );
296 *va_arg( args, int* ) = 1;
297 *ppp_title = malloc( sizeof( **ppp_title ) );
300 **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks );
303 case ACCESS_GET_TITLE:
304 *va_arg( args, unsigned * ) = 0;
307 case ACCESS_GET_SEEKPOINT:
308 *va_arg( args, unsigned * ) = p_sys->cur_seekpoint;
311 case ACCESS_SET_TITLE:
312 /* ignore - only one title */
315 case ACCESS_SET_SEEKPOINT:
316 i = va_arg( args, int );
317 return Seek( p_access, p_sys->p_marks->seekpoint[i]->i_byte_offset );
319 case ACCESS_GET_META:
322 p_meta = va_arg( args, vlc_meta_t* );
323 vlc_meta_Merge( p_meta, p_sys->p_meta );
332 /*****************************************************************************
333 * Read and concatenate files
334 *****************************************************************************/
335 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
337 access_sys_t *p_sys = p_access->p_sys;
339 if( p_sys->fd == -1 )
342 p_access->info.b_eof = true;
346 ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
351 p_access->info.i_pos += i_ret;
352 UpdateFileSize( p_access );
353 FindSeekpoint( p_access );
356 else if( i_ret == 0 )
358 /* check for new files in case the recording is still active */
359 if( p_sys->i_current_file >= FILE_COUNT - 1 )
360 ImportNextFile( p_access );
362 SwitchFile( p_access, p_sys->i_current_file + 1 );
365 else if( errno == EINTR )
367 /* try again later */
372 /* abort on read error */
373 msg_Err( p_access, "failed to read (%s)", vlc_strerror_c(errno) );
374 dialog_Fatal( p_access, _("File reading failed"),
375 _("VLC could not read the file (%s)."),
376 vlc_strerror(errno) );
377 SwitchFile( p_access, -1 );
382 /*****************************************************************************
383 * Seek to a specific location in a file
384 *****************************************************************************/
385 static int Seek( access_t *p_access, uint64_t i_pos )
387 access_sys_t *p_sys = p_access->p_sys;
389 /* might happen if called by ACCESS_SET_SEEKPOINT */
390 i_pos = __MIN( i_pos, p_sys->size );
392 p_access->info.i_pos = i_pos;
393 p_access->info.b_eof = false;
395 /* find correct chapter */
396 FindSeekpoint( p_access );
398 /* find correct file */
400 while( i_file < FILE_COUNT - 1 &&
401 i_pos >= FILE_SIZE( i_file ) )
403 i_pos -= FILE_SIZE( i_file );
406 if( !SwitchFile( p_access, i_file ) )
409 /* adjust position within that file */
410 return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ?
411 VLC_SUCCESS : VLC_EGENERIC;
414 /*****************************************************************************
415 * Change the chapter index to match the current position
416 *****************************************************************************/
417 static void FindSeekpoint( access_t *p_access )
419 access_sys_t *p_sys = p_access->p_sys;
420 if( !p_sys->p_marks )
423 int new_seekpoint = p_sys->cur_seekpoint;
424 if( p_access->info.i_pos < (uint64_t)p_sys->p_marks->
425 seekpoint[p_sys->cur_seekpoint]->i_byte_offset )
427 /* i_pos moved backwards, start fresh */
431 /* only need to check the following seekpoints */
432 while( new_seekpoint + 1 < p_sys->p_marks->i_seekpoint &&
433 p_access->info.i_pos >= (uint64_t)p_sys->p_marks->
434 seekpoint[new_seekpoint + 1]->i_byte_offset )
439 p_sys->cur_seekpoint = new_seekpoint;
442 /*****************************************************************************
443 * Returns the path of a certain part
444 *****************************************************************************/
445 static char *GetFilePath( access_t *p_access, unsigned i_file )
448 if( asprintf( &psz_path, p_access->p_sys->b_ts_format ?
449 "%s" DIR_SEP "%05u.ts" : "%s" DIR_SEP "%03u.vdr",
450 p_access->psz_filepath, i_file + 1 ) == -1 )
456 /*****************************************************************************
457 * Check if another part exists and import it
458 *****************************************************************************/
459 static bool ImportNextFile( access_t *p_access )
461 access_sys_t *p_sys = p_access->p_sys;
463 char *psz_path = GetFilePath( p_access, FILE_COUNT );
468 if( vlc_stat( psz_path, &st ) )
470 msg_Dbg( p_access, "could not stat %s: %s", psz_path,
471 vlc_strerror_c(errno) );
475 if( !S_ISREG( st.st_mode ) )
477 msg_Dbg( p_access, "%s is not a regular file", psz_path );
481 msg_Dbg( p_access, "%s exists", psz_path );
484 ARRAY_APPEND( p_sys->file_sizes, st.st_size );
485 p_sys->size += st.st_size;
490 /*****************************************************************************
491 * Close the current file and open another
492 *****************************************************************************/
493 static bool SwitchFile( access_t *p_access, unsigned i_file )
495 access_sys_t *p_sys = p_access->p_sys;
497 /* requested file already open? */
498 if( p_sys->fd != -1 && p_sys->i_current_file == i_file )
502 if( p_sys->fd != -1 )
509 if( i_file >= FILE_COUNT )
511 p_sys->i_current_file = i_file;
514 char *psz_path = GetFilePath( p_access, i_file );
517 p_sys->fd = vlc_open( psz_path, O_RDONLY );
519 if( p_sys->fd == -1 )
521 msg_Err( p_access, "Failed to open %s: %s", psz_path,
522 vlc_strerror_c(errno) );
526 /* cannot handle anything except normal files */
528 if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) )
530 msg_Err( p_access, "%s is not a regular file", psz_path );
534 OptimizeForRead( p_sys->fd );
536 msg_Dbg( p_access, "opened %s", psz_path );
541 dialog_Fatal (p_access, _("File reading failed"), _("VLC could not"
542 " open the file \"%s\" (%s)."), psz_path, vlc_strerror(errno) );
543 if( p_sys->fd != -1 )
552 /*****************************************************************************
553 * Some tweaks to speed up read()
554 *****************************************************************************/
555 static void OptimizeForRead( int fd )
557 /* cf. Open() in file access module */
559 #ifdef HAVE_POSIX_FADVISE
560 posix_fadvise( fd, 0, 4096, POSIX_FADV_WILLNEED );
561 posix_fadvise( fd, 0, 0, POSIX_FADV_NOREUSE );
564 fcntl( fd, F_RDAHEAD, 1 );
567 fcntl( fd, F_NOCACHE, 0 );
571 /*****************************************************************************
572 * Fix size if the (last) part is still growing
573 *****************************************************************************/
574 static void UpdateFileSize( access_t *p_access )
576 access_sys_t *p_sys = p_access->p_sys;
579 if( p_sys->size >= p_access->info.i_pos )
582 /* TODO: not sure if this can happen or what to do in this case */
583 if( fstat( p_sys->fd, &st ) )
585 if( (uint64_t)st.st_size <= CURRENT_FILE_SIZE )
588 p_sys->size -= CURRENT_FILE_SIZE;
589 CURRENT_FILE_SIZE = st.st_size;
590 p_sys->size += CURRENT_FILE_SIZE;
593 /*****************************************************************************
594 * Open file relative to base directory for reading.
595 *****************************************************************************/
596 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file )
598 /* build path and add extension */
600 if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
601 p_access->psz_filepath, psz_file,
602 p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
605 FILE *file = vlc_fopen( psz_path, "rb" );
607 msg_Warn( p_access, "Failed to open %s: %s", psz_path,
608 vlc_strerror_c(errno) );
614 /*****************************************************************************
615 * Read a line of text. Returns false on error or EOF.
616 *****************************************************************************/
617 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file )
619 ssize_t read = getline( ppsz_line, pi_size, p_file );
623 /* automatically free buffer on eof */
629 if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' )
630 (*ppsz_line)[ read - 1 ] = '\0';
631 EnsureUTF8( *ppsz_line );
636 /*****************************************************************************
638 *****************************************************************************/
639 static void ImportMeta( access_t *p_access )
641 access_sys_t *p_sys = p_access->p_sys;
643 FILE *infofile = OpenRelativeFile( p_access, "info" );
647 vlc_meta_t *p_meta = vlc_meta_New();
648 p_sys->p_meta = p_meta;
657 char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL;
659 while( ReadLine( &line, &line_len, infofile ) )
661 if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' )
665 char *text = line + 2;
669 char *psz_name = strchr( text, ' ' );
673 vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
675 vlc_meta_AddExtra( p_meta, "Transponder", text );
678 else if( tag == 'E' )
680 unsigned i_id, i_start, i_length;
681 if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 )
685 time_t start = i_start;
686 localtime_r( &start, &tm );
689 strftime( str, sizeof(str), "%Y-%m-%d %H:%M", &tm );
690 vlc_meta_AddExtra( p_meta, "Date", str );
692 psz_date = strdup( str );
694 /* display in minutes */
695 i_length = ( i_length + 59 ) / 60;
696 snprintf( str, sizeof(str), "%u:%02u", i_length / 60, i_length % 60 );
697 vlc_meta_AddExtra( p_meta, "Duration", str );
701 else if( tag == 'T' )
704 psz_title = strdup( text );
705 vlc_meta_AddExtra( p_meta, "Title", text );
708 else if( tag == 'S' )
710 free( psz_smalltext );
711 psz_smalltext = strdup( text );
712 vlc_meta_AddExtra( p_meta, "Info", text );
715 else if( tag == 'D' )
717 for( char *p = text; *p; ++p )
722 vlc_meta_SetDescription( p_meta, text );
725 /* FPS are required to convert between timestamps and frames */
726 else if( tag == 'F' )
728 float fps = atof( text );
731 vlc_meta_AddExtra( p_meta, "Frame Rate", text );
734 else if( tag == 'P' )
736 vlc_meta_AddExtra( p_meta, "Priority", text );
739 else if( tag == 'L' )
741 vlc_meta_AddExtra( p_meta, "Lifetime", text );
745 /* create a meaningful title */
747 ( psz_title ? strlen( psz_title ) : 0 ) +
748 ( psz_smalltext ? strlen( psz_smalltext ) : 0 ) +
749 ( psz_date ? strlen( psz_date ) : 0 );
750 char *psz_display = malloc( i_len );
756 strcat( psz_display, psz_title );
757 if( psz_title && psz_smalltext )
758 strcat( psz_display, " - " );
760 strcat( psz_display, psz_smalltext );
761 if( ( psz_title || psz_smalltext ) && psz_date )
763 strcat( psz_display, " (" );
764 strcat( psz_display, psz_date );
765 strcat( psz_display, ")" );
768 vlc_meta_SetTitle( p_meta, psz_display );
773 free( psz_smalltext );
779 /*****************************************************************************
780 * Import cut marks and convert them to seekpoints (chapters).
781 *****************************************************************************/
782 static void ImportMarks( access_t *p_access )
784 access_sys_t *p_sys = p_access->p_sys;
786 FILE *marksfile = OpenRelativeFile( p_access, "marks" );
790 FILE *indexfile = OpenRelativeFile( p_access, "index" );
797 /* get the length of this recording (index stores 8 bytes per frame) */
799 if( fstat( fileno( indexfile ), &st ) )
805 int64_t i_frame_count = st.st_size / 8;
807 /* Put all cut marks in a "dummy" title */
808 input_title_t *p_marks = vlc_input_title_New();
815 p_marks->psz_name = strdup( _("VDR Cut Marks") );
816 p_marks->i_length = i_frame_count * (int64_t)( CLOCK_FREQ / p_sys->fps );
817 p_marks->i_size = p_sys->size;
819 /* offset for chapter positions */
820 int i_chapter_offset = p_sys->fps / 1000 *
821 var_InheritInteger( p_access, "vdr-chapter-offset" );
823 /* minimum chapter size in frames */
824 int i_min_chapter_size = p_sys->fps * MIN_CHAPTER_SIZE;
826 /* the last chapter started at this frame (init to 0 so
827 * we skip useless chapters near the beginning as well) */
828 int64_t i_prev_chapter = 0;
830 /* parse lines of the form "0:00:00.00 foobar" */
833 while( ReadLine( &line, &line_len, marksfile ) )
835 int64_t i_frame = ParseFrameNumber( line, p_sys->fps );
837 /* skip chapters which are near the end or too close to each other */
838 if( i_frame - i_prev_chapter < i_min_chapter_size ||
839 i_frame >= i_frame_count - i_min_chapter_size )
841 i_prev_chapter = i_frame;
843 /* move chapters (simple workaround for inaccurate cut marks) */
844 if( i_frame > -i_chapter_offset )
845 i_frame += i_chapter_offset;
850 uint16_t i_file_number;
851 if( !ReadIndexRecord( indexfile, p_sys->b_ts_format,
852 i_frame, &i_offset, &i_file_number ) )
854 if( i_file_number < 1 || i_file_number > FILE_COUNT )
857 /* add file sizes to get the "global" offset */
858 seekpoint_t *sp = vlc_seekpoint_New();
861 sp->i_time_offset = i_frame * (int64_t)( CLOCK_FREQ / p_sys->fps );
862 sp->i_byte_offset = i_offset;
863 for( int i = 0; i + 1 < i_file_number; ++i )
864 sp->i_byte_offset += FILE_SIZE( i );
865 sp->psz_name = strdup( line );
867 TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
870 /* add a chapter at the beginning if missing */
871 if( p_marks->i_seekpoint > 0 && p_marks->seekpoint[0]->i_byte_offset > 0 )
873 seekpoint_t *sp = vlc_seekpoint_New();
876 sp->i_byte_offset = 0;
877 sp->i_time_offset = 0;
878 sp->psz_name = strdup( _("Start") );
879 TAB_INSERT( p_marks->i_seekpoint, p_marks->seekpoint, sp, 0 );
883 if( p_marks->i_seekpoint > 0 )
884 p_sys->p_marks = p_marks;
886 vlc_input_title_Delete( p_marks );
892 /*****************************************************************************
893 * Lookup frame offset in index file
894 *****************************************************************************/
895 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
896 uint64_t *pi_offset, uint16_t *pi_file_num )
898 uint8_t index_record[8];
899 if( fseek( p_file, sizeof(index_record) * i_frame, SEEK_SET ) != 0 )
901 if( fread( &index_record, sizeof(index_record), 1, p_file ) < 1 )
904 /* VDR usually (only?) runs on little endian machines, but VLC has a
905 * broader audience. See recording.* in VDR source for data layout. */
908 uint64_t i_index_entry = GetQWLE( &index_record );
909 *pi_offset = i_index_entry & UINT64_C(0xFFFFFFFFFF);
910 *pi_file_num = i_index_entry >> 48;
914 *pi_offset = GetDWLE( &index_record );
915 *pi_file_num = index_record[5];
921 /*****************************************************************************
922 * Convert time stamp from file to frame number
923 *****************************************************************************/
924 static int64_t ParseFrameNumber( const char *psz_line, float fps )
926 unsigned h, m, s, f, n;
928 /* hour:min:sec.frame (frame is optional) */
929 n = sscanf( psz_line, "%u:%u:%u.%u", &h, &m, &s, &f );
934 int64_t i_seconds = (int64_t)h * 3600 + (int64_t)m * 60 + s;
935 return (int64_t)( i_seconds * (double)fps ) + __MAX(1, f) - 1;
938 /* only a frame number */
939 int64_t i_frame = strtoll( psz_line, NULL, 10 );
940 return __MAX(1, i_frame) - 1;
943 /*****************************************************************************
944 * Return the last path component (including trailing separators)
945 *****************************************************************************/
946 static const char *BaseName( const char *psz_path )
948 const char *psz_name = psz_path + strlen( psz_path );
950 /* skip superfluous separators at the end */
951 while( psz_name > psz_path && psz_name[-1] == DIR_SEP_CHAR )
954 /* skip last component */
955 while( psz_name > psz_path && psz_name[-1] != DIR_SEP_CHAR )