]> git.sesse.net Git - vlc/blob - modules/access/vdr.c
vpx: fix leak
[vlc] / modules / access / vdr.c
1 /*****************************************************************************
2  * vdr.c: VDR recordings access plugin
3  *****************************************************************************
4  * Copyright (C) 2010 Tobias Güntner
5  *
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.
10  *
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.
15  *
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  *****************************************************************************/
20
21 /***
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.
27
28 VDR recordings have either of two directory layouts:
29     1) PES format:
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, ...
33     2) TS format:
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.
38 ***/
39
40 /*****************************************************************************
41  * Preamble
42  *****************************************************************************/
43
44 #ifdef HAVE_CONFIG_H
45 # include "config.h"
46 #endif
47
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <fcntl.h>
51 #include <unistd.h>
52
53 #include <ctype.h>
54 #include <time.h>
55 #include <errno.h>
56
57 #include <vlc_common.h>
58 #include <vlc_plugin.h>
59 #include <vlc_access.h>
60 #include <vlc_input.h>
61 #include <vlc_fs.h>
62 #include <vlc_charset.h>
63 #include <vlc_dialog.h>
64 #include <vlc_configuration.h>
65
66 /*****************************************************************************
67  * Module descriptor
68  *****************************************************************************/
69 static int  Open ( vlc_object_t * );
70 static void Close( vlc_object_t * );
71
72 #define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).")
73
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." )
77
78 #define FPS_TEXT N_("Frame rate")
79 #define FPS_LONGTEXT N_( \
80     "Default frame rate for chapter import." )
81
82 vlc_module_begin ()
83     set_category( CAT_INPUT )
84     set_shortname( N_("VDR") )
85     set_help( HELP_TEXT )
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 )
93     add_shortcut( "vdr" )
94     add_shortcut( "directory" )
95     add_shortcut( "dir" )
96     add_shortcut( "file" )
97     set_callbacks( Open, Close )
98 vlc_module_end ()
99
100 /*****************************************************************************
101  * Local prototypes, constants, structures
102  *****************************************************************************/
103
104 /* minimum chapter size in seconds */
105 #define MIN_CHAPTER_SIZE 5
106
107 TYPEDEF_ARRAY( uint64_t, size_array_t );
108
109 struct access_sys_t
110 {
111     /* file sizes of all parts */
112     size_array_t file_sizes;
113     uint64_t size; /* total size */
114
115     /* index and fd of current open file */
116     unsigned i_current_file;
117     int fd;
118
119     /* meta data */
120     vlc_meta_t *p_meta;
121
122     /* cut marks */
123     input_title_t *p_marks;
124     unsigned cur_seekpoint;
125     float fps;
126
127     /* file format: true=TS, false=PES */
128     bool b_ts_format;
129 };
130
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
134
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 );
153
154 /*****************************************************************************
155  * Open a directory
156  *****************************************************************************/
157 static int Open( vlc_object_t *p_this )
158 {
159     access_t *p_access = (access_t*)p_this;
160
161     if( !p_access->psz_filepath )
162         return VLC_EGENERIC;
163
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" );
168
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. */
172     if( b_strict )
173     {
174         char psz_extension[4];
175         int i_length = 0;
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' ) )
180             return VLC_EGENERIC;
181     }
182
183     /* Only directories can be recordings */
184     struct stat st;
185     if( vlc_stat( p_access->psz_filepath, &st ) ||
186         !S_ISDIR( st.st_mode ) )
187         return VLC_EGENERIC;
188
189     access_sys_t *p_sys;
190     STANDARD_READ_ACCESS_INIT;
191     p_sys->fd = -1;
192     p_sys->cur_seekpoint = 0;
193     p_sys->fps = var_InheritFloat( p_access, "vdr-fps" );
194     ARRAY_INIT( p_sys->file_sizes );
195
196     /* Import all files and prepare playback. */
197     if( !ScanDirectory( p_access ) ||
198         !SwitchFile( p_access, 0 ) )
199     {
200         Close( p_this );
201         return VLC_EGENERIC;
202     }
203
204     free( p_access->psz_demux );
205     p_access->psz_demux = strdup( p_sys->b_ts_format ? "ts" : "ps" );
206     return VLC_SUCCESS;
207 }
208
209 /*****************************************************************************
210  * Close files and free resources
211  *****************************************************************************/
212 static void Close( vlc_object_t * p_this )
213 {
214     access_t *p_access = (access_t*)p_this;
215     access_sys_t *p_sys = p_access->p_sys;
216
217     if( p_sys->fd != -1 )
218         close( p_sys->fd );
219     ARRAY_RESET( p_sys->file_sizes );
220
221     if( p_sys->p_meta )
222         vlc_meta_Delete( p_sys->p_meta );
223
224     vlc_input_title_Delete( p_sys->p_marks );
225     free( p_sys );
226 }
227
228 /*****************************************************************************
229  * Determine format and import files
230  *****************************************************************************/
231 static bool ScanDirectory( access_t *p_access )
232 {
233     access_sys_t *p_sys = p_access->p_sys;
234
235     /* find first part and determine directory format */
236     p_sys->b_ts_format = true;
237     if( !ImportNextFile( p_access ) )
238     {
239         p_sys->b_ts_format = !p_sys->b_ts_format;
240         if( !ImportNextFile( p_access ) )
241             return false;
242     }
243
244     /* get all remaining parts */
245     while( ImportNextFile( p_access ) )
246         continue;
247
248     /* import meta data etc. */
249     ImportMeta( p_access );
250
251     /* cut marks depend on meta data and file sizes */
252     ImportMarks( p_access );
253
254     return true;
255 }
256
257 /*****************************************************************************
258  * Control input stream
259  *****************************************************************************/
260 static int Control( access_t *p_access, int i_query, va_list args )
261 {
262     access_sys_t *p_sys = p_access->p_sys;
263     input_title_t ***ppp_title;
264     int i;
265     int64_t *pi64;
266     vlc_meta_t *p_meta;
267
268     switch( i_query )
269     {
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;
275             break;
276
277         case ACCESS_GET_SIZE:
278             *va_arg( args, uint64_t* ) = p_sys->size;
279             break;
280
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" );
285             break;
286
287         case ACCESS_SET_PAUSE_STATE:
288             /* nothing to do */
289             break;
290
291         case ACCESS_GET_TITLE_INFO:
292             /* return a copy of our seek points */
293             if( !p_sys->p_marks )
294                 return VLC_EGENERIC;
295             ppp_title = va_arg( args, input_title_t*** );
296             *va_arg( args, int* ) = 1;
297             *ppp_title = malloc( sizeof( **ppp_title ) );
298             if( !*ppp_title )
299                 return VLC_ENOMEM;
300             **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks );
301             break;
302
303         case ACCESS_GET_TITLE:
304             *va_arg( args, unsigned * ) = 0;
305             break;
306
307         case ACCESS_GET_SEEKPOINT:
308             *va_arg( args, unsigned * ) = p_sys->cur_seekpoint;
309             break;
310
311         case ACCESS_SET_TITLE:
312             /* ignore - only one title */
313             break;
314
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 );
318
319         case ACCESS_GET_META:
320             if( !p_sys->p_meta )
321                 return VLC_EGENERIC;
322             p_meta = va_arg( args, vlc_meta_t* );
323             vlc_meta_Merge( p_meta, p_sys->p_meta );
324             break;
325
326         default:
327             return VLC_EGENERIC;
328     }
329     return VLC_SUCCESS;
330 }
331
332 /*****************************************************************************
333  * Read and concatenate files
334  *****************************************************************************/
335 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
336 {
337     access_sys_t *p_sys = p_access->p_sys;
338
339     if( p_sys->fd == -1 )
340     {
341         /* no more data */
342         p_access->info.b_eof = true;
343         return 0;
344     }
345
346     ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
347
348     if( i_ret > 0 )
349     {
350         /* success */
351         p_access->info.i_pos += i_ret;
352         UpdateFileSize( p_access );
353         FindSeekpoint( p_access );
354         return i_ret;
355     }
356     else if( i_ret == 0 )
357     {
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 );
361         /* play next file */
362         SwitchFile( p_access, p_sys->i_current_file + 1 );
363         return -1;
364     }
365     else if( errno == EINTR )
366     {
367         /* try again later */
368         return -1;
369     }
370     else
371     {
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 );
378         return 0;
379     }
380 }
381
382 /*****************************************************************************
383  * Seek to a specific location in a file
384  *****************************************************************************/
385 static int Seek( access_t *p_access, uint64_t i_pos )
386 {
387     access_sys_t *p_sys = p_access->p_sys;
388
389     /* might happen if called by ACCESS_SET_SEEKPOINT */
390     i_pos = __MIN( i_pos, p_sys->size );
391
392     p_access->info.i_pos = i_pos;
393     p_access->info.b_eof = false;
394
395     /* find correct chapter */
396     FindSeekpoint( p_access );
397
398     /* find correct file */
399     unsigned i_file = 0;
400     while( i_file < FILE_COUNT - 1 &&
401            i_pos >= FILE_SIZE( i_file ) )
402     {
403         i_pos -= FILE_SIZE( i_file );
404         i_file++;
405     }
406     if( !SwitchFile( p_access, i_file ) )
407         return VLC_EGENERIC;
408
409     /* adjust position within that file */
410     return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ?
411         VLC_SUCCESS : VLC_EGENERIC;
412 }
413
414 /*****************************************************************************
415  * Change the chapter index to match the current position
416  *****************************************************************************/
417 static void FindSeekpoint( access_t *p_access )
418 {
419     access_sys_t *p_sys = p_access->p_sys;
420     if( !p_sys->p_marks )
421         return;
422
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 )
426     {
427         /* i_pos moved backwards, start fresh */
428         new_seekpoint = 0;
429     }
430
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 )
435     {
436         new_seekpoint++;
437     }
438
439     p_sys->cur_seekpoint = new_seekpoint;
440 }
441
442 /*****************************************************************************
443  * Returns the path of a certain part
444  *****************************************************************************/
445 static char *GetFilePath( access_t *p_access, unsigned i_file )
446 {
447     char *psz_path;
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 )
451         return NULL;
452     else
453         return psz_path;
454 }
455
456 /*****************************************************************************
457  * Check if another part exists and import it
458  *****************************************************************************/
459 static bool ImportNextFile( access_t *p_access )
460 {
461     access_sys_t *p_sys = p_access->p_sys;
462
463     char *psz_path = GetFilePath( p_access, FILE_COUNT );
464     if( !psz_path )
465         return false;
466
467     struct stat st;
468     if( vlc_stat( psz_path, &st ) )
469     {
470         msg_Dbg( p_access, "could not stat %s: %s", psz_path,
471                  vlc_strerror_c(errno) );
472         free( psz_path );
473         return false;
474     }
475     if( !S_ISREG( st.st_mode ) )
476     {
477         msg_Dbg( p_access, "%s is not a regular file", psz_path );
478         free( psz_path );
479         return false;
480     }
481     msg_Dbg( p_access, "%s exists", psz_path );
482     free( psz_path );
483
484     ARRAY_APPEND( p_sys->file_sizes, st.st_size );
485     p_sys->size += st.st_size;
486
487     return true;
488 }
489
490 /*****************************************************************************
491  * Close the current file and open another
492  *****************************************************************************/
493 static bool SwitchFile( access_t *p_access, unsigned i_file )
494 {
495     access_sys_t *p_sys = p_access->p_sys;
496
497     /* requested file already open? */
498     if( p_sys->fd != -1 && p_sys->i_current_file == i_file )
499         return true;
500
501     /* close old file */
502     if( p_sys->fd != -1 )
503     {
504         close( p_sys->fd );
505         p_sys->fd = -1;
506     }
507
508     /* switch */
509     if( i_file >= FILE_COUNT )
510         return false;
511     p_sys->i_current_file = i_file;
512
513     /* open new file */
514     char *psz_path = GetFilePath( p_access, i_file );
515     if( !psz_path )
516         return false;
517     p_sys->fd = vlc_open( psz_path, O_RDONLY );
518
519     if( p_sys->fd == -1 )
520     {
521         msg_Err( p_access, "Failed to open %s: %s", psz_path,
522                  vlc_strerror_c(errno) );
523         goto error;
524     }
525
526     /* cannot handle anything except normal files */
527     struct stat st;
528     if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) )
529     {
530         msg_Err( p_access, "%s is not a regular file", psz_path );
531         goto error;
532     }
533
534     OptimizeForRead( p_sys->fd );
535
536     msg_Dbg( p_access, "opened %s", psz_path );
537     free( psz_path );
538     return true;
539
540 error:
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 )
544     {
545         close( p_sys->fd );
546         p_sys->fd = -1;
547     }
548     free( psz_path );
549     return false;
550 }
551
552 /*****************************************************************************
553  * Some tweaks to speed up read()
554  *****************************************************************************/
555 static void OptimizeForRead( int fd )
556 {
557     /* cf. Open() in file access module */
558     VLC_UNUSED(fd);
559 #ifdef HAVE_POSIX_FADVISE
560     posix_fadvise( fd, 0, 4096, POSIX_FADV_WILLNEED );
561     posix_fadvise( fd, 0, 0, POSIX_FADV_NOREUSE );
562 #endif
563 #ifdef F_RDAHEAD
564     fcntl( fd, F_RDAHEAD, 1 );
565 #endif
566 #ifdef F_NOCACHE
567     fcntl( fd, F_NOCACHE, 0 );
568 #endif
569 }
570
571 /*****************************************************************************
572  * Fix size if the (last) part is still growing
573  *****************************************************************************/
574 static void UpdateFileSize( access_t *p_access )
575 {
576     access_sys_t *p_sys = p_access->p_sys;
577     struct stat st;
578
579     if( p_sys->size >= p_access->info.i_pos )
580         return;
581
582     /* TODO: not sure if this can happen or what to do in this case */
583     if( fstat( p_sys->fd, &st ) )
584         return;
585     if( (uint64_t)st.st_size <= CURRENT_FILE_SIZE )
586         return;
587
588     p_sys->size -= CURRENT_FILE_SIZE;
589     CURRENT_FILE_SIZE = st.st_size;
590     p_sys->size += CURRENT_FILE_SIZE;
591 }
592
593 /*****************************************************************************
594  * Open file relative to base directory for reading.
595  *****************************************************************************/
596 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file )
597 {
598     /* build path and add extension */
599     char *psz_path;
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 )
603         return NULL;
604
605     FILE *file = vlc_fopen( psz_path, "rb" );
606     if( !file )
607         msg_Warn( p_access, "Failed to open %s: %s", psz_path,
608                   vlc_strerror_c(errno) );
609     free( psz_path );
610
611     return file;
612 }
613
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 )
618 {
619     ssize_t read = getline( ppsz_line, pi_size, p_file );
620
621     if( read == -1 )
622     {
623         /* automatically free buffer on eof */
624         free( *ppsz_line );
625         *ppsz_line = NULL;
626         return false;
627     }
628
629     if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' )
630         (*ppsz_line)[ read - 1 ] = '\0';
631     EnsureUTF8( *ppsz_line );
632
633     return true;
634 }
635
636 /*****************************************************************************
637  * Import meta data
638  *****************************************************************************/
639 static void ImportMeta( access_t *p_access )
640 {
641     access_sys_t *p_sys = p_access->p_sys;
642
643     FILE *infofile = OpenRelativeFile( p_access, "info" );
644     if( !infofile )
645         return;
646
647     vlc_meta_t *p_meta = vlc_meta_New();
648     p_sys->p_meta = p_meta;
649     if( !p_meta )
650     {
651         fclose( infofile );
652         return;
653     }
654
655     char *line = NULL;
656     size_t line_len;
657     char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL;
658
659     while( ReadLine( &line, &line_len, infofile ) )
660     {
661         if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' )
662             continue;
663
664         char tag = line[0];
665         char *text = line + 2;
666
667         if( tag == 'C' )
668         {
669             char *psz_name = strchr( text, ' ' );
670             if( psz_name )
671             {
672                 *psz_name = '\0';
673                 vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
674             }
675             vlc_meta_AddExtra( p_meta, "Transponder", text );
676         }
677
678         else if( tag == 'E' )
679         {
680             unsigned i_id, i_start, i_length;
681             if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 )
682             {
683                 char str[50];
684                 struct tm tm;
685                 time_t start = i_start;
686                 localtime_r( &start, &tm );
687
688                 /* TODO: locale */
689                 strftime( str, sizeof(str), "%Y-%m-%d %H:%M", &tm );
690                 vlc_meta_AddExtra( p_meta, "Date", str );
691                 free( psz_date );
692                 psz_date = strdup( str );
693
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 );
698             }
699         }
700
701         else if( tag == 'T' )
702         {
703             free( psz_title );
704             psz_title = strdup( text );
705             vlc_meta_AddExtra( p_meta, "Title", text );
706         }
707
708         else if( tag == 'S' )
709         {
710             free( psz_smalltext );
711             psz_smalltext = strdup( text );
712             vlc_meta_AddExtra( p_meta, "Info", text );
713         }
714
715         else if( tag == 'D' )
716         {
717             for( char *p = text; *p; ++p )
718             {
719                 if( *p == '|' )
720                     *p = '\n';
721             }
722             vlc_meta_SetDescription( p_meta, text );
723         }
724
725         /* FPS are required to convert between timestamps and frames */
726         else if( tag == 'F' )
727         {
728             float fps = atof( text );
729             if( fps >= 1 )
730                 p_sys->fps = fps;
731             vlc_meta_AddExtra( p_meta, "Frame Rate", text );
732         }
733
734         else if( tag == 'P' )
735         {
736             vlc_meta_AddExtra( p_meta, "Priority", text );
737         }
738
739         else if( tag == 'L' )
740         {
741             vlc_meta_AddExtra( p_meta, "Lifetime", text );
742         }
743     }
744
745     /* create a meaningful title */
746     int i_len = 10 +
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 );
751
752     if( psz_display )
753     {
754         *psz_display = '\0';
755         if( psz_title )
756             strcat( psz_display, psz_title );
757         if( psz_title && psz_smalltext )
758             strcat( psz_display, " - " );
759         if( psz_smalltext )
760             strcat( psz_display, psz_smalltext );
761         if( ( psz_title || psz_smalltext ) && psz_date )
762         {
763             strcat( psz_display, " (" );
764             strcat( psz_display, psz_date );
765             strcat( psz_display, ")" );
766         }
767         if( *psz_display )
768             vlc_meta_SetTitle( p_meta, psz_display );
769     }
770
771     free( psz_display );
772     free( psz_title );
773     free( psz_smalltext );
774     free( psz_date );
775
776     fclose( infofile );
777 }
778
779 /*****************************************************************************
780  * Import cut marks and convert them to seekpoints (chapters).
781  *****************************************************************************/
782 static void ImportMarks( access_t *p_access )
783 {
784     access_sys_t *p_sys = p_access->p_sys;
785
786     FILE *marksfile = OpenRelativeFile( p_access, "marks" );
787     if( !marksfile )
788         return;
789
790     FILE *indexfile = OpenRelativeFile( p_access, "index" );
791     if( !indexfile )
792     {
793         fclose( marksfile );
794         return;
795     }
796
797     /* get the length of this recording (index stores 8 bytes per frame) */
798     struct stat st;
799     if( fstat( fileno( indexfile ), &st ) )
800     {
801         fclose( marksfile );
802         fclose( indexfile );
803         return;
804     }
805     int64_t i_frame_count = st.st_size / 8;
806
807     /* Put all cut marks in a "dummy" title */
808     input_title_t *p_marks = vlc_input_title_New();
809     if( !p_marks )
810     {
811         fclose( marksfile );
812         fclose( indexfile );
813         return;
814     }
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;
818
819     /* offset for chapter positions */
820     int i_chapter_offset = p_sys->fps / 1000 *
821         var_InheritInteger( p_access, "vdr-chapter-offset" );
822
823     /* minimum chapter size in frames */
824     int i_min_chapter_size = p_sys->fps * MIN_CHAPTER_SIZE;
825
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;
829
830     /* parse lines of the form "0:00:00.00 foobar" */
831     char *line = NULL;
832     size_t line_len;
833     while( ReadLine( &line, &line_len, marksfile ) )
834     {
835         int64_t i_frame = ParseFrameNumber( line, p_sys->fps );
836
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 )
840             continue;
841         i_prev_chapter = i_frame;
842
843         /* move chapters (simple workaround for inaccurate cut marks) */
844         if( i_frame > -i_chapter_offset )
845             i_frame += i_chapter_offset;
846         else
847             i_frame = 0;
848
849         uint64_t i_offset;
850         uint16_t i_file_number;
851         if( !ReadIndexRecord( indexfile, p_sys->b_ts_format,
852             i_frame, &i_offset, &i_file_number ) )
853             continue;
854         if( i_file_number < 1 || i_file_number > FILE_COUNT )
855             continue;
856
857         /* add file sizes to get the "global" offset */
858         seekpoint_t *sp = vlc_seekpoint_New();
859         if( !sp )
860             continue;
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 );
866
867         TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
868     }
869
870     /* add a chapter at the beginning if missing */
871     if( p_marks->i_seekpoint > 0 && p_marks->seekpoint[0]->i_byte_offset > 0 )
872     {
873         seekpoint_t *sp = vlc_seekpoint_New();
874         if( sp )
875         {
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 );
880         }
881     }
882
883     if( p_marks->i_seekpoint > 0 )
884         p_sys->p_marks = p_marks;
885     else
886         vlc_input_title_Delete( p_marks );
887
888     fclose( marksfile );
889     fclose( indexfile );
890 }
891
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 )
897 {
898     uint8_t index_record[8];
899     if( fseek( p_file, sizeof(index_record) * i_frame, SEEK_SET ) != 0 )
900         return false;
901     if( fread( &index_record, sizeof(index_record), 1, p_file ) < 1 )
902         return false;
903
904     /* VDR usually (only?) runs on little endian machines, but VLC has a
905      * broader audience. See recording.* in VDR source for data layout. */
906     if( b_ts )
907     {
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;
911     }
912     else
913     {
914         *pi_offset = GetDWLE( &index_record );
915         *pi_file_num = index_record[5];
916     }
917
918     return true;
919 }
920
921 /*****************************************************************************
922  * Convert time stamp from file to frame number
923  *****************************************************************************/
924 static int64_t ParseFrameNumber( const char *psz_line, float fps )
925 {
926     unsigned h, m, s, f, n;
927
928     /* hour:min:sec.frame (frame is optional) */
929     n = sscanf( psz_line, "%u:%u:%u.%u", &h, &m, &s, &f );
930     if( n >= 3 )
931     {
932         if( n < 4 )
933             f = 1;
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;
936     }
937
938     /* only a frame number */
939     int64_t i_frame = strtoll( psz_line, NULL, 10 );
940     return __MAX(1, i_frame) - 1;
941 }
942
943 /*****************************************************************************
944  * Return the last path component (including trailing separators)
945  *****************************************************************************/
946 static const char *BaseName( const char *psz_path )
947 {
948     const char *psz_name = psz_path + strlen( psz_path );
949
950     /* skip superfluous separators at the end */
951     while( psz_name > psz_path && psz_name[-1] == DIR_SEP_CHAR )
952         --psz_name;
953
954     /* skip last component */
955     while( psz_name > psz_path && psz_name[-1] != DIR_SEP_CHAR )
956         --psz_name;
957
958     return psz_name;
959 }