add_savefile: remove callback parameter
[vlc/asuraparaju-public.git] / modules / access / vdr.c
blobff8ca79e8046cadb7dbb80ba3f32fcb3e6c71a61
1 /*****************************************************************************
2 * vdr.c: VDR recordings access plugin
3 *****************************************************************************
4 * Copyright (C) 2010 Tobias Güntner
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
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.
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 ***/
40 /*****************************************************************************
41 * Preamble
42 *****************************************************************************/
44 #ifdef HAVE_CONFIG_H
45 # include "config.h"
46 #endif
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
63 #include <ctype.h>
64 #include <time.h>
65 #include <errno.h>
67 #if defined( WIN32 ) && !defined( UNDER_CE )
68 # undef lseek
69 # define lseek _lseeki64
70 #endif
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>
81 /*****************************************************************************
82 * Module descriptor
83 *****************************************************************************/
84 static int Open ( vlc_object_t * );
85 static void Close( vlc_object_t * );
87 #define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).")
89 #define CACHING_TEXT N_("Caching value in ms")
90 #define CACHING_LONGTEXT N_( \
91 "Caching value for files. This value should be set in milliseconds." )
93 #define CHAPTER_OFFSET_TEXT N_("Chapter offset in ms")
94 #define CHAPTER_OFFSET_LONGTEXT N_( \
95 "Move all chapters. This value should be set in milliseconds." )
97 #define FPS_TEXT N_("Frame rate")
98 #define FPS_LONGTEXT N_( \
99 "Default frame rate for chapter import." )
101 vlc_module_begin ()
102 set_category( CAT_INPUT )
103 set_shortname( N_("VDR") )
104 set_help( HELP_TEXT )
105 set_subcategory( SUBCAT_INPUT_ACCESS )
106 set_description( N_("VDR recordings") )
107 add_integer( "vdr-caching", 5 * DEFAULT_PTS_DELAY / 1000, NULL,
108 CACHING_TEXT, CACHING_LONGTEXT, true )
109 add_integer( "vdr-chapter-offset", 0, NULL,
110 CHAPTER_OFFSET_TEXT, CHAPTER_OFFSET_LONGTEXT, true )
111 add_float_with_range( "vdr-fps", 25, 1, 1000, NULL,
112 FPS_TEXT, FPS_LONGTEXT, true )
113 set_capability( "access", 60 )
114 add_shortcut( "vdr" )
115 add_shortcut( "directory" )
116 add_shortcut( "dir" )
117 add_shortcut( "file" )
118 set_callbacks( Open, Close )
119 vlc_module_end ()
121 /*****************************************************************************
122 * Local prototypes, constants, structures
123 *****************************************************************************/
125 TYPEDEF_ARRAY( uint64_t, size_array_t );
127 struct access_sys_t
129 /* file sizes of all parts */
130 size_array_t file_sizes;
132 /* index and fd of current open file */
133 unsigned i_current_file;
134 int fd;
136 /* meta data */
137 vlc_meta_t *p_meta;
139 /* cut marks */
140 input_title_t *p_marks;
141 float fps;
143 /* file format: true=TS, false=PES */
144 bool b_ts_format;
147 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
148 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
149 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
151 static int Control( access_t *, int, va_list );
152 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len );
153 static int Seek( access_t *p_access, uint64_t i_pos);
154 static void FindSeekpoint( access_t *p_access );
155 static bool ScanDirectory( access_t *p_access, bool b_strict );
156 static char *GetFilePath( access_t *p_access, unsigned i_file );
157 static bool ImportNextFile( access_t *p_access );
158 static bool SwitchFile( access_t *p_access, unsigned i_file );
159 static void OptimizeForRead( int fd );
160 static void UpdateFileSize( access_t *p_access );
161 static int StatRelativeFile( access_t *p_access, const char *psz_file,
162 struct stat *p_stat );
163 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file );
164 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file );
165 static void ImportMeta( access_t *p_access );
166 static void ImportMarks( access_t *p_access );
167 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
168 uint64_t *pi_offset, uint16_t *pi_file_num );
169 static int64_t ParseFrameNumber( const char *psz_line, float fps );
171 /*****************************************************************************
172 * Open a directory
173 *****************************************************************************/
174 static int Open( vlc_object_t *p_this )
176 access_t *p_access = (access_t*)p_this;
178 if( !p_access->psz_filepath )
179 return VLC_EGENERIC;
181 /* Some tests can be skipped if this module was explicitly requested.
182 * That way, the user can play "corrupt" recordings if necessary
183 * and we can avoid false positives in the general case. */
184 bool b_strict = strcmp( p_access->psz_access, "vdr" );
186 /* Do a quick test based on the directory extension to see if this
187 * directory might contain a VDR recording. We can be reasonably
188 * sure if ScanDirectory() actually finds files. */
189 if( b_strict )
191 const char *psz_ext = strrchr( p_access->psz_filepath, '.' );
192 if( !psz_ext || strcasecmp( psz_ext, ".rec" ) )
193 return VLC_EGENERIC;
196 /* Only directories can be recordings */
197 struct stat st;
198 if( vlc_stat( p_access->psz_filepath, &st ) ||
199 !S_ISDIR( st.st_mode ) )
200 return VLC_EGENERIC;
202 access_sys_t *p_sys;
203 STANDARD_READ_ACCESS_INIT;
204 p_sys->fd = -1;
205 p_sys->fps = var_InheritFloat( p_access, "vdr-fps" );
206 ARRAY_INIT( p_sys->file_sizes );
208 /* Import all files and prepare playback. */
209 if( !ScanDirectory( p_access, b_strict ) ||
210 !SwitchFile( p_access, 0 ) )
212 Close( p_this );
213 return VLC_EGENERIC;
216 return VLC_SUCCESS;
219 /*****************************************************************************
220 * Close files and free resources
221 *****************************************************************************/
222 static void Close( vlc_object_t * p_this )
224 access_t *p_access = (access_t*)p_this;
225 access_sys_t *p_sys = p_access->p_sys;
227 if( p_sys->fd != -1 )
228 close( p_sys->fd );
229 ARRAY_RESET( p_sys->file_sizes );
231 if( p_sys->p_meta )
232 vlc_meta_Delete( p_sys->p_meta );
234 vlc_input_title_Delete( p_sys->p_marks );
235 free( p_sys );
238 /*****************************************************************************
239 * Determine format and import files
240 *****************************************************************************/
241 static bool ScanDirectory( access_t *p_access, bool b_strict )
243 access_sys_t *p_sys = p_access->p_sys;
245 /* find first part and determine directory format */
246 p_sys->b_ts_format = true;
247 if( !ImportNextFile( p_access ) )
249 p_sys->b_ts_format = !p_sys->b_ts_format;
250 if( !ImportNextFile( p_access ) )
251 return false;
254 /* meta data and index should exist */
255 if( b_strict )
257 struct stat st;
258 if( StatRelativeFile( p_access, "info", &st ) ||
259 StatRelativeFile( p_access, "index", &st ) )
260 return false;
263 /* get all remaining parts */
264 while( ImportNextFile( p_access ) )
265 continue;
267 /* import meta data etc. */
268 ImportMeta( p_access );
270 /* cut marks depend on meta data and file sizes */
271 ImportMarks( p_access );
273 return true;
276 /*****************************************************************************
277 * Control input stream
278 *****************************************************************************/
279 static int Control( access_t *p_access, int i_query, va_list args )
281 access_sys_t *p_sys = p_access->p_sys;
282 input_title_t ***ppp_title;
283 int i;
284 int64_t *pi64;
285 vlc_meta_t *p_meta;
287 switch( i_query )
289 case ACCESS_CAN_SEEK:
290 case ACCESS_CAN_PAUSE:
291 case ACCESS_CAN_CONTROL_PACE:
292 *va_arg( args, bool* ) = true;
293 break;
295 case ACCESS_CAN_FASTSEEK:
296 /* Seek() can open files, so it might be "too slow" */
297 *va_arg( args, bool* ) = false;
298 break;
300 case ACCESS_GET_PTS_DELAY:
301 pi64 = va_arg( args, int64_t * );
302 *pi64 = var_InheritInteger( p_access, "vdr-caching" ) * INT64_C(1000);
303 break;
305 case ACCESS_SET_PAUSE_STATE:
306 /* nothing to do */
307 break;
309 case ACCESS_GET_TITLE_INFO:
310 /* return a copy of our seek points */
311 if( !p_sys->p_marks )
312 return VLC_EGENERIC;
313 ppp_title = va_arg( args, input_title_t*** );
314 *va_arg( args, int* ) = 1;
315 *ppp_title = malloc( sizeof( input_title_t** ) );
316 if( !*ppp_title )
317 return VLC_ENOMEM;
318 **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks );
319 break;
321 case ACCESS_SET_TITLE:
322 /* ignore - only one title */
323 break;
325 case ACCESS_SET_SEEKPOINT:
326 i = va_arg( args, int );
327 /* Seek updates p_access->info */
328 return Seek( p_access, p_sys->p_marks->seekpoint[i]->i_byte_offset );
330 case ACCESS_GET_META:
331 if( !p_sys->p_meta )
332 return VLC_EGENERIC;
333 p_meta = va_arg( args, vlc_meta_t* );
334 vlc_meta_Merge( p_meta, p_sys->p_meta );
335 break;
337 case ACCESS_SET_PRIVATE_ID_STATE:
338 case ACCESS_GET_CONTENT_TYPE:
339 return VLC_EGENERIC;
341 default:
342 msg_Warn( p_access, "unimplemented query in control" );
343 return VLC_EGENERIC;
345 return VLC_SUCCESS;
348 /*****************************************************************************
349 * Read and concatenate files
350 *****************************************************************************/
351 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
353 access_sys_t *p_sys = p_access->p_sys;
355 if( p_sys->fd == -1 )
357 /* no more data */
358 p_access->info.b_eof = true;
359 return 0;
362 ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
364 if( i_ret > 0 )
366 /* success */
367 p_access->info.i_pos += i_ret;
368 UpdateFileSize( p_access );
369 FindSeekpoint( p_access );
370 return i_ret;
372 else if( i_ret == 0 )
374 /* check for new files in case the recording is still active */
375 if( p_sys->i_current_file >= FILE_COUNT - 1 )
376 ImportNextFile( p_access );
377 /* play next file */
378 SwitchFile( p_access, p_sys->i_current_file + 1 );
379 return -1;
381 else if( errno == EINTR )
383 /* try again later */
384 return -1;
386 else
388 /* abort on read error */
389 msg_Err( p_access, "failed to read (%m)" );
390 dialog_Fatal( p_access, _("File reading failed"), "%s",
391 _("VLC could not read the file.") );
392 SwitchFile( p_access, -1 );
393 return 0;
397 /*****************************************************************************
398 * Seek to a specific location in a file
399 *****************************************************************************/
400 static int Seek( access_t *p_access, uint64_t i_pos )
402 access_sys_t *p_sys = p_access->p_sys;
404 /* might happen if called by ACCESS_SET_SEEKPOINT */
405 i_pos = __MIN( i_pos, p_access->info.i_size );
407 p_access->info.i_pos = i_pos;
408 p_access->info.b_eof = false;
410 /* find correct chapter */
411 FindSeekpoint( p_access );
413 /* find correct file */
414 unsigned i_file = 0;
415 while( i_pos >= FILE_SIZE( i_file ) &&
416 i_file < FILE_COUNT - 1 )
418 i_pos -= FILE_SIZE( i_file );
419 i_file++;
421 if( !SwitchFile( p_access, i_file ) )
422 return VLC_EGENERIC;
424 /* adjust position within that file */
425 return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ?
426 VLC_SUCCESS : VLC_EGENERIC;
429 /*****************************************************************************
430 * Change the chapter index to match the current position
431 *****************************************************************************/
432 static void FindSeekpoint( access_t *p_access )
434 access_sys_t *p_sys = p_access->p_sys;
435 if( !p_sys->p_marks )
436 return;
438 int i_new_seekpoint = p_access->info.i_seekpoint;
439 if( p_access->info.i_pos < (uint64_t)p_sys->p_marks->
440 seekpoint[ p_access->info.i_seekpoint ]->i_byte_offset )
442 /* i_pos moved backwards, start fresh */
443 i_new_seekpoint = 0;
446 /* only need to check the following seekpoints */
447 while( i_new_seekpoint + 1 < p_sys->p_marks->i_seekpoint &&
448 p_access->info.i_pos >= (uint64_t)p_sys->p_marks->
449 seekpoint[ i_new_seekpoint + 1 ]->i_byte_offset )
451 i_new_seekpoint++;
454 /* avoid unnecessary events */
455 if( p_access->info.i_seekpoint != i_new_seekpoint )
457 p_access->info.i_seekpoint = i_new_seekpoint;
458 p_access->info.i_update |= INPUT_UPDATE_SEEKPOINT;
462 /*****************************************************************************
463 * Returns the path of a certain part
464 *****************************************************************************/
465 static char *GetFilePath( access_t *p_access, unsigned i_file )
467 char *psz_path;
468 if( asprintf( &psz_path, p_access->p_sys->b_ts_format ?
469 "%s" DIR_SEP "%05u.ts" : "%s" DIR_SEP "%03u.vdr",
470 p_access->psz_filepath, i_file + 1 ) == -1 )
471 return NULL;
472 else
473 return psz_path;
476 /*****************************************************************************
477 * Check if another part exists and import it
478 *****************************************************************************/
479 static bool ImportNextFile( access_t *p_access )
481 access_sys_t *p_sys = p_access->p_sys;
483 char *psz_path = GetFilePath( p_access, FILE_COUNT );
484 if( !psz_path )
485 return false;
487 struct stat st;
488 if( vlc_stat( psz_path, &st ) )
490 msg_Dbg( p_access, "could not stat %s: %m", psz_path );
491 free( psz_path );
492 return false;
494 if( !S_ISREG( st.st_mode ) )
496 msg_Dbg( p_access, "%s is not a regular file", psz_path );
497 free( psz_path );
498 return false;
500 msg_Dbg( p_access, "%s exists", psz_path );
501 free( psz_path );
503 ARRAY_APPEND( p_sys->file_sizes, st.st_size );
504 p_access->info.i_size += st.st_size;
505 p_access->info.i_update |= INPUT_UPDATE_SIZE;
507 return true;
510 /*****************************************************************************
511 * Close the current file and open another
512 *****************************************************************************/
513 static bool SwitchFile( access_t *p_access, unsigned i_file )
515 access_sys_t *p_sys = p_access->p_sys;
517 /* requested file already open? */
518 if( p_sys->fd != -1 && p_sys->i_current_file == i_file )
519 return true;
521 /* close old file */
522 if( p_sys->fd != -1 )
524 close( p_sys->fd );
525 p_sys->fd = -1;
528 /* switch */
529 if( i_file >= FILE_COUNT )
530 return false;
531 p_sys->i_current_file = i_file;
533 /* open new file */
534 char *psz_path = GetFilePath( p_access, i_file );
535 if( !psz_path )
536 return false;
537 p_sys->fd = vlc_open( psz_path, O_RDONLY );
539 if( p_sys->fd == -1 )
541 msg_Err( p_access, "Failed to open %s: %m", psz_path );
542 goto error;
545 /* cannot handle anything except normal files */
546 struct stat st;
547 if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) )
549 msg_Err( p_access, "%s is not a regular file", psz_path );
550 goto error;
553 OptimizeForRead( p_sys->fd );
555 msg_Dbg( p_access, "opened %s", psz_path );
556 free( psz_path );
557 return true;
559 error:
560 dialog_Fatal (p_access, _("File reading failed"), _("VLC could not"
561 " open the file \"%s\"."), psz_path);
562 if( p_sys->fd != -1 )
564 close( p_sys->fd );
565 p_sys->fd = -1;
567 free( psz_path );
568 return false;
571 /*****************************************************************************
572 * Some tweaks to speed up read()
573 *****************************************************************************/
574 static void OptimizeForRead( int fd )
576 /* cf. Open() in file access module */
577 VLC_UNUSED(fd);
578 #ifdef HAVE_POSIX_FADVISE
579 posix_fadvise( fd, 0, 4096, POSIX_FADV_WILLNEED );
580 posix_fadvise( fd, 0, 0, POSIX_FADV_NOREUSE );
581 #endif
582 #ifdef HAVE_FCNTL
583 #ifdef F_RDAHEAD
584 fcntl( fd, F_RDAHEAD, 1 );
585 #endif
586 #ifdef F_NOCACHE
587 fcntl( fd, F_NOCACHE, 1 );
588 #endif
589 #endif
592 /*****************************************************************************
593 * Fix size if the (last) part is still growing
594 *****************************************************************************/
595 static void UpdateFileSize( access_t *p_access )
597 access_sys_t *p_sys = p_access->p_sys;
598 struct stat st;
600 if( p_access->info.i_size >= p_access->info.i_pos )
601 return;
603 /* TODO: not sure if this can happen or what to do in this case */
604 if( fstat( p_sys->fd, &st ) )
605 return;
606 if( (uint64_t)st.st_size <= CURRENT_FILE_SIZE )
607 return;
609 p_access->info.i_size -= CURRENT_FILE_SIZE;
610 CURRENT_FILE_SIZE = st.st_size;
611 p_access->info.i_size += CURRENT_FILE_SIZE;
612 p_access->info.i_update |= INPUT_UPDATE_SIZE;
615 /*****************************************************************************
616 * Stat file relative to base directory
617 *****************************************************************************/
618 static int StatRelativeFile( access_t *p_access, const char *psz_file,
619 struct stat *p_stat )
621 /* build path and add extension */
622 char *psz_path;
623 if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
624 p_access->psz_filepath, psz_file,
625 p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
626 return -1;
628 int ret = vlc_stat( psz_path, p_stat );
629 if( ret )
630 msg_Dbg( p_access, "could not stat %s: %m", psz_path );
631 free( psz_path );
633 return ret;
636 /*****************************************************************************
637 * Open file relative to base directory for reading.
638 *****************************************************************************/
639 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file )
641 /* build path and add extension */
642 char *psz_path;
643 if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
644 p_access->psz_filepath, psz_file,
645 p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
646 return NULL;
648 FILE *file = vlc_fopen( psz_path, "rb" );
649 if( !file )
650 msg_Warn( p_access, "Failed to open %s: %m", psz_path );
651 free( psz_path );
653 return file;
656 /*****************************************************************************
657 * Read a line of text. Returns false on error or EOF.
658 *****************************************************************************/
659 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file )
661 ssize_t read = getline( ppsz_line, pi_size, p_file );
663 if( read == -1 )
665 /* automatically free buffer on eof */
666 free( *ppsz_line );
667 *ppsz_line = NULL;
668 return false;
671 if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' )
672 (*ppsz_line)[ read - 1 ] = '\0';
673 EnsureUTF8( *ppsz_line );
675 return true;
678 /*****************************************************************************
679 * Import meta data
680 *****************************************************************************/
681 static void ImportMeta( access_t *p_access )
683 access_sys_t *p_sys = p_access->p_sys;
685 FILE *infofile = OpenRelativeFile( p_access, "info" );
686 if( !infofile )
687 return;
689 vlc_meta_t *p_meta = vlc_meta_New();
690 p_sys->p_meta = p_meta;
691 if( !p_meta )
693 fclose( infofile );
694 return;
697 char *line = NULL;
698 size_t line_len;
699 char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL;
701 while( ReadLine( &line, &line_len, infofile ) )
703 if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' )
704 continue;
706 char tag = line[0];
707 char *text = line + 2;
709 if( tag == 'C' )
711 char *psz_name = strchr( text, ' ' );
712 if( psz_name )
714 *psz_name = '\0';
715 vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
717 vlc_meta_AddExtra( p_meta, "Transponder", text );
720 else if( tag == 'E' )
722 unsigned i_id, i_start, i_length;
723 if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 )
725 char str[50];
726 struct tm tm;
727 time_t start = i_start;
728 localtime_r( &start, &tm );
730 /* TODO: locale */
731 strftime( str, sizeof(str), "%Y-%m-%d %H:%M", &tm );
732 vlc_meta_AddExtra( p_meta, "Date", str );
733 free( psz_date );
734 psz_date = strdup( str );
736 /* display in minutes */
737 i_length = ( i_length + 59 ) / 60;
738 snprintf( str, sizeof(str), "%u:%02u", i_length / 60, i_length % 60 );
739 vlc_meta_AddExtra( p_meta, "Duration", str );
743 else if( tag == 'T' )
745 free( psz_title );
746 psz_title = strdup( text );
747 vlc_meta_AddExtra( p_meta, "Title", text );
750 else if( tag == 'S' )
752 free( psz_smalltext );
753 psz_smalltext = strdup( text );
754 vlc_meta_AddExtra( p_meta, "Info", text );
757 else if( tag == 'D' )
759 for( char *p = text; *p; ++p )
761 if( *p == '|' )
762 *p = '\n';
764 vlc_meta_SetDescription( p_meta, text );
767 /* FPS are required to convert between timestamps and frames */
768 else if( tag == 'F' )
770 float fps = atof( text );
771 if( fps >= 1 )
772 p_sys->fps = fps;
773 vlc_meta_AddExtra( p_meta, "Frame Rate", text );
776 else if( tag == 'P' )
778 vlc_meta_AddExtra( p_meta, "Priority", text );
781 else if( tag == 'L' )
783 vlc_meta_AddExtra( p_meta, "Lifetime", text );
787 /* create a meaningful title */
788 int i_len = 10 +
789 ( psz_title ? strlen( psz_title ) : 0 ) +
790 ( psz_smalltext ? strlen( psz_smalltext ) : 0 ) +
791 ( psz_date ? strlen( psz_date ) : 0 );
792 char *psz_display = malloc( i_len );
794 if( psz_display )
796 *psz_display = '\0';
797 if( psz_title )
798 strcat( psz_display, psz_title );
799 if( psz_title && psz_smalltext )
800 strcat( psz_display, " - " );
801 if( psz_smalltext )
802 strcat( psz_display, psz_smalltext );
803 if( ( psz_title || psz_smalltext ) && psz_date )
805 strcat( psz_display, " (" );
806 strcat( psz_display, psz_date );
807 strcat( psz_display, ")" );
809 if( *psz_display )
810 vlc_meta_SetTitle( p_meta, psz_display );
813 free( psz_display );
814 free( psz_title );
815 free( psz_smalltext );
816 free( psz_date );
818 fclose( infofile );
821 /*****************************************************************************
822 * Import cut marks and convert them to seekpoints (chapters).
823 *****************************************************************************/
824 static void ImportMarks( access_t *p_access )
826 access_sys_t *p_sys = p_access->p_sys;
828 FILE *marksfile = OpenRelativeFile( p_access, "marks" );
829 if( !marksfile )
830 return;
832 FILE *indexfile = OpenRelativeFile( p_access, "index" );
833 if( !indexfile )
835 fclose( marksfile );
836 return;
839 /* Put all cut marks in a "dummy" title */
840 input_title_t *p_marks = vlc_input_title_New();
841 if( !p_marks )
843 fclose( marksfile );
844 fclose( indexfile );
845 return;
847 p_marks->psz_name = strdup( _("VDR Cut Marks") );
849 /* offset for chapter positions */
850 int i_chapter_offset = p_sys->fps / 1000 *
851 var_InheritInteger( p_access, "vdr-chapter-offset" );
853 /* parse lines of the form "0:00:00.00 foobar" */
854 char *line = NULL;
855 size_t line_len;
856 while( ReadLine( &line, &line_len, marksfile ) )
858 int64_t i_frame = ParseFrameNumber( line, p_sys->fps );
860 /* move chapters (simple workaround for inaccurate cut marks) */
861 if( i_frame > -i_chapter_offset )
862 i_frame += i_chapter_offset;
863 else
864 i_frame = 0;
866 uint64_t i_offset;
867 uint16_t i_file_number;
868 if( !ReadIndexRecord( indexfile, p_sys->b_ts_format,
869 i_frame, &i_offset, &i_file_number ) )
870 continue;
871 if( i_file_number < 1 || i_file_number > FILE_COUNT )
872 continue;
874 /* add file sizes to get the "global" offset */
875 seekpoint_t *sp = vlc_seekpoint_New();
876 if( !sp )
877 continue;
878 sp->i_time_offset = i_frame * (int64_t)( CLOCK_FREQ / p_sys->fps );
879 sp->i_byte_offset = i_offset;
880 for( int i = 0; i + 1 < i_file_number; ++i )
881 sp->i_byte_offset += FILE_SIZE( i );
882 sp->psz_name = strdup( line );
884 TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
887 if( p_marks->i_seekpoint > 0 )
889 seekpoint_t *sp = vlc_seekpoint_New();
890 if( sp )
892 sp->i_byte_offset = 0;
893 sp->i_time_offset = 0;
894 sp->psz_name = strdup( _("Start") );
895 TAB_INSERT( p_marks->i_seekpoint, p_marks->seekpoint, sp, 0 );
897 p_sys->p_marks = p_marks;
899 else
901 vlc_input_title_Delete( p_marks );
904 fclose( marksfile );
905 fclose( indexfile );
908 /*****************************************************************************
909 * Lookup frame offset in index file
910 *****************************************************************************/
911 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
912 uint64_t *pi_offset, uint16_t *pi_file_num )
914 uint8_t index_record[8];
915 if( fseek( p_file, sizeof(index_record) * i_frame, SEEK_SET ) != 0 )
916 return false;
917 if( fread( &index_record, sizeof(index_record), 1, p_file ) <= 0 )
918 return false;
920 /* VDR usually (only?) runs on little endian machines, but VLC has a
921 * broader audience. See recording.* in VDR source for data layout. */
922 if( b_ts )
924 uint64_t i_index_entry = GetQWLE( &index_record );
925 *pi_offset = i_index_entry & UINT64_C(0xFFFFFFFFFF);
926 *pi_file_num = i_index_entry >> 48;
928 else
930 *pi_offset = GetDWLE( &index_record );
931 *pi_file_num = index_record[5];
934 return true;
937 /*****************************************************************************
938 * Convert time stamp from file to frame number
939 *****************************************************************************/
940 static int64_t ParseFrameNumber( const char *psz_line, float fps )
942 unsigned h, m, s, f, n;
944 /* hour:min:sec.frame (frame is optional) */
945 n = sscanf( psz_line, "%u:%u:%u.%u", &h, &m, &s, &f );
946 if( n >= 3 )
948 if( n < 4 )
949 f = 1;
950 int64_t i_seconds = (int64_t)h * 3600 + (int64_t)m * 60 + s;
951 return (int64_t)( i_seconds * (double)fps ) + __MAX(1, f) - 1;
954 /* only a frame number */
955 int64_t i_frame = strtoll( psz_line, NULL, 10 );
956 return __MAX(1, i_frame) - 1;