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
;
114 uint64_t size
; /* total size */
116 /* index and fd of current open file */
117 unsigned i_current_file
;
124 input_title_t
*p_marks
;
126 unsigned cur_seekpoint
;
129 /* file format: true=TS, false=PES */
133 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
134 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
135 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
137 static int Control( stream_t
*, int, va_list );
138 static ssize_t
Read( stream_t
*p_access
, void *p_buffer
, size_t i_len
);
139 static int Seek( stream_t
*p_access
, uint64_t i_pos
);
140 static void FindSeekpoint( stream_t
*p_access
);
141 static bool ScanDirectory( stream_t
*p_access
);
142 static char *GetFilePath( stream_t
*p_access
, unsigned i_file
);
143 static bool ImportNextFile( stream_t
*p_access
);
144 static bool SwitchFile( stream_t
*p_access
, unsigned i_file
);
145 static void OptimizeForRead( int fd
);
146 static void UpdateFileSize( stream_t
*p_access
);
147 static FILE *OpenRelativeFile( stream_t
*p_access
, const char *psz_file
);
148 static bool ReadLine( char **ppsz_line
, size_t *pi_size
, FILE *p_file
);
149 static void ImportMeta( stream_t
*p_access
);
150 static void ImportMarks( stream_t
*p_access
);
151 static bool ReadIndexRecord( FILE *p_file
, bool b_ts
, int64_t i_frame
,
152 uint64_t *pi_offset
, uint16_t *pi_file_num
);
153 static int64_t ParseFrameNumber( const char *psz_line
, float fps
);
154 static const char *BaseName( const char *psz_path
);
156 /*****************************************************************************
158 *****************************************************************************/
159 static int Open( vlc_object_t
*p_this
)
161 stream_t
*p_access
= (stream_t
*)p_this
;
163 if( !p_access
->psz_filepath
)
166 /* Some tests can be skipped if this module was explicitly requested.
167 * That way, the user can play "corrupt" recordings if necessary
168 * and we can avoid false positives in the general case. */
169 bool b_strict
= strcmp( p_access
->psz_name
, "vdr" );
171 /* Do a quick test based on the directory name to see if this
172 * directory might contain a VDR recording. We can be reasonably
173 * sure if ScanDirectory() actually finds files. */
176 char psz_extension
[4];
178 const char *psz_name
= BaseName( p_access
->psz_filepath
);
179 if( sscanf( psz_name
, "%*u-%*u-%*u.%*u.%*u.%*u%*[-.]%*u.%3s%n",
180 psz_extension
, &i_length
) != 1 || strcasecmp( psz_extension
, "rec" ) ||
181 ( psz_name
[i_length
] != DIR_SEP_CHAR
&& psz_name
[i_length
] != '\0' ) )
185 /* Only directories can be recordings */
187 if( vlc_stat( p_access
->psz_filepath
, &st
) ||
188 !S_ISDIR( st
.st_mode
) )
191 access_sys_t
*p_sys
= vlc_obj_calloc( p_this
, 1, sizeof( *p_sys
) );
193 if( unlikely(p_sys
== NULL
) )
196 p_access
->p_sys
= p_sys
;
198 p_sys
->cur_seekpoint
= 0;
199 p_sys
->fps
= var_InheritFloat( p_access
, "vdr-fps" );
200 ARRAY_INIT( p_sys
->file_sizes
);
202 /* Import all files and prepare playback. */
203 if( !ScanDirectory( p_access
) ||
204 !SwitchFile( p_access
, 0 ) )
210 ACCESS_SET_CALLBACKS( Read
, NULL
, Control
, Seek
);
214 /*****************************************************************************
215 * Close files and free resources
216 *****************************************************************************/
217 static void Close( vlc_object_t
* p_this
)
219 stream_t
*p_access
= (stream_t
*)p_this
;
220 access_sys_t
*p_sys
= p_access
->p_sys
;
222 if( p_sys
->fd
!= -1 )
223 vlc_close( p_sys
->fd
);
224 ARRAY_RESET( p_sys
->file_sizes
);
227 vlc_meta_Delete( p_sys
->p_meta
);
229 free( p_sys
->offsets
);
230 vlc_input_title_Delete( p_sys
->p_marks
);
233 /*****************************************************************************
234 * Determine format and import files
235 *****************************************************************************/
236 static bool ScanDirectory( stream_t
*p_access
)
238 access_sys_t
*p_sys
= p_access
->p_sys
;
240 /* find first part and determine directory format */
241 p_sys
->b_ts_format
= true;
242 if( !ImportNextFile( p_access
) )
244 p_sys
->b_ts_format
= !p_sys
->b_ts_format
;
245 if( !ImportNextFile( p_access
) )
249 /* get all remaining parts */
250 while( ImportNextFile( p_access
) )
253 /* import meta data etc. */
254 ImportMeta( p_access
);
256 /* cut marks depend on meta data and file sizes */
257 ImportMarks( p_access
);
262 /*****************************************************************************
263 * Control input stream
264 *****************************************************************************/
265 static int Control( stream_t
*p_access
, int i_query
, va_list args
)
267 access_sys_t
*p_sys
= p_access
->p_sys
;
268 input_title_t
***ppp_title
;
274 case STREAM_CAN_SEEK
:
275 case STREAM_CAN_FASTSEEK
:
276 case STREAM_CAN_PAUSE
:
277 case STREAM_CAN_CONTROL_PACE
:
278 *va_arg( args
, bool* ) = true;
281 case STREAM_GET_SIZE
:
282 *va_arg( args
, uint64_t* ) = p_sys
->size
;
285 case STREAM_GET_PTS_DELAY
:
286 *va_arg( args
, vlc_tick_t
* ) =
287 VLC_TICK_FROM_MS(var_InheritInteger( p_access
, "file-caching" ));
290 case STREAM_SET_PAUSE_STATE
:
294 case STREAM_GET_TITLE_INFO
:
295 /* return a copy of our seek points */
296 if( !p_sys
->p_marks
)
298 ppp_title
= va_arg( args
, input_title_t
*** );
299 *va_arg( args
, int* ) = 1;
300 *ppp_title
= malloc( sizeof( **ppp_title
) );
303 **ppp_title
= vlc_input_title_Duplicate( p_sys
->p_marks
);
306 case STREAM_GET_TITLE
:
307 *va_arg( args
, unsigned * ) = 0;
310 case STREAM_GET_SEEKPOINT
:
311 *va_arg( args
, unsigned * ) = p_sys
->cur_seekpoint
;
314 case STREAM_GET_CONTENT_TYPE
:
315 *va_arg( args
, char ** ) =
316 strdup( p_sys
->b_ts_format
? "video/MP2T" : "video/MP2P" );
319 case STREAM_SET_TITLE
:
320 /* ignore - only one title */
323 case STREAM_SET_SEEKPOINT
:
324 i
= va_arg( args
, int );
325 return Seek( p_access
, p_sys
->offsets
[i
] );
327 case STREAM_GET_META
:
328 p_meta
= va_arg( args
, vlc_meta_t
* );
329 vlc_meta_Merge( p_meta
, p_sys
->p_meta
);
338 /*****************************************************************************
339 * Read and concatenate files
340 *****************************************************************************/
341 static ssize_t
Read( stream_t
*p_access
, void *p_buffer
, size_t i_len
)
343 access_sys_t
*p_sys
= p_access
->p_sys
;
345 if( p_sys
->fd
== -1 )
349 ssize_t i_ret
= read( p_sys
->fd
, p_buffer
, i_len
);
354 p_sys
->offset
+= i_ret
;
355 UpdateFileSize( p_access
);
356 FindSeekpoint( p_access
);
359 else if( i_ret
== 0 )
361 /* check for new files in case the recording is still active */
362 if( p_sys
->i_current_file
>= FILE_COUNT
- 1 )
363 ImportNextFile( p_access
);
365 SwitchFile( p_access
, p_sys
->i_current_file
+ 1 );
368 else if( errno
== EINTR
)
370 /* try again later */
375 /* abort on read error */
376 msg_Err( p_access
, "failed to read (%s)", vlc_strerror_c(errno
) );
377 vlc_dialog_display_error( p_access
, _("File reading failed"),
378 _("VLC could not read the file (%s)."),
379 vlc_strerror(errno
) );
380 SwitchFile( p_access
, -1 );
385 /*****************************************************************************
386 * Seek to a specific location in a file
387 *****************************************************************************/
388 static int Seek( stream_t
*p_access
, uint64_t i_pos
)
390 access_sys_t
*p_sys
= p_access
->p_sys
;
392 /* might happen if called by STREAM_SET_SEEKPOINT */
393 i_pos
= __MIN( i_pos
, p_sys
->size
);
395 p_sys
->offset
= i_pos
;
397 /* find correct chapter */
398 FindSeekpoint( p_access
);
400 /* find correct file */
402 while( i_file
< FILE_COUNT
- 1 &&
403 i_pos
>= FILE_SIZE( i_file
) )
405 i_pos
-= FILE_SIZE( i_file
);
408 if( !SwitchFile( p_access
, i_file
) )
411 /* adjust position within that file */
412 return lseek( p_sys
->fd
, i_pos
, SEEK_SET
) != -1 ?
413 VLC_SUCCESS
: VLC_EGENERIC
;
416 /*****************************************************************************
417 * Change the chapter index to match the current position
418 *****************************************************************************/
419 static void FindSeekpoint( stream_t
*p_access
)
421 access_sys_t
*p_sys
= p_access
->p_sys
;
422 if( !p_sys
->p_marks
)
425 int new_seekpoint
= p_sys
->cur_seekpoint
;
426 if( p_sys
->offset
< p_sys
->offsets
[p_sys
->cur_seekpoint
] )
428 /* i_pos moved backwards, start fresh */
432 /* only need to check the following seekpoints */
433 while( new_seekpoint
+ 1 < p_sys
->p_marks
->i_seekpoint
&&
434 p_sys
->offset
>= p_sys
->offsets
[new_seekpoint
+ 1] )
439 p_sys
->cur_seekpoint
= new_seekpoint
;
442 /*****************************************************************************
443 * Returns the path of a certain part
444 *****************************************************************************/
445 static char *GetFilePath( stream_t
*p_access
, unsigned i_file
)
447 access_sys_t
*sys
= p_access
->p_sys
;
450 if( asprintf( &psz_path
, sys
->b_ts_format
?
451 "%s" DIR_SEP
"%05u.ts" : "%s" DIR_SEP
"%03u.vdr",
452 p_access
->psz_filepath
, i_file
+ 1 ) == -1 )
458 /*****************************************************************************
459 * Check if another part exists and import it
460 *****************************************************************************/
461 static bool ImportNextFile( stream_t
*p_access
)
463 access_sys_t
*p_sys
= p_access
->p_sys
;
465 char *psz_path
= GetFilePath( p_access
, FILE_COUNT
);
470 if( vlc_stat( psz_path
, &st
) )
472 msg_Dbg( p_access
, "could not stat %s: %s", psz_path
,
473 vlc_strerror_c(errno
) );
477 if( !S_ISREG( st
.st_mode
) )
479 msg_Dbg( p_access
, "%s is not a regular file", psz_path
);
483 msg_Dbg( p_access
, "%s exists", psz_path
);
486 ARRAY_APPEND( p_sys
->file_sizes
, st
.st_size
);
487 p_sys
->size
+= st
.st_size
;
492 /*****************************************************************************
493 * Close the current file and open another
494 *****************************************************************************/
495 static bool SwitchFile( stream_t
*p_access
, unsigned i_file
)
497 access_sys_t
*p_sys
= p_access
->p_sys
;
499 /* requested file already open? */
500 if( p_sys
->fd
!= -1 && p_sys
->i_current_file
== i_file
)
504 if( p_sys
->fd
!= -1 )
506 vlc_close( p_sys
->fd
);
511 if( i_file
>= FILE_COUNT
)
513 p_sys
->i_current_file
= i_file
;
516 char *psz_path
= GetFilePath( p_access
, i_file
);
519 p_sys
->fd
= vlc_open( psz_path
, O_RDONLY
);
521 if( p_sys
->fd
== -1 )
523 msg_Err( p_access
, "Failed to open %s: %s", psz_path
,
524 vlc_strerror_c(errno
) );
528 /* cannot handle anything except normal files */
530 if( fstat( p_sys
->fd
, &st
) || !S_ISREG( st
.st_mode
) )
532 msg_Err( p_access
, "%s is not a regular file", psz_path
);
536 OptimizeForRead( p_sys
->fd
);
538 msg_Dbg( p_access
, "opened %s", psz_path
);
543 vlc_dialog_display_error (p_access
, _("File reading failed"), _("VLC could not"
544 " open the file \"%s\" (%s)."), psz_path
, vlc_strerror(errno
) );
545 if( p_sys
->fd
!= -1 )
547 vlc_close( p_sys
->fd
);
554 /*****************************************************************************
555 * Some tweaks to speed up read()
556 *****************************************************************************/
557 static void OptimizeForRead( int fd
)
559 /* cf. Open() in file access module */
561 #ifdef HAVE_POSIX_FADVISE
562 posix_fadvise( fd
, 0, 4096, POSIX_FADV_WILLNEED
);
563 posix_fadvise( fd
, 0, 0, POSIX_FADV_NOREUSE
);
566 fcntl( fd
, F_RDAHEAD
, 1 );
569 fcntl( fd
, F_NOCACHE
, 0 );
573 /*****************************************************************************
574 * Fix size if the (last) part is still growing
575 *****************************************************************************/
576 static void UpdateFileSize( stream_t
*p_access
)
578 access_sys_t
*p_sys
= p_access
->p_sys
;
581 if( p_sys
->size
>= p_sys
->offset
)
584 /* TODO: not sure if this can happen or what to do in this case */
585 if( fstat( p_sys
->fd
, &st
) )
587 if( (uint64_t)st
.st_size
<= CURRENT_FILE_SIZE
)
590 p_sys
->size
-= CURRENT_FILE_SIZE
;
591 CURRENT_FILE_SIZE
= st
.st_size
;
592 p_sys
->size
+= CURRENT_FILE_SIZE
;
595 /*****************************************************************************
596 * Open file relative to base directory for reading.
597 *****************************************************************************/
598 static FILE *OpenRelativeFile( stream_t
*p_access
, const char *psz_file
)
600 access_sys_t
*sys
= p_access
->p_sys
;
602 /* build path and add extension */
604 if( asprintf( &psz_path
, "%s" DIR_SEP
"%s%s", p_access
->psz_filepath
,
605 psz_file
, sys
->b_ts_format
? "" : ".vdr" ) == -1 )
608 FILE *file
= vlc_fopen( psz_path
, "rb" );
610 msg_Warn( p_access
, "Failed to open %s: %s", psz_path
,
611 vlc_strerror_c(errno
) );
617 /*****************************************************************************
618 * Read a line of text. Returns false on error or EOF.
619 *****************************************************************************/
620 static bool ReadLine( char **ppsz_line
, size_t *pi_size
, FILE *p_file
)
622 ssize_t read
= getline( ppsz_line
, pi_size
, p_file
);
626 /* automatically free buffer on eof */
632 if( read
> 0 && (*ppsz_line
)[ read
- 1 ] == '\n' )
633 (*ppsz_line
)[ read
- 1 ] = '\0';
634 EnsureUTF8( *ppsz_line
);
639 /*****************************************************************************
641 *****************************************************************************/
642 static void ImportMeta( stream_t
*p_access
)
644 access_sys_t
*p_sys
= p_access
->p_sys
;
646 FILE *infofile
= OpenRelativeFile( p_access
, "info" );
650 vlc_meta_t
*p_meta
= vlc_meta_New();
651 p_sys
->p_meta
= p_meta
;
660 char *psz_title
= NULL
, *psz_smalltext
= NULL
, *psz_date
= NULL
;
662 while( ReadLine( &line
, &line_len
, infofile
) )
664 if( !isalpha( (unsigned char)line
[0] ) || line
[1] != ' ' )
668 char *text
= line
+ 2;
672 char *psz_name
= strchr( text
, ' ' );
676 vlc_meta_AddExtra( p_meta
, "Channel", psz_name
+ 1 );
678 vlc_meta_AddExtra( p_meta
, "Transponder", text
);
681 else if( tag
== 'E' )
683 unsigned i_id
, i_start
, i_length
;
684 if( sscanf( text
, "%u %u %u", &i_id
, &i_start
, &i_length
) == 3 )
688 time_t start
= i_start
;
689 localtime_r( &start
, &tm
);
692 strftime( str
, sizeof(str
), "%Y-%m-%d %H:%M", &tm
);
693 vlc_meta_AddExtra( p_meta
, "Date", str
);
695 psz_date
= strdup( str
);
697 /* display in minutes */
698 i_length
= ( i_length
+ 59 ) / 60;
699 snprintf( str
, sizeof(str
), "%u:%02u", i_length
/ 60, i_length
% 60 );
700 vlc_meta_AddExtra( p_meta
, "Duration", str
);
704 else if( tag
== 'T' )
707 psz_title
= strdup( text
);
708 vlc_meta_AddExtra( p_meta
, "Title", text
);
711 else if( tag
== 'S' )
713 free( psz_smalltext
);
714 psz_smalltext
= strdup( text
);
715 vlc_meta_AddExtra( p_meta
, "Info", text
);
718 else if( tag
== 'D' )
720 for( char *p
= text
; *p
; ++p
)
725 vlc_meta_SetDescription( p_meta
, text
);
728 /* FPS are required to convert between timestamps and frames */
729 else if( tag
== 'F' )
731 float fps
= atof( text
);
734 vlc_meta_AddExtra( p_meta
, "Frame Rate", text
);
737 else if( tag
== 'P' )
739 vlc_meta_AddExtra( p_meta
, "Priority", text
);
742 else if( tag
== 'L' )
744 vlc_meta_AddExtra( p_meta
, "Lifetime", text
);
748 /* create a meaningful title */
750 ( psz_title
? strlen( psz_title
) : 0 ) +
751 ( psz_smalltext
? strlen( psz_smalltext
) : 0 ) +
752 ( psz_date
? strlen( psz_date
) : 0 );
753 char *psz_display
= malloc( i_len
);
759 strcat( psz_display
, psz_title
);
760 if( psz_title
&& psz_smalltext
)
761 strcat( psz_display
, " - " );
763 strcat( psz_display
, psz_smalltext
);
764 if( ( psz_title
|| psz_smalltext
) && psz_date
)
766 strcat( psz_display
, " (" );
767 strcat( psz_display
, psz_date
);
768 strcat( psz_display
, ")" );
771 vlc_meta_SetTitle( p_meta
, psz_display
);
776 free( psz_smalltext
);
782 /*****************************************************************************
783 * Import cut marks and convert them to seekpoints (chapters).
784 *****************************************************************************/
785 static void ImportMarks( stream_t
*p_access
)
787 access_sys_t
*p_sys
= p_access
->p_sys
;
789 FILE *marksfile
= OpenRelativeFile( p_access
, "marks" );
793 FILE *indexfile
= OpenRelativeFile( p_access
, "index" );
800 /* get the length of this recording (index stores 8 bytes per frame) */
802 if( fstat( fileno( indexfile
), &st
) )
808 int64_t i_frame_count
= st
.st_size
/ 8;
810 /* Put all cut marks in a "dummy" title */
811 input_title_t
*p_marks
= vlc_input_title_New();
818 p_marks
->psz_name
= strdup( _("VDR Cut Marks") );
819 p_marks
->i_length
= i_frame_count
* (vlc_tick_t
)( CLOCK_FREQ
/ p_sys
->fps
);
821 uint64_t *offsetv
= NULL
;
823 /* offset for chapter positions */
824 int i_chapter_offset
= p_sys
->fps
/ 1000 *
825 var_InheritInteger( p_access
, "vdr-chapter-offset" );
827 /* minimum chapter size in frames */
828 int i_min_chapter_size
= p_sys
->fps
* MIN_CHAPTER_SIZE
;
830 /* the last chapter started at this frame (init to 0 so
831 * we skip useless chapters near the beginning as well) */
832 int64_t i_prev_chapter
= 0;
834 /* parse lines of the form "0:00:00.00 foobar" */
837 while( ReadLine( &line
, &line_len
, marksfile
) )
839 int64_t i_frame
= ParseFrameNumber( line
, p_sys
->fps
);
841 /* skip chapters which are near the end or too close to each other */
842 if( i_frame
- i_prev_chapter
< i_min_chapter_size
||
843 i_frame
>= i_frame_count
- i_min_chapter_size
)
845 i_prev_chapter
= i_frame
;
847 /* move chapters (simple workaround for inaccurate cut marks) */
848 if( i_frame
> -i_chapter_offset
)
849 i_frame
+= i_chapter_offset
;
854 uint16_t i_file_number
;
855 if( !ReadIndexRecord( indexfile
, p_sys
->b_ts_format
,
856 i_frame
, &i_offset
, &i_file_number
) )
858 if( i_file_number
< 1 || i_file_number
> FILE_COUNT
)
861 /* add file sizes to get the "global" offset */
862 seekpoint_t
*sp
= vlc_seekpoint_New();
865 sp
->i_time_offset
= i_frame
* (vlc_tick_t
)( CLOCK_FREQ
/ p_sys
->fps
);
866 sp
->psz_name
= strdup( line
);
868 TAB_APPEND( p_marks
->i_seekpoint
, p_marks
->seekpoint
, sp
);
869 offsetv
= xrealloc(offsetv
, p_marks
->i_seekpoint
* sizeof (*offsetv
));
871 for( int i
= 0; i
+ 1 < i_file_number
; ++i
)
872 i_offset
+= FILE_SIZE(i
);
874 offsetv
[p_marks
->i_seekpoint
- 1] = i_offset
;
877 /* add a chapter at the beginning if missing */
878 if( p_marks
->i_seekpoint
> 0 && offsetv
[0] > 0 )
880 seekpoint_t
*sp
= vlc_seekpoint_New();
883 sp
->i_time_offset
= 0;
884 sp
->psz_name
= strdup( _("Start") );
885 TAB_INSERT( p_marks
->i_seekpoint
, p_marks
->seekpoint
, sp
, 0 );
886 offsetv
= xrealloc(offsetv
,
887 p_marks
->i_seekpoint
* sizeof (*offsetv
));
888 memmove(offsetv
+ 1, offsetv
,
889 (p_marks
->i_seekpoint
- 1) * sizeof (*offsetv
));
894 if( p_marks
->i_seekpoint
> 0 )
896 p_sys
->p_marks
= p_marks
;
897 p_sys
->offsets
= offsetv
;
901 vlc_input_title_Delete( p_marks
);
909 /*****************************************************************************
910 * Lookup frame offset in index file
911 *****************************************************************************/
912 static bool ReadIndexRecord( FILE *p_file
, bool b_ts
, int64_t i_frame
,
913 uint64_t *pi_offset
, uint16_t *pi_file_num
)
915 uint8_t index_record
[8];
916 if( fseek( p_file
, sizeof(index_record
) * i_frame
, SEEK_SET
) != 0 )
918 if( fread( &index_record
, sizeof(index_record
), 1, p_file
) < 1 )
921 /* VDR usually (only?) runs on little endian machines, but VLC has a
922 * broader audience. See recording.* in VDR source for data layout. */
925 uint64_t i_index_entry
= GetQWLE( &index_record
);
926 *pi_offset
= i_index_entry
& UINT64_C(0xFFFFFFFFFF);
927 *pi_file_num
= i_index_entry
>> 48;
931 *pi_offset
= GetDWLE( &index_record
);
932 *pi_file_num
= index_record
[5];
938 /*****************************************************************************
939 * Convert time stamp from file to frame number
940 *****************************************************************************/
941 static int64_t ParseFrameNumber( const char *psz_line
, float fps
)
943 unsigned h
, m
, s
, f
, n
;
945 /* hour:min:sec.frame (frame is optional) */
946 n
= sscanf( psz_line
, "%u:%u:%u.%u", &h
, &m
, &s
, &f
);
951 int64_t i_seconds
= (int64_t)h
* 3600 + (int64_t)m
* 60 + s
;
952 return (int64_t)( i_seconds
* (double)fps
) + __MAX(1, f
) - 1;
955 /* only a frame number */
956 int64_t i_frame
= strtoll( psz_line
, NULL
, 10 );
957 return __MAX(1, i_frame
) - 1;
960 /*****************************************************************************
961 * Return the last path component (including trailing separators)
962 *****************************************************************************/
963 static const char *BaseName( const char *psz_path
)
965 const char *psz_name
= psz_path
+ strlen( psz_path
);
967 /* skip superfluous separators at the end */
968 while( psz_name
> psz_path
&& psz_name
[-1] == DIR_SEP_CHAR
)
971 /* skip last component */
972 while( psz_name
> psz_path
&& psz_name
[-1] != DIR_SEP_CHAR
)