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
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 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 General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, 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 001.ts, 002.ts, 003.ts, ...
36 index, info, marks, ...
37 See http://www.vdr-wiki.de/ and http://www.tvdr.de/ for more information.
40 /*****************************************************************************
42 *****************************************************************************/
48 #ifdef HAVE_SYS_TYPES_H
49 # include <sys/types.h>
51 #ifdef HAVE_SYS_STAT_H
52 # include <sys/stat.h>
59 #elif defined( WIN32 ) && !defined( UNDER_CE )
67 #if defined( WIN32 ) && !defined( UNDER_CE )
69 # define lseek _lseeki64
72 #include <vlc_common.h>
73 #include <vlc_plugin.h>
74 #include <vlc_access.h>
75 #include <vlc_input.h>
77 #include <vlc_charset.h>
78 #include <vlc_dialog.h>
79 #include <vlc_configuration.h>
81 /*****************************************************************************
83 *****************************************************************************/
84 static int Open ( vlc_object_t
* );
85 static void Close( vlc_object_t
* );
87 #define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).")
89 #define CHAPTER_OFFSET_TEXT N_("Chapter offset in ms")
90 #define CHAPTER_OFFSET_LONGTEXT N_( \
91 "Move all chapters. This value should be set in milliseconds." )
93 #define FPS_TEXT N_("Frame rate")
94 #define FPS_LONGTEXT N_( \
95 "Default frame rate for chapter import." )
98 set_category( CAT_INPUT
)
99 set_shortname( N_("VDR") )
100 set_help( HELP_TEXT
)
101 set_subcategory( SUBCAT_INPUT_ACCESS
)
102 set_description( N_("VDR recordings") )
103 add_integer( "vdr-chapter-offset", 0,
104 CHAPTER_OFFSET_TEXT
, CHAPTER_OFFSET_LONGTEXT
, true )
105 add_float_with_range( "vdr-fps", 25, 1, 1000,
106 FPS_TEXT
, FPS_LONGTEXT
, true )
107 set_capability( "access", 60 )
108 add_shortcut( "vdr" )
109 add_shortcut( "directory" )
110 add_shortcut( "dir" )
111 add_shortcut( "file" )
112 set_callbacks( Open
, Close
)
115 /*****************************************************************************
116 * Local prototypes, constants, structures
117 *****************************************************************************/
119 /* minimum chapter size in seconds */
120 #define MIN_CHAPTER_SIZE 5
122 TYPEDEF_ARRAY( uint64_t, size_array_t
);
126 /* file sizes of all parts */
127 size_array_t file_sizes
;
129 /* index and fd of current open file */
130 unsigned i_current_file
;
137 input_title_t
*p_marks
;
140 /* file format: true=TS, false=PES */
144 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
145 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
146 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
148 static int Control( access_t
*, int, va_list );
149 static ssize_t
Read( access_t
*p_access
, uint8_t *p_buffer
, size_t i_len
);
150 static int Seek( access_t
*p_access
, uint64_t i_pos
);
151 static void FindSeekpoint( access_t
*p_access
);
152 static bool ScanDirectory( access_t
*p_access
, bool b_strict
);
153 static char *GetFilePath( access_t
*p_access
, unsigned i_file
);
154 static bool ImportNextFile( access_t
*p_access
);
155 static bool SwitchFile( access_t
*p_access
, unsigned i_file
);
156 static void OptimizeForRead( int fd
);
157 static void UpdateFileSize( access_t
*p_access
);
158 static int StatRelativeFile( access_t
*p_access
, const char *psz_file
,
159 struct stat
*p_stat
);
160 static FILE *OpenRelativeFile( access_t
*p_access
, const char *psz_file
);
161 static bool ReadLine( char **ppsz_line
, size_t *pi_size
, FILE *p_file
);
162 static void ImportMeta( access_t
*p_access
);
163 static void ImportMarks( access_t
*p_access
);
164 static bool ReadIndexRecord( FILE *p_file
, bool b_ts
, int64_t i_frame
,
165 uint64_t *pi_offset
, uint16_t *pi_file_num
);
166 static int64_t ParseFrameNumber( const char *psz_line
, float fps
);
168 /*****************************************************************************
170 *****************************************************************************/
171 static int Open( vlc_object_t
*p_this
)
173 access_t
*p_access
= (access_t
*)p_this
;
175 if( !p_access
->psz_filepath
)
178 /* Some tests can be skipped if this module was explicitly requested.
179 * That way, the user can play "corrupt" recordings if necessary
180 * and we can avoid false positives in the general case. */
181 bool b_strict
= strcmp( p_access
->psz_access
, "vdr" );
183 /* Do a quick test based on the directory extension to see if this
184 * directory might contain a VDR recording. We can be reasonably
185 * sure if ScanDirectory() actually finds files. */
188 const char *psz_ext
= strrchr( p_access
->psz_filepath
, '.' );
189 if( !psz_ext
|| strcasecmp( psz_ext
, ".rec" ) )
193 /* Only directories can be recordings */
195 if( vlc_stat( p_access
->psz_filepath
, &st
) ||
196 !S_ISDIR( st
.st_mode
) )
200 STANDARD_READ_ACCESS_INIT
;
202 p_sys
->fps
= var_InheritFloat( p_access
, "vdr-fps" );
203 ARRAY_INIT( p_sys
->file_sizes
);
205 /* Import all files and prepare playback. */
206 if( !ScanDirectory( p_access
, b_strict
) ||
207 !SwitchFile( p_access
, 0 ) )
216 /*****************************************************************************
217 * Close files and free resources
218 *****************************************************************************/
219 static void Close( vlc_object_t
* p_this
)
221 access_t
*p_access
= (access_t
*)p_this
;
222 access_sys_t
*p_sys
= p_access
->p_sys
;
224 if( p_sys
->fd
!= -1 )
226 ARRAY_RESET( p_sys
->file_sizes
);
229 vlc_meta_Delete( p_sys
->p_meta
);
231 vlc_input_title_Delete( p_sys
->p_marks
);
235 /*****************************************************************************
236 * Determine format and import files
237 *****************************************************************************/
238 static bool ScanDirectory( access_t
*p_access
, bool b_strict
)
240 access_sys_t
*p_sys
= p_access
->p_sys
;
242 /* find first part and determine directory format */
243 p_sys
->b_ts_format
= true;
244 if( !ImportNextFile( p_access
) )
246 p_sys
->b_ts_format
= !p_sys
->b_ts_format
;
247 if( !ImportNextFile( p_access
) )
251 /* meta data and index should exist */
255 if( StatRelativeFile( p_access
, "info", &st
) ||
256 StatRelativeFile( p_access
, "index", &st
) )
260 /* get all remaining parts */
261 while( ImportNextFile( p_access
) )
264 /* import meta data etc. */
265 ImportMeta( p_access
);
267 /* cut marks depend on meta data and file sizes */
268 ImportMarks( p_access
);
273 /*****************************************************************************
274 * Control input stream
275 *****************************************************************************/
276 static int Control( access_t
*p_access
, int i_query
, va_list args
)
278 access_sys_t
*p_sys
= p_access
->p_sys
;
279 input_title_t
***ppp_title
;
286 case ACCESS_CAN_SEEK
:
287 case ACCESS_CAN_PAUSE
:
288 case ACCESS_CAN_CONTROL_PACE
:
289 *va_arg( args
, bool* ) = true;
292 case ACCESS_CAN_FASTSEEK
:
293 /* Seek() can open files, so it might be "too slow" */
294 *va_arg( args
, bool* ) = false;
297 case ACCESS_GET_PTS_DELAY
:
298 pi64
= va_arg( args
, int64_t * );
299 *pi64
= INT64_C(1000)
300 * var_InheritInteger( p_access
, "file-caching" );
303 case ACCESS_SET_PAUSE_STATE
:
307 case ACCESS_GET_TITLE_INFO
:
308 /* return a copy of our seek points */
309 if( !p_sys
->p_marks
)
311 ppp_title
= va_arg( args
, input_title_t
*** );
312 *va_arg( args
, int* ) = 1;
313 *ppp_title
= malloc( sizeof( input_title_t
** ) );
316 **ppp_title
= vlc_input_title_Duplicate( p_sys
->p_marks
);
319 case ACCESS_SET_TITLE
:
320 /* ignore - only one title */
323 case ACCESS_SET_SEEKPOINT
:
324 i
= va_arg( args
, int );
325 /* Seek updates p_access->info */
326 return Seek( p_access
, p_sys
->p_marks
->seekpoint
[i
]->i_byte_offset
);
328 case ACCESS_GET_META
:
331 p_meta
= va_arg( args
, vlc_meta_t
* );
332 vlc_meta_Merge( p_meta
, p_sys
->p_meta
);
335 case ACCESS_SET_PRIVATE_ID_STATE
:
336 case ACCESS_GET_CONTENT_TYPE
:
340 msg_Warn( p_access
, "unimplemented query in control" );
346 /*****************************************************************************
347 * Read and concatenate files
348 *****************************************************************************/
349 static ssize_t
Read( access_t
*p_access
, uint8_t *p_buffer
, size_t i_len
)
351 access_sys_t
*p_sys
= p_access
->p_sys
;
353 if( p_sys
->fd
== -1 )
356 p_access
->info
.b_eof
= true;
360 ssize_t i_ret
= read( p_sys
->fd
, p_buffer
, i_len
);
365 p_access
->info
.i_pos
+= i_ret
;
366 UpdateFileSize( p_access
);
367 FindSeekpoint( p_access
);
370 else if( i_ret
== 0 )
372 /* check for new files in case the recording is still active */
373 if( p_sys
->i_current_file
>= FILE_COUNT
- 1 )
374 ImportNextFile( p_access
);
376 SwitchFile( p_access
, p_sys
->i_current_file
+ 1 );
379 else if( errno
== EINTR
)
381 /* try again later */
386 /* abort on read error */
387 msg_Err( p_access
, "failed to read (%m)" );
388 dialog_Fatal( p_access
, _("File reading failed"), "%s (%m)",
389 _("VLC could not read the file.") );
390 SwitchFile( p_access
, -1 );
395 /*****************************************************************************
396 * Seek to a specific location in a file
397 *****************************************************************************/
398 static int Seek( access_t
*p_access
, uint64_t i_pos
)
400 access_sys_t
*p_sys
= p_access
->p_sys
;
402 /* might happen if called by ACCESS_SET_SEEKPOINT */
403 i_pos
= __MIN( i_pos
, p_access
->info
.i_size
);
405 p_access
->info
.i_pos
= i_pos
;
406 p_access
->info
.b_eof
= false;
408 /* find correct chapter */
409 FindSeekpoint( p_access
);
411 /* find correct file */
413 while( i_pos
>= FILE_SIZE( i_file
) &&
414 i_file
< FILE_COUNT
- 1 )
416 i_pos
-= FILE_SIZE( i_file
);
419 if( !SwitchFile( p_access
, i_file
) )
422 /* adjust position within that file */
423 return lseek( p_sys
->fd
, i_pos
, SEEK_SET
) != -1 ?
424 VLC_SUCCESS
: VLC_EGENERIC
;
427 /*****************************************************************************
428 * Change the chapter index to match the current position
429 *****************************************************************************/
430 static void FindSeekpoint( access_t
*p_access
)
432 access_sys_t
*p_sys
= p_access
->p_sys
;
433 if( !p_sys
->p_marks
)
436 int i_new_seekpoint
= p_access
->info
.i_seekpoint
;
437 if( p_access
->info
.i_pos
< (uint64_t)p_sys
->p_marks
->
438 seekpoint
[ p_access
->info
.i_seekpoint
]->i_byte_offset
)
440 /* i_pos moved backwards, start fresh */
444 /* only need to check the following seekpoints */
445 while( i_new_seekpoint
+ 1 < p_sys
->p_marks
->i_seekpoint
&&
446 p_access
->info
.i_pos
>= (uint64_t)p_sys
->p_marks
->
447 seekpoint
[ i_new_seekpoint
+ 1 ]->i_byte_offset
)
452 /* avoid unnecessary events */
453 if( p_access
->info
.i_seekpoint
!= i_new_seekpoint
)
455 p_access
->info
.i_seekpoint
= i_new_seekpoint
;
456 p_access
->info
.i_update
|= INPUT_UPDATE_SEEKPOINT
;
460 /*****************************************************************************
461 * Returns the path of a certain part
462 *****************************************************************************/
463 static char *GetFilePath( access_t
*p_access
, unsigned i_file
)
466 if( asprintf( &psz_path
, p_access
->p_sys
->b_ts_format
?
467 "%s" DIR_SEP
"%05u.ts" : "%s" DIR_SEP
"%03u.vdr",
468 p_access
->psz_filepath
, i_file
+ 1 ) == -1 )
474 /*****************************************************************************
475 * Check if another part exists and import it
476 *****************************************************************************/
477 static bool ImportNextFile( access_t
*p_access
)
479 access_sys_t
*p_sys
= p_access
->p_sys
;
481 char *psz_path
= GetFilePath( p_access
, FILE_COUNT
);
486 if( vlc_stat( psz_path
, &st
) )
488 msg_Dbg( p_access
, "could not stat %s: %m", psz_path
);
492 if( !S_ISREG( st
.st_mode
) )
494 msg_Dbg( p_access
, "%s is not a regular file", psz_path
);
498 msg_Dbg( p_access
, "%s exists", psz_path
);
501 ARRAY_APPEND( p_sys
->file_sizes
, st
.st_size
);
502 p_access
->info
.i_size
+= st
.st_size
;
503 p_access
->info
.i_update
|= INPUT_UPDATE_SIZE
;
508 /*****************************************************************************
509 * Close the current file and open another
510 *****************************************************************************/
511 static bool SwitchFile( access_t
*p_access
, unsigned i_file
)
513 access_sys_t
*p_sys
= p_access
->p_sys
;
515 /* requested file already open? */
516 if( p_sys
->fd
!= -1 && p_sys
->i_current_file
== i_file
)
520 if( p_sys
->fd
!= -1 )
527 if( i_file
>= FILE_COUNT
)
529 p_sys
->i_current_file
= i_file
;
532 char *psz_path
= GetFilePath( p_access
, i_file
);
535 p_sys
->fd
= vlc_open( psz_path
, O_RDONLY
);
537 if( p_sys
->fd
== -1 )
539 msg_Err( p_access
, "Failed to open %s: %m", psz_path
);
543 /* cannot handle anything except normal files */
545 if( fstat( p_sys
->fd
, &st
) || !S_ISREG( st
.st_mode
) )
547 msg_Err( p_access
, "%s is not a regular file", psz_path
);
551 OptimizeForRead( p_sys
->fd
);
553 msg_Dbg( p_access
, "opened %s", psz_path
);
558 dialog_Fatal (p_access
, _("File reading failed"), _("VLC could not"
559 " open the file \"%s\". (%m)"), psz_path
);
560 if( p_sys
->fd
!= -1 )
569 /*****************************************************************************
570 * Some tweaks to speed up read()
571 *****************************************************************************/
572 static void OptimizeForRead( int fd
)
574 /* cf. Open() in file access module */
576 #ifdef HAVE_POSIX_FADVISE
577 posix_fadvise( fd
, 0, 4096, POSIX_FADV_WILLNEED
);
578 posix_fadvise( fd
, 0, 0, POSIX_FADV_NOREUSE
);
582 fcntl( fd
, F_RDAHEAD
, 1 );
585 fcntl( fd
, F_NOCACHE
, 1 );
590 /*****************************************************************************
591 * Fix size if the (last) part is still growing
592 *****************************************************************************/
593 static void UpdateFileSize( access_t
*p_access
)
595 access_sys_t
*p_sys
= p_access
->p_sys
;
598 if( p_access
->info
.i_size
>= p_access
->info
.i_pos
)
601 /* TODO: not sure if this can happen or what to do in this case */
602 if( fstat( p_sys
->fd
, &st
) )
604 if( (uint64_t)st
.st_size
<= CURRENT_FILE_SIZE
)
607 p_access
->info
.i_size
-= CURRENT_FILE_SIZE
;
608 CURRENT_FILE_SIZE
= st
.st_size
;
609 p_access
->info
.i_size
+= CURRENT_FILE_SIZE
;
610 p_access
->info
.i_update
|= INPUT_UPDATE_SIZE
;
613 /*****************************************************************************
614 * Stat file relative to base directory
615 *****************************************************************************/
616 static int StatRelativeFile( access_t
*p_access
, const char *psz_file
,
617 struct stat
*p_stat
)
619 /* build path and add extension */
621 if( asprintf( &psz_path
, "%s" DIR_SEP
"%s%s",
622 p_access
->psz_filepath
, psz_file
,
623 p_access
->p_sys
->b_ts_format
? "" : ".vdr" ) == -1 )
626 int ret
= vlc_stat( psz_path
, p_stat
);
628 msg_Dbg( p_access
, "could not stat %s: %m", psz_path
);
634 /*****************************************************************************
635 * Open file relative to base directory for reading.
636 *****************************************************************************/
637 static FILE *OpenRelativeFile( access_t
*p_access
, const char *psz_file
)
639 /* build path and add extension */
641 if( asprintf( &psz_path
, "%s" DIR_SEP
"%s%s",
642 p_access
->psz_filepath
, psz_file
,
643 p_access
->p_sys
->b_ts_format
? "" : ".vdr" ) == -1 )
646 FILE *file
= vlc_fopen( psz_path
, "rb" );
648 msg_Warn( p_access
, "Failed to open %s: %m", psz_path
);
654 /*****************************************************************************
655 * Read a line of text. Returns false on error or EOF.
656 *****************************************************************************/
657 static bool ReadLine( char **ppsz_line
, size_t *pi_size
, FILE *p_file
)
659 ssize_t read
= getline( ppsz_line
, pi_size
, p_file
);
663 /* automatically free buffer on eof */
669 if( read
> 0 && (*ppsz_line
)[ read
- 1 ] == '\n' )
670 (*ppsz_line
)[ read
- 1 ] = '\0';
671 EnsureUTF8( *ppsz_line
);
676 /*****************************************************************************
678 *****************************************************************************/
679 static void ImportMeta( access_t
*p_access
)
681 access_sys_t
*p_sys
= p_access
->p_sys
;
683 FILE *infofile
= OpenRelativeFile( p_access
, "info" );
687 vlc_meta_t
*p_meta
= vlc_meta_New();
688 p_sys
->p_meta
= p_meta
;
697 char *psz_title
= NULL
, *psz_smalltext
= NULL
, *psz_date
= NULL
;
699 while( ReadLine( &line
, &line_len
, infofile
) )
701 if( !isalpha( (unsigned char)line
[0] ) || line
[1] != ' ' )
705 char *text
= line
+ 2;
709 char *psz_name
= strchr( text
, ' ' );
713 vlc_meta_AddExtra( p_meta
, "Channel", psz_name
+ 1 );
715 vlc_meta_AddExtra( p_meta
, "Transponder", text
);
718 else if( tag
== 'E' )
720 unsigned i_id
, i_start
, i_length
;
721 if( sscanf( text
, "%u %u %u", &i_id
, &i_start
, &i_length
) == 3 )
725 time_t start
= i_start
;
726 localtime_r( &start
, &tm
);
729 strftime( str
, sizeof(str
), "%Y-%m-%d %H:%M", &tm
);
730 vlc_meta_AddExtra( p_meta
, "Date", str
);
732 psz_date
= strdup( str
);
734 /* display in minutes */
735 i_length
= ( i_length
+ 59 ) / 60;
736 snprintf( str
, sizeof(str
), "%u:%02u", i_length
/ 60, i_length
% 60 );
737 vlc_meta_AddExtra( p_meta
, "Duration", str
);
741 else if( tag
== 'T' )
744 psz_title
= strdup( text
);
745 vlc_meta_AddExtra( p_meta
, "Title", text
);
748 else if( tag
== 'S' )
750 free( psz_smalltext
);
751 psz_smalltext
= strdup( text
);
752 vlc_meta_AddExtra( p_meta
, "Info", text
);
755 else if( tag
== 'D' )
757 for( char *p
= text
; *p
; ++p
)
762 vlc_meta_SetDescription( p_meta
, text
);
765 /* FPS are required to convert between timestamps and frames */
766 else if( tag
== 'F' )
768 float fps
= atof( text
);
771 vlc_meta_AddExtra( p_meta
, "Frame Rate", text
);
774 else if( tag
== 'P' )
776 vlc_meta_AddExtra( p_meta
, "Priority", text
);
779 else if( tag
== 'L' )
781 vlc_meta_AddExtra( p_meta
, "Lifetime", text
);
785 /* create a meaningful title */
787 ( psz_title
? strlen( psz_title
) : 0 ) +
788 ( psz_smalltext
? strlen( psz_smalltext
) : 0 ) +
789 ( psz_date
? strlen( psz_date
) : 0 );
790 char *psz_display
= malloc( i_len
);
796 strcat( psz_display
, psz_title
);
797 if( psz_title
&& psz_smalltext
)
798 strcat( psz_display
, " - " );
800 strcat( psz_display
, psz_smalltext
);
801 if( ( psz_title
|| psz_smalltext
) && psz_date
)
803 strcat( psz_display
, " (" );
804 strcat( psz_display
, psz_date
);
805 strcat( psz_display
, ")" );
808 vlc_meta_SetTitle( p_meta
, psz_display
);
813 free( psz_smalltext
);
819 /*****************************************************************************
820 * Import cut marks and convert them to seekpoints (chapters).
821 *****************************************************************************/
822 static void ImportMarks( access_t
*p_access
)
824 access_sys_t
*p_sys
= p_access
->p_sys
;
826 FILE *marksfile
= OpenRelativeFile( p_access
, "marks" );
830 FILE *indexfile
= OpenRelativeFile( p_access
, "index" );
837 /* get the length of this recording (index stores 8 bytes per frame) */
839 if( fstat( fileno( indexfile
), &st
) )
845 int64_t i_frame_count
= st
.st_size
/ 8;
847 /* Put all cut marks in a "dummy" title */
848 input_title_t
*p_marks
= vlc_input_title_New();
855 p_marks
->psz_name
= strdup( _("VDR Cut Marks") );
857 /* offset for chapter positions */
858 int i_chapter_offset
= p_sys
->fps
/ 1000 *
859 var_InheritInteger( p_access
, "vdr-chapter-offset" );
861 /* minimum chapter size in frames */
862 int i_min_chapter_size
= p_sys
->fps
* MIN_CHAPTER_SIZE
;
864 /* the last chapter started at this frame (init to 0 so
865 * we skip useless chapters near the beginning as well) */
866 int64_t i_prev_chapter
= 0;
868 /* parse lines of the form "0:00:00.00 foobar" */
871 while( ReadLine( &line
, &line_len
, marksfile
) )
873 int64_t i_frame
= ParseFrameNumber( line
, p_sys
->fps
);
875 /* skip chapters which are near the end or too close to each other */
876 if( i_frame
- i_prev_chapter
< i_min_chapter_size
||
877 i_frame
>= i_frame_count
- i_min_chapter_size
)
879 i_prev_chapter
= i_frame
;
881 /* move chapters (simple workaround for inaccurate cut marks) */
882 if( i_frame
> -i_chapter_offset
)
883 i_frame
+= i_chapter_offset
;
888 uint16_t i_file_number
;
889 if( !ReadIndexRecord( indexfile
, p_sys
->b_ts_format
,
890 i_frame
, &i_offset
, &i_file_number
) )
892 if( i_file_number
< 1 || i_file_number
> FILE_COUNT
)
895 /* add file sizes to get the "global" offset */
896 seekpoint_t
*sp
= vlc_seekpoint_New();
899 sp
->i_time_offset
= i_frame
* (int64_t)( CLOCK_FREQ
/ p_sys
->fps
);
900 sp
->i_byte_offset
= i_offset
;
901 for( int i
= 0; i
+ 1 < i_file_number
; ++i
)
902 sp
->i_byte_offset
+= FILE_SIZE( i
);
903 sp
->psz_name
= strdup( line
);
905 TAB_APPEND( p_marks
->i_seekpoint
, p_marks
->seekpoint
, sp
);
908 /* add a chapter at the beginning if missing */
909 if( p_marks
->i_seekpoint
> 0 && p_marks
->seekpoint
[0]->i_byte_offset
> 0 )
911 seekpoint_t
*sp
= vlc_seekpoint_New();
914 sp
->i_byte_offset
= 0;
915 sp
->i_time_offset
= 0;
916 sp
->psz_name
= strdup( _("Start") );
917 TAB_INSERT( p_marks
->i_seekpoint
, p_marks
->seekpoint
, sp
, 0 );
921 if( p_marks
->i_seekpoint
> 0 )
922 p_sys
->p_marks
= p_marks
;
924 vlc_input_title_Delete( p_marks
);
930 /*****************************************************************************
931 * Lookup frame offset in index file
932 *****************************************************************************/
933 static bool ReadIndexRecord( FILE *p_file
, bool b_ts
, int64_t i_frame
,
934 uint64_t *pi_offset
, uint16_t *pi_file_num
)
936 uint8_t index_record
[8];
937 if( fseek( p_file
, sizeof(index_record
) * i_frame
, SEEK_SET
) != 0 )
939 if( fread( &index_record
, sizeof(index_record
), 1, p_file
) <= 0 )
942 /* VDR usually (only?) runs on little endian machines, but VLC has a
943 * broader audience. See recording.* in VDR source for data layout. */
946 uint64_t i_index_entry
= GetQWLE( &index_record
);
947 *pi_offset
= i_index_entry
& UINT64_C(0xFFFFFFFFFF);
948 *pi_file_num
= i_index_entry
>> 48;
952 *pi_offset
= GetDWLE( &index_record
);
953 *pi_file_num
= index_record
[5];
959 /*****************************************************************************
960 * Convert time stamp from file to frame number
961 *****************************************************************************/
962 static int64_t ParseFrameNumber( const char *psz_line
, float fps
)
964 unsigned h
, m
, s
, f
, n
;
966 /* hour:min:sec.frame (frame is optional) */
967 n
= sscanf( psz_line
, "%u:%u:%u.%u", &h
, &m
, &s
, &f
);
972 int64_t i_seconds
= (int64_t)h
* 3600 + (int64_t)m
* 60 + s
;
973 return (int64_t)( i_seconds
* (double)fps
) + __MAX(1, f
) - 1;
976 /* only a frame number */
977 int64_t i_frame
= strtoll( psz_line
, NULL
, 10 );
978 return __MAX(1, i_frame
) - 1;