contrib: soxr: enable by default
[vlc.git] / modules / access / vdr.c
blobcd9ee7fea64a2f8199219969644e07a27ddce76c
1 /*****************************************************************************
2 * vdr.c: VDR recordings access plugin
3 *****************************************************************************
4 * Copyright (C) 2010 Tobias Güntner
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
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 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 ***/
40 /*****************************************************************************
41 * Preamble
42 *****************************************************************************/
44 #ifdef HAVE_CONFIG_H
45 # include "config.h"
46 #endif
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <fcntl.h>
51 #include <unistd.h>
53 #include <ctype.h>
54 #include <time.h>
55 #include <errno.h>
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>
66 /*****************************************************************************
67 * Module descriptor
68 *****************************************************************************/
69 static int Open ( vlc_object_t * );
70 static void Close( vlc_object_t * );
72 #define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).")
74 #define CHAPTER_OFFSET_TEXT N_("Chapter offset in ms")
75 #define CHAPTER_OFFSET_LONGTEXT N_( \
76 "Move all chapters. This value should be set in milliseconds." )
78 #define FPS_TEXT N_("Frame rate")
79 #define FPS_LONGTEXT N_( \
80 "Default frame rate for chapter import." )
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 ()
100 /*****************************************************************************
101 * Local prototypes, constants, structures
102 *****************************************************************************/
104 /* minimum chapter size in seconds */
105 #define MIN_CHAPTER_SIZE 5
107 TYPEDEF_ARRAY( uint64_t, size_array_t );
109 struct access_sys_t
111 /* file sizes of all parts */
112 size_array_t file_sizes;
113 uint64_t offset;
114 uint64_t size; /* total size */
116 /* index and fd of current open file */
117 unsigned i_current_file;
118 int fd;
120 /* meta data */
121 vlc_meta_t *p_meta;
123 /* cut marks */
124 input_title_t *p_marks;
125 uint64_t *offsets;
126 unsigned cur_seekpoint;
127 float fps;
129 /* file format: true=TS, false=PES */
130 bool b_ts_format;
133 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
134 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
135 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
137 static int Control( stream_t *, int, va_list );
138 static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len );
139 static int Seek( stream_t *p_access, uint64_t i_pos);
140 static void FindSeekpoint( stream_t *p_access );
141 static bool ScanDirectory( stream_t *p_access );
142 static char *GetFilePath( stream_t *p_access, unsigned i_file );
143 static bool ImportNextFile( stream_t *p_access );
144 static bool SwitchFile( stream_t *p_access, unsigned i_file );
145 static void OptimizeForRead( int fd );
146 static void UpdateFileSize( stream_t *p_access );
147 static FILE *OpenRelativeFile( stream_t *p_access, const char *psz_file );
148 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file );
149 static void ImportMeta( stream_t *p_access );
150 static void ImportMarks( stream_t *p_access );
151 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
152 uint64_t *pi_offset, uint16_t *pi_file_num );
153 static int64_t ParseFrameNumber( const char *psz_line, float fps );
154 static const char *BaseName( const char *psz_path );
156 /*****************************************************************************
157 * Open a directory
158 *****************************************************************************/
159 static int Open( vlc_object_t *p_this )
161 stream_t *p_access = (stream_t*)p_this;
163 if( !p_access->psz_filepath )
164 return VLC_EGENERIC;
166 /* Some tests can be skipped if this module was explicitly requested.
167 * That way, the user can play "corrupt" recordings if necessary
168 * and we can avoid false positives in the general case. */
169 bool b_strict = strcmp( p_access->psz_name, "vdr" );
171 /* Do a quick test based on the directory name to see if this
172 * directory might contain a VDR recording. We can be reasonably
173 * sure if ScanDirectory() actually finds files. */
174 if( b_strict )
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;
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;
191 access_sys_t *p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
193 if( unlikely(p_sys == NULL) )
194 return VLC_ENOMEM;
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 );
202 /* Import all files and prepare playback. */
203 if( !ScanDirectory( p_access ) ||
204 !SwitchFile( p_access, 0 ) )
206 Close( p_this );
207 return VLC_EGENERIC;
210 ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
211 return VLC_SUCCESS;
214 /*****************************************************************************
215 * Close files and free resources
216 *****************************************************************************/
217 static void Close( vlc_object_t * p_this )
219 stream_t *p_access = (stream_t*)p_this;
220 access_sys_t *p_sys = p_access->p_sys;
222 if( p_sys->fd != -1 )
223 vlc_close( p_sys->fd );
224 ARRAY_RESET( p_sys->file_sizes );
226 if( p_sys->p_meta )
227 vlc_meta_Delete( p_sys->p_meta );
229 free( p_sys->offsets );
230 vlc_input_title_Delete( p_sys->p_marks );
233 /*****************************************************************************
234 * Determine format and import files
235 *****************************************************************************/
236 static bool ScanDirectory( stream_t *p_access )
238 access_sys_t *p_sys = p_access->p_sys;
240 /* find first part and determine directory format */
241 p_sys->b_ts_format = true;
242 if( !ImportNextFile( p_access ) )
244 p_sys->b_ts_format = !p_sys->b_ts_format;
245 if( !ImportNextFile( p_access ) )
246 return false;
249 /* get all remaining parts */
250 while( ImportNextFile( p_access ) )
251 continue;
253 /* import meta data etc. */
254 ImportMeta( p_access );
256 /* cut marks depend on meta data and file sizes */
257 ImportMarks( p_access );
259 return true;
262 /*****************************************************************************
263 * Control input stream
264 *****************************************************************************/
265 static int Control( stream_t *p_access, int i_query, va_list args )
267 access_sys_t *p_sys = p_access->p_sys;
268 input_title_t ***ppp_title;
269 int i;
270 int64_t *pi64;
271 vlc_meta_t *p_meta;
273 switch( i_query )
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;
282 case STREAM_GET_SIZE:
283 *va_arg( args, uint64_t* ) = p_sys->size;
284 break;
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;
292 case STREAM_SET_PAUSE_STATE:
293 /* nothing to do */
294 break;
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;
308 case STREAM_GET_TITLE:
309 *va_arg( args, unsigned * ) = 0;
310 break;
312 case STREAM_GET_SEEKPOINT:
313 *va_arg( args, unsigned * ) = p_sys->cur_seekpoint;
314 break;
316 case STREAM_GET_CONTENT_TYPE:
317 *va_arg( args, char ** ) =
318 strdup( p_sys->b_ts_format ? "video/MP2T" : "video/MP2P" );
319 break;
321 case STREAM_SET_TITLE:
322 /* ignore - only one title */
323 break;
325 case STREAM_SET_SEEKPOINT:
326 i = va_arg( args, int );
327 return Seek( p_access, p_sys->offsets[i] );
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;
334 default:
335 return VLC_EGENERIC;
337 return VLC_SUCCESS;
340 /*****************************************************************************
341 * Read and concatenate files
342 *****************************************************************************/
343 static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len )
345 access_sys_t *p_sys = p_access->p_sys;
347 if( p_sys->fd == -1 )
348 /* no more data */
349 return 0;
351 ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
353 if( i_ret > 0 )
355 /* success */
356 p_sys->offset += i_ret;
357 UpdateFileSize( p_access );
358 FindSeekpoint( p_access );
359 return i_ret;
361 else if( i_ret == 0 )
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;
370 else if( errno == EINTR )
372 /* try again later */
373 return -1;
375 else
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;
387 /*****************************************************************************
388 * Seek to a specific location in a file
389 *****************************************************************************/
390 static int Seek( stream_t *p_access, uint64_t i_pos )
392 access_sys_t *p_sys = p_access->p_sys;
394 /* might happen if called by STREAM_SET_SEEKPOINT */
395 i_pos = __MIN( i_pos, p_sys->size );
397 p_sys->offset = i_pos;
399 /* find correct chapter */
400 FindSeekpoint( p_access );
402 /* find correct file */
403 unsigned i_file = 0;
404 while( i_file < FILE_COUNT - 1 &&
405 i_pos >= FILE_SIZE( i_file ) )
407 i_pos -= FILE_SIZE( i_file );
408 i_file++;
410 if( !SwitchFile( p_access, i_file ) )
411 return VLC_EGENERIC;
413 /* adjust position within that file */
414 return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ?
415 VLC_SUCCESS : VLC_EGENERIC;
418 /*****************************************************************************
419 * Change the chapter index to match the current position
420 *****************************************************************************/
421 static void FindSeekpoint( stream_t *p_access )
423 access_sys_t *p_sys = p_access->p_sys;
424 if( !p_sys->p_marks )
425 return;
427 int new_seekpoint = p_sys->cur_seekpoint;
428 if( p_sys->offset < p_sys->offsets[p_sys->cur_seekpoint] )
430 /* i_pos moved backwards, start fresh */
431 new_seekpoint = 0;
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] )
438 new_seekpoint++;
441 p_sys->cur_seekpoint = new_seekpoint;
444 /*****************************************************************************
445 * Returns the path of a certain part
446 *****************************************************************************/
447 static char *GetFilePath( stream_t *p_access, unsigned i_file )
449 access_sys_t *sys = p_access->p_sys;
450 char *psz_path;
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;
460 /*****************************************************************************
461 * Check if another part exists and import it
462 *****************************************************************************/
463 static bool ImportNextFile( stream_t *p_access )
465 access_sys_t *p_sys = p_access->p_sys;
467 char *psz_path = GetFilePath( p_access, FILE_COUNT );
468 if( !psz_path )
469 return false;
471 struct stat st;
472 if( vlc_stat( psz_path, &st ) )
474 msg_Dbg( p_access, "could not stat %s: %s", psz_path,
475 vlc_strerror_c(errno) );
476 free( psz_path );
477 return false;
479 if( !S_ISREG( st.st_mode ) )
481 msg_Dbg( p_access, "%s is not a regular file", psz_path );
482 free( psz_path );
483 return false;
485 msg_Dbg( p_access, "%s exists", psz_path );
486 free( psz_path );
488 ARRAY_APPEND( p_sys->file_sizes, st.st_size );
489 p_sys->size += st.st_size;
491 return true;
494 /*****************************************************************************
495 * Close the current file and open another
496 *****************************************************************************/
497 static bool SwitchFile( stream_t *p_access, unsigned i_file )
499 access_sys_t *p_sys = p_access->p_sys;
501 /* requested file already open? */
502 if( p_sys->fd != -1 && p_sys->i_current_file == i_file )
503 return true;
505 /* close old file */
506 if( p_sys->fd != -1 )
508 vlc_close( p_sys->fd );
509 p_sys->fd = -1;
512 /* switch */
513 if( i_file >= FILE_COUNT )
514 return false;
515 p_sys->i_current_file = i_file;
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 );
523 if( p_sys->fd == -1 )
525 msg_Err( p_access, "Failed to open %s: %s", psz_path,
526 vlc_strerror_c(errno) );
527 goto error;
530 /* cannot handle anything except normal files */
531 struct stat st;
532 if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) )
534 msg_Err( p_access, "%s is not a regular file", psz_path );
535 goto error;
538 OptimizeForRead( p_sys->fd );
540 msg_Dbg( p_access, "opened %s", psz_path );
541 free( psz_path );
542 return true;
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 )
549 vlc_close( p_sys->fd );
550 p_sys->fd = -1;
552 free( psz_path );
553 return false;
556 /*****************************************************************************
557 * Some tweaks to speed up read()
558 *****************************************************************************/
559 static void OptimizeForRead( int fd )
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
575 /*****************************************************************************
576 * Fix size if the (last) part is still growing
577 *****************************************************************************/
578 static void UpdateFileSize( stream_t *p_access )
580 access_sys_t *p_sys = p_access->p_sys;
581 struct stat st;
583 if( p_sys->size >= p_sys->offset )
584 return;
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;
592 p_sys->size -= CURRENT_FILE_SIZE;
593 CURRENT_FILE_SIZE = st.st_size;
594 p_sys->size += CURRENT_FILE_SIZE;
597 /*****************************************************************************
598 * Open file relative to base directory for reading.
599 *****************************************************************************/
600 static FILE *OpenRelativeFile( stream_t *p_access, const char *psz_file )
602 access_sys_t *sys = p_access->p_sys;
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;
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 );
616 return file;
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 )
624 ssize_t read = getline( ppsz_line, pi_size, p_file );
626 if( read == -1 )
628 /* automatically free buffer on eof */
629 free( *ppsz_line );
630 *ppsz_line = NULL;
631 return false;
634 if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' )
635 (*ppsz_line)[ read - 1 ] = '\0';
636 EnsureUTF8( *ppsz_line );
638 return true;
641 /*****************************************************************************
642 * Import meta data
643 *****************************************************************************/
644 static void ImportMeta( stream_t *p_access )
646 access_sys_t *p_sys = p_access->p_sys;
648 FILE *infofile = OpenRelativeFile( p_access, "info" );
649 if( !infofile )
650 return;
652 vlc_meta_t *p_meta = vlc_meta_New();
653 p_sys->p_meta = p_meta;
654 if( !p_meta )
656 fclose( infofile );
657 return;
660 char *line = NULL;
661 size_t line_len;
662 char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL;
664 while( ReadLine( &line, &line_len, infofile ) )
666 if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' )
667 continue;
669 char tag = line[0];
670 char *text = line + 2;
672 if( tag == 'C' )
674 char *psz_name = strchr( text, ' ' );
675 if( psz_name )
677 *psz_name = '\0';
678 vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
680 vlc_meta_AddExtra( p_meta, "Transponder", text );
683 else if( tag == 'E' )
685 unsigned i_id, i_start, i_length;
686 if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 )
688 char str[50];
689 struct tm tm;
690 time_t start = i_start;
691 localtime_r( &start, &tm );
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 );
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 );
706 else if( tag == 'T' )
708 free( psz_title );
709 psz_title = strdup( text );
710 vlc_meta_AddExtra( p_meta, "Title", text );
713 else if( tag == 'S' )
715 free( psz_smalltext );
716 psz_smalltext = strdup( text );
717 vlc_meta_AddExtra( p_meta, "Info", text );
720 else if( tag == 'D' )
722 for( char *p = text; *p; ++p )
724 if( *p == '|' )
725 *p = '\n';
727 vlc_meta_SetDescription( p_meta, text );
730 /* FPS are required to convert between timestamps and frames */
731 else if( tag == 'F' )
733 float fps = atof( text );
734 if( fps >= 1 )
735 p_sys->fps = fps;
736 vlc_meta_AddExtra( p_meta, "Frame Rate", text );
739 else if( tag == 'P' )
741 vlc_meta_AddExtra( p_meta, "Priority", text );
744 else if( tag == 'L' )
746 vlc_meta_AddExtra( p_meta, "Lifetime", text );
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 );
757 if( psz_display )
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 )
768 strcat( psz_display, " (" );
769 strcat( psz_display, psz_date );
770 strcat( psz_display, ")" );
772 if( *psz_display )
773 vlc_meta_SetTitle( p_meta, psz_display );
776 free( psz_display );
777 free( psz_title );
778 free( psz_smalltext );
779 free( psz_date );
781 fclose( infofile );
784 /*****************************************************************************
785 * Import cut marks and convert them to seekpoints (chapters).
786 *****************************************************************************/
787 static void ImportMarks( stream_t *p_access )
789 access_sys_t *p_sys = p_access->p_sys;
791 FILE *marksfile = OpenRelativeFile( p_access, "marks" );
792 if( !marksfile )
793 return;
795 FILE *indexfile = OpenRelativeFile( p_access, "index" );
796 if( !indexfile )
798 fclose( marksfile );
799 return;
802 /* get the length of this recording (index stores 8 bytes per frame) */
803 struct stat st;
804 if( fstat( fileno( indexfile ), &st ) )
806 fclose( marksfile );
807 fclose( indexfile );
808 return;
810 int64_t i_frame_count = st.st_size / 8;
812 /* Put all cut marks in a "dummy" title */
813 input_title_t *p_marks = vlc_input_title_New();
814 if( !p_marks )
816 fclose( marksfile );
817 fclose( indexfile );
818 return;
820 p_marks->psz_name = strdup( _("VDR Cut Marks") );
821 p_marks->i_length = i_frame_count * (int64_t)( CLOCK_FREQ / p_sys->fps );
823 uint64_t *offsetv = NULL;
825 /* offset for chapter positions */
826 int i_chapter_offset = p_sys->fps / 1000 *
827 var_InheritInteger( p_access, "vdr-chapter-offset" );
829 /* minimum chapter size in frames */
830 int i_min_chapter_size = p_sys->fps * MIN_CHAPTER_SIZE;
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;
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 ) )
841 int64_t i_frame = ParseFrameNumber( line, p_sys->fps );
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;
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;
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;
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 );
870 TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
871 offsetv = xrealloc(offsetv, p_marks->i_seekpoint * sizeof (*offsetv));
873 for( int i = 0; i + 1 < i_file_number; ++i )
874 i_offset += FILE_SIZE(i);
876 offsetv[p_marks->i_seekpoint - 1] = i_offset;
879 /* add a chapter at the beginning if missing */
880 if( p_marks->i_seekpoint > 0 && offsetv[0] > 0 )
882 seekpoint_t *sp = vlc_seekpoint_New();
883 if( sp )
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;
896 if( p_marks->i_seekpoint > 0 )
898 p_sys->p_marks = p_marks;
899 p_sys->offsets = offsetv;
901 else
903 vlc_input_title_Delete( p_marks );
904 free(offsetv);
907 fclose( marksfile );
908 fclose( indexfile );
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 )
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;
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 )
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;
931 else
933 *pi_offset = GetDWLE( &index_record );
934 *pi_file_num = index_record[5];
937 return true;
940 /*****************************************************************************
941 * Convert time stamp from file to frame number
942 *****************************************************************************/
943 static int64_t ParseFrameNumber( const char *psz_line, float fps )
945 unsigned h, m, s, f, n;
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 )
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;
957 /* only a frame number */
958 int64_t i_frame = strtoll( psz_line, NULL, 10 );
959 return __MAX(1, i_frame) - 1;
962 /*****************************************************************************
963 * Return the last path component (including trailing separators)
964 *****************************************************************************/
965 static const char *BaseName( const char *psz_path )
967 const char *psz_name = psz_path + strlen( psz_path );
969 /* skip superfluous separators at the end */
970 while( psz_name > psz_path && psz_name[-1] == DIR_SEP_CHAR )
971 --psz_name;
973 /* skip last component */
974 while( psz_name > psz_path && psz_name[-1] != DIR_SEP_CHAR )
975 --psz_name;
977 return psz_name;