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