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