More mailmap fixes
[vlc.git] / modules / access / vdr.c
blob0939669f7f58e25cafa392dbbeff27a3e47914f1
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 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." )
93 #define FPS_TEXT N_("Frame rate")
94 #define FPS_LONGTEXT N_( \
95 "Default frame rate for chapter import." )
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 ()
115 /*****************************************************************************
116 * Local prototypes, constants, structures
117 *****************************************************************************/
119 /* minimum chapter size in seconds */
120 #define MIN_CHAPTER_SIZE 5
122 TYPEDEF_ARRAY( uint64_t, size_array_t );
124 struct access_sys_t
126 /* file sizes of all parts */
127 size_array_t file_sizes;
129 /* index and fd of current open file */
130 unsigned i_current_file;
131 int fd;
133 /* meta data */
134 vlc_meta_t *p_meta;
136 /* cut marks */
137 input_title_t *p_marks;
138 float fps;
140 /* file format: true=TS, false=PES */
141 bool b_ts_format;
144 #define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file)
145 #define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos)
146 #define FILE_COUNT (unsigned)p_sys->file_sizes.i_size
148 static int Control( access_t *, int, va_list );
149 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len );
150 static int Seek( access_t *p_access, uint64_t i_pos);
151 static void FindSeekpoint( access_t *p_access );
152 static bool ScanDirectory( access_t *p_access, bool b_strict );
153 static char *GetFilePath( access_t *p_access, unsigned i_file );
154 static bool ImportNextFile( access_t *p_access );
155 static bool SwitchFile( access_t *p_access, unsigned i_file );
156 static void OptimizeForRead( int fd );
157 static void UpdateFileSize( access_t *p_access );
158 static int StatRelativeFile( access_t *p_access, const char *psz_file,
159 struct stat *p_stat );
160 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file );
161 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file );
162 static void ImportMeta( access_t *p_access );
163 static void ImportMarks( access_t *p_access );
164 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
165 uint64_t *pi_offset, uint16_t *pi_file_num );
166 static int64_t ParseFrameNumber( const char *psz_line, float fps );
168 /*****************************************************************************
169 * Open a directory
170 *****************************************************************************/
171 static int Open( vlc_object_t *p_this )
173 access_t *p_access = (access_t*)p_this;
175 if( !p_access->psz_filepath )
176 return VLC_EGENERIC;
178 /* Some tests can be skipped if this module was explicitly requested.
179 * That way, the user can play "corrupt" recordings if necessary
180 * and we can avoid false positives in the general case. */
181 bool b_strict = strcmp( p_access->psz_access, "vdr" );
183 /* Do a quick test based on the directory extension to see if this
184 * directory might contain a VDR recording. We can be reasonably
185 * sure if ScanDirectory() actually finds files. */
186 if( b_strict )
188 const char *psz_ext = strrchr( p_access->psz_filepath, '.' );
189 if( !psz_ext || strcasecmp( psz_ext, ".rec" ) )
190 return VLC_EGENERIC;
193 /* Only directories can be recordings */
194 struct stat st;
195 if( vlc_stat( p_access->psz_filepath, &st ) ||
196 !S_ISDIR( st.st_mode ) )
197 return VLC_EGENERIC;
199 access_sys_t *p_sys;
200 STANDARD_READ_ACCESS_INIT;
201 p_sys->fd = -1;
202 p_sys->fps = var_InheritFloat( p_access, "vdr-fps" );
203 ARRAY_INIT( p_sys->file_sizes );
205 /* Import all files and prepare playback. */
206 if( !ScanDirectory( p_access, b_strict ) ||
207 !SwitchFile( p_access, 0 ) )
209 Close( p_this );
210 return VLC_EGENERIC;
213 return VLC_SUCCESS;
216 /*****************************************************************************
217 * Close files and free resources
218 *****************************************************************************/
219 static void Close( vlc_object_t * p_this )
221 access_t *p_access = (access_t*)p_this;
222 access_sys_t *p_sys = p_access->p_sys;
224 if( p_sys->fd != -1 )
225 close( p_sys->fd );
226 ARRAY_RESET( p_sys->file_sizes );
228 if( p_sys->p_meta )
229 vlc_meta_Delete( p_sys->p_meta );
231 vlc_input_title_Delete( p_sys->p_marks );
232 free( p_sys );
235 /*****************************************************************************
236 * Determine format and import files
237 *****************************************************************************/
238 static bool ScanDirectory( access_t *p_access, bool b_strict )
240 access_sys_t *p_sys = p_access->p_sys;
242 /* find first part and determine directory format */
243 p_sys->b_ts_format = true;
244 if( !ImportNextFile( p_access ) )
246 p_sys->b_ts_format = !p_sys->b_ts_format;
247 if( !ImportNextFile( p_access ) )
248 return false;
251 /* meta data and index should exist */
252 if( b_strict )
254 struct stat st;
255 if( StatRelativeFile( p_access, "info", &st ) ||
256 StatRelativeFile( p_access, "index", &st ) )
257 return false;
260 /* get all remaining parts */
261 while( ImportNextFile( p_access ) )
262 continue;
264 /* import meta data etc. */
265 ImportMeta( p_access );
267 /* cut marks depend on meta data and file sizes */
268 ImportMarks( p_access );
270 return true;
273 /*****************************************************************************
274 * Control input stream
275 *****************************************************************************/
276 static int Control( access_t *p_access, int i_query, va_list args )
278 access_sys_t *p_sys = p_access->p_sys;
279 input_title_t ***ppp_title;
280 int i;
281 int64_t *pi64;
282 vlc_meta_t *p_meta;
284 switch( i_query )
286 case ACCESS_CAN_SEEK:
287 case ACCESS_CAN_PAUSE:
288 case ACCESS_CAN_CONTROL_PACE:
289 *va_arg( args, bool* ) = true;
290 break;
292 case ACCESS_CAN_FASTSEEK:
293 /* Seek() can open files, so it might be "too slow" */
294 *va_arg( args, bool* ) = false;
295 break;
297 case ACCESS_GET_PTS_DELAY:
298 pi64 = va_arg( args, int64_t * );
299 *pi64 = INT64_C(1000)
300 * var_InheritInteger( p_access, "file-caching" );
301 break;
303 case ACCESS_SET_PAUSE_STATE:
304 /* nothing to do */
305 break;
307 case ACCESS_GET_TITLE_INFO:
308 /* return a copy of our seek points */
309 if( !p_sys->p_marks )
310 return VLC_EGENERIC;
311 ppp_title = va_arg( args, input_title_t*** );
312 *va_arg( args, int* ) = 1;
313 *ppp_title = malloc( sizeof( input_title_t** ) );
314 if( !*ppp_title )
315 return VLC_ENOMEM;
316 **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks );
317 break;
319 case ACCESS_SET_TITLE:
320 /* ignore - only one title */
321 break;
323 case ACCESS_SET_SEEKPOINT:
324 i = va_arg( args, int );
325 /* Seek updates p_access->info */
326 return Seek( p_access, p_sys->p_marks->seekpoint[i]->i_byte_offset );
328 case ACCESS_GET_META:
329 if( !p_sys->p_meta )
330 return VLC_EGENERIC;
331 p_meta = va_arg( args, vlc_meta_t* );
332 vlc_meta_Merge( p_meta, p_sys->p_meta );
333 break;
335 case ACCESS_SET_PRIVATE_ID_STATE:
336 case ACCESS_GET_CONTENT_TYPE:
337 return VLC_EGENERIC;
339 default:
340 msg_Warn( p_access, "unimplemented query in control" );
341 return VLC_EGENERIC;
343 return VLC_SUCCESS;
346 /*****************************************************************************
347 * Read and concatenate files
348 *****************************************************************************/
349 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
351 access_sys_t *p_sys = p_access->p_sys;
353 if( p_sys->fd == -1 )
355 /* no more data */
356 p_access->info.b_eof = true;
357 return 0;
360 ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
362 if( i_ret > 0 )
364 /* success */
365 p_access->info.i_pos += i_ret;
366 UpdateFileSize( p_access );
367 FindSeekpoint( p_access );
368 return i_ret;
370 else if( i_ret == 0 )
372 /* check for new files in case the recording is still active */
373 if( p_sys->i_current_file >= FILE_COUNT - 1 )
374 ImportNextFile( p_access );
375 /* play next file */
376 SwitchFile( p_access, p_sys->i_current_file + 1 );
377 return -1;
379 else if( errno == EINTR )
381 /* try again later */
382 return -1;
384 else
386 /* abort on read error */
387 msg_Err( p_access, "failed to read (%m)" );
388 dialog_Fatal( p_access, _("File reading failed"), "%s (%m)",
389 _("VLC could not read the file.") );
390 SwitchFile( p_access, -1 );
391 return 0;
395 /*****************************************************************************
396 * Seek to a specific location in a file
397 *****************************************************************************/
398 static int Seek( access_t *p_access, uint64_t i_pos )
400 access_sys_t *p_sys = p_access->p_sys;
402 /* might happen if called by ACCESS_SET_SEEKPOINT */
403 i_pos = __MIN( i_pos, p_access->info.i_size );
405 p_access->info.i_pos = i_pos;
406 p_access->info.b_eof = false;
408 /* find correct chapter */
409 FindSeekpoint( p_access );
411 /* find correct file */
412 unsigned i_file = 0;
413 while( i_pos >= FILE_SIZE( i_file ) &&
414 i_file < FILE_COUNT - 1 )
416 i_pos -= FILE_SIZE( i_file );
417 i_file++;
419 if( !SwitchFile( p_access, i_file ) )
420 return VLC_EGENERIC;
422 /* adjust position within that file */
423 return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ?
424 VLC_SUCCESS : VLC_EGENERIC;
427 /*****************************************************************************
428 * Change the chapter index to match the current position
429 *****************************************************************************/
430 static void FindSeekpoint( access_t *p_access )
432 access_sys_t *p_sys = p_access->p_sys;
433 if( !p_sys->p_marks )
434 return;
436 int i_new_seekpoint = p_access->info.i_seekpoint;
437 if( p_access->info.i_pos < (uint64_t)p_sys->p_marks->
438 seekpoint[ p_access->info.i_seekpoint ]->i_byte_offset )
440 /* i_pos moved backwards, start fresh */
441 i_new_seekpoint = 0;
444 /* only need to check the following seekpoints */
445 while( i_new_seekpoint + 1 < p_sys->p_marks->i_seekpoint &&
446 p_access->info.i_pos >= (uint64_t)p_sys->p_marks->
447 seekpoint[ i_new_seekpoint + 1 ]->i_byte_offset )
449 i_new_seekpoint++;
452 /* avoid unnecessary events */
453 if( p_access->info.i_seekpoint != i_new_seekpoint )
455 p_access->info.i_seekpoint = i_new_seekpoint;
456 p_access->info.i_update |= INPUT_UPDATE_SEEKPOINT;
460 /*****************************************************************************
461 * Returns the path of a certain part
462 *****************************************************************************/
463 static char *GetFilePath( access_t *p_access, unsigned i_file )
465 char *psz_path;
466 if( asprintf( &psz_path, p_access->p_sys->b_ts_format ?
467 "%s" DIR_SEP "%05u.ts" : "%s" DIR_SEP "%03u.vdr",
468 p_access->psz_filepath, i_file + 1 ) == -1 )
469 return NULL;
470 else
471 return psz_path;
474 /*****************************************************************************
475 * Check if another part exists and import it
476 *****************************************************************************/
477 static bool ImportNextFile( access_t *p_access )
479 access_sys_t *p_sys = p_access->p_sys;
481 char *psz_path = GetFilePath( p_access, FILE_COUNT );
482 if( !psz_path )
483 return false;
485 struct stat st;
486 if( vlc_stat( psz_path, &st ) )
488 msg_Dbg( p_access, "could not stat %s: %m", psz_path );
489 free( psz_path );
490 return false;
492 if( !S_ISREG( st.st_mode ) )
494 msg_Dbg( p_access, "%s is not a regular file", psz_path );
495 free( psz_path );
496 return false;
498 msg_Dbg( p_access, "%s exists", psz_path );
499 free( psz_path );
501 ARRAY_APPEND( p_sys->file_sizes, st.st_size );
502 p_access->info.i_size += st.st_size;
503 p_access->info.i_update |= INPUT_UPDATE_SIZE;
505 return true;
508 /*****************************************************************************
509 * Close the current file and open another
510 *****************************************************************************/
511 static bool SwitchFile( access_t *p_access, unsigned i_file )
513 access_sys_t *p_sys = p_access->p_sys;
515 /* requested file already open? */
516 if( p_sys->fd != -1 && p_sys->i_current_file == i_file )
517 return true;
519 /* close old file */
520 if( p_sys->fd != -1 )
522 close( p_sys->fd );
523 p_sys->fd = -1;
526 /* switch */
527 if( i_file >= FILE_COUNT )
528 return false;
529 p_sys->i_current_file = i_file;
531 /* open new file */
532 char *psz_path = GetFilePath( p_access, i_file );
533 if( !psz_path )
534 return false;
535 p_sys->fd = vlc_open( psz_path, O_RDONLY );
537 if( p_sys->fd == -1 )
539 msg_Err( p_access, "Failed to open %s: %m", psz_path );
540 goto error;
543 /* cannot handle anything except normal files */
544 struct stat st;
545 if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) )
547 msg_Err( p_access, "%s is not a regular file", psz_path );
548 goto error;
551 OptimizeForRead( p_sys->fd );
553 msg_Dbg( p_access, "opened %s", psz_path );
554 free( psz_path );
555 return true;
557 error:
558 dialog_Fatal (p_access, _("File reading failed"), _("VLC could not"
559 " open the file \"%s\". (%m)"), psz_path);
560 if( p_sys->fd != -1 )
562 close( p_sys->fd );
563 p_sys->fd = -1;
565 free( psz_path );
566 return false;
569 /*****************************************************************************
570 * Some tweaks to speed up read()
571 *****************************************************************************/
572 static void OptimizeForRead( int fd )
574 /* cf. Open() in file access module */
575 VLC_UNUSED(fd);
576 #ifdef HAVE_POSIX_FADVISE
577 posix_fadvise( fd, 0, 4096, POSIX_FADV_WILLNEED );
578 posix_fadvise( fd, 0, 0, POSIX_FADV_NOREUSE );
579 #endif
580 #ifdef HAVE_FCNTL
581 #ifdef F_RDAHEAD
582 fcntl( fd, F_RDAHEAD, 1 );
583 #endif
584 #ifdef F_NOCACHE
585 fcntl( fd, F_NOCACHE, 1 );
586 #endif
587 #endif
590 /*****************************************************************************
591 * Fix size if the (last) part is still growing
592 *****************************************************************************/
593 static void UpdateFileSize( access_t *p_access )
595 access_sys_t *p_sys = p_access->p_sys;
596 struct stat st;
598 if( p_access->info.i_size >= p_access->info.i_pos )
599 return;
601 /* TODO: not sure if this can happen or what to do in this case */
602 if( fstat( p_sys->fd, &st ) )
603 return;
604 if( (uint64_t)st.st_size <= CURRENT_FILE_SIZE )
605 return;
607 p_access->info.i_size -= CURRENT_FILE_SIZE;
608 CURRENT_FILE_SIZE = st.st_size;
609 p_access->info.i_size += CURRENT_FILE_SIZE;
610 p_access->info.i_update |= INPUT_UPDATE_SIZE;
613 /*****************************************************************************
614 * Stat file relative to base directory
615 *****************************************************************************/
616 static int StatRelativeFile( access_t *p_access, const char *psz_file,
617 struct stat *p_stat )
619 /* build path and add extension */
620 char *psz_path;
621 if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
622 p_access->psz_filepath, psz_file,
623 p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
624 return -1;
626 int ret = vlc_stat( psz_path, p_stat );
627 if( ret )
628 msg_Dbg( p_access, "could not stat %s: %m", psz_path );
629 free( psz_path );
631 return ret;
634 /*****************************************************************************
635 * Open file relative to base directory for reading.
636 *****************************************************************************/
637 static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file )
639 /* build path and add extension */
640 char *psz_path;
641 if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
642 p_access->psz_filepath, psz_file,
643 p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
644 return NULL;
646 FILE *file = vlc_fopen( psz_path, "rb" );
647 if( !file )
648 msg_Warn( p_access, "Failed to open %s: %m", psz_path );
649 free( psz_path );
651 return file;
654 /*****************************************************************************
655 * Read a line of text. Returns false on error or EOF.
656 *****************************************************************************/
657 static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file )
659 ssize_t read = getline( ppsz_line, pi_size, p_file );
661 if( read == -1 )
663 /* automatically free buffer on eof */
664 free( *ppsz_line );
665 *ppsz_line = NULL;
666 return false;
669 if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' )
670 (*ppsz_line)[ read - 1 ] = '\0';
671 EnsureUTF8( *ppsz_line );
673 return true;
676 /*****************************************************************************
677 * Import meta data
678 *****************************************************************************/
679 static void ImportMeta( access_t *p_access )
681 access_sys_t *p_sys = p_access->p_sys;
683 FILE *infofile = OpenRelativeFile( p_access, "info" );
684 if( !infofile )
685 return;
687 vlc_meta_t *p_meta = vlc_meta_New();
688 p_sys->p_meta = p_meta;
689 if( !p_meta )
691 fclose( infofile );
692 return;
695 char *line = NULL;
696 size_t line_len;
697 char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL;
699 while( ReadLine( &line, &line_len, infofile ) )
701 if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' )
702 continue;
704 char tag = line[0];
705 char *text = line + 2;
707 if( tag == 'C' )
709 char *psz_name = strchr( text, ' ' );
710 if( psz_name )
712 *psz_name = '\0';
713 vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
715 vlc_meta_AddExtra( p_meta, "Transponder", text );
718 else if( tag == 'E' )
720 unsigned i_id, i_start, i_length;
721 if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 )
723 char str[50];
724 struct tm tm;
725 time_t start = i_start;
726 localtime_r( &start, &tm );
728 /* TODO: locale */
729 strftime( str, sizeof(str), "%Y-%m-%d %H:%M", &tm );
730 vlc_meta_AddExtra( p_meta, "Date", str );
731 free( psz_date );
732 psz_date = strdup( str );
734 /* display in minutes */
735 i_length = ( i_length + 59 ) / 60;
736 snprintf( str, sizeof(str), "%u:%02u", i_length / 60, i_length % 60 );
737 vlc_meta_AddExtra( p_meta, "Duration", str );
741 else if( tag == 'T' )
743 free( psz_title );
744 psz_title = strdup( text );
745 vlc_meta_AddExtra( p_meta, "Title", text );
748 else if( tag == 'S' )
750 free( psz_smalltext );
751 psz_smalltext = strdup( text );
752 vlc_meta_AddExtra( p_meta, "Info", text );
755 else if( tag == 'D' )
757 for( char *p = text; *p; ++p )
759 if( *p == '|' )
760 *p = '\n';
762 vlc_meta_SetDescription( p_meta, text );
765 /* FPS are required to convert between timestamps and frames */
766 else if( tag == 'F' )
768 float fps = atof( text );
769 if( fps >= 1 )
770 p_sys->fps = fps;
771 vlc_meta_AddExtra( p_meta, "Frame Rate", text );
774 else if( tag == 'P' )
776 vlc_meta_AddExtra( p_meta, "Priority", text );
779 else if( tag == 'L' )
781 vlc_meta_AddExtra( p_meta, "Lifetime", text );
785 /* create a meaningful title */
786 int i_len = 10 +
787 ( psz_title ? strlen( psz_title ) : 0 ) +
788 ( psz_smalltext ? strlen( psz_smalltext ) : 0 ) +
789 ( psz_date ? strlen( psz_date ) : 0 );
790 char *psz_display = malloc( i_len );
792 if( psz_display )
794 *psz_display = '\0';
795 if( psz_title )
796 strcat( psz_display, psz_title );
797 if( psz_title && psz_smalltext )
798 strcat( psz_display, " - " );
799 if( psz_smalltext )
800 strcat( psz_display, psz_smalltext );
801 if( ( psz_title || psz_smalltext ) && psz_date )
803 strcat( psz_display, " (" );
804 strcat( psz_display, psz_date );
805 strcat( psz_display, ")" );
807 if( *psz_display )
808 vlc_meta_SetTitle( p_meta, psz_display );
811 free( psz_display );
812 free( psz_title );
813 free( psz_smalltext );
814 free( psz_date );
816 fclose( infofile );
819 /*****************************************************************************
820 * Import cut marks and convert them to seekpoints (chapters).
821 *****************************************************************************/
822 static void ImportMarks( access_t *p_access )
824 access_sys_t *p_sys = p_access->p_sys;
826 FILE *marksfile = OpenRelativeFile( p_access, "marks" );
827 if( !marksfile )
828 return;
830 FILE *indexfile = OpenRelativeFile( p_access, "index" );
831 if( !indexfile )
833 fclose( marksfile );
834 return;
837 /* get the length of this recording (index stores 8 bytes per frame) */
838 struct stat st;
839 if( fstat( fileno( indexfile ), &st ) )
841 fclose( marksfile );
842 fclose( indexfile );
843 return;
845 int64_t i_frame_count = st.st_size / 8;
847 /* Put all cut marks in a "dummy" title */
848 input_title_t *p_marks = vlc_input_title_New();
849 if( !p_marks )
851 fclose( marksfile );
852 fclose( indexfile );
853 return;
855 p_marks->psz_name = strdup( _("VDR Cut Marks") );
857 /* offset for chapter positions */
858 int i_chapter_offset = p_sys->fps / 1000 *
859 var_InheritInteger( p_access, "vdr-chapter-offset" );
861 /* minimum chapter size in frames */
862 int i_min_chapter_size = p_sys->fps * MIN_CHAPTER_SIZE;
864 /* the last chapter started at this frame (init to 0 so
865 * we skip useless chapters near the beginning as well) */
866 int64_t i_prev_chapter = 0;
868 /* parse lines of the form "0:00:00.00 foobar" */
869 char *line = NULL;
870 size_t line_len;
871 while( ReadLine( &line, &line_len, marksfile ) )
873 int64_t i_frame = ParseFrameNumber( line, p_sys->fps );
875 /* skip chapters which are near the end or too close to each other */
876 if( i_frame - i_prev_chapter < i_min_chapter_size ||
877 i_frame >= i_frame_count - i_min_chapter_size )
878 continue;
879 i_prev_chapter = i_frame;
881 /* move chapters (simple workaround for inaccurate cut marks) */
882 if( i_frame > -i_chapter_offset )
883 i_frame += i_chapter_offset;
884 else
885 i_frame = 0;
887 uint64_t i_offset;
888 uint16_t i_file_number;
889 if( !ReadIndexRecord( indexfile, p_sys->b_ts_format,
890 i_frame, &i_offset, &i_file_number ) )
891 continue;
892 if( i_file_number < 1 || i_file_number > FILE_COUNT )
893 continue;
895 /* add file sizes to get the "global" offset */
896 seekpoint_t *sp = vlc_seekpoint_New();
897 if( !sp )
898 continue;
899 sp->i_time_offset = i_frame * (int64_t)( CLOCK_FREQ / p_sys->fps );
900 sp->i_byte_offset = i_offset;
901 for( int i = 0; i + 1 < i_file_number; ++i )
902 sp->i_byte_offset += FILE_SIZE( i );
903 sp->psz_name = strdup( line );
905 TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
908 /* add a chapter at the beginning if missing */
909 if( p_marks->i_seekpoint > 0 && p_marks->seekpoint[0]->i_byte_offset > 0 )
911 seekpoint_t *sp = vlc_seekpoint_New();
912 if( sp )
914 sp->i_byte_offset = 0;
915 sp->i_time_offset = 0;
916 sp->psz_name = strdup( _("Start") );
917 TAB_INSERT( p_marks->i_seekpoint, p_marks->seekpoint, sp, 0 );
921 if( p_marks->i_seekpoint > 0 )
922 p_sys->p_marks = p_marks;
923 else
924 vlc_input_title_Delete( p_marks );
926 fclose( marksfile );
927 fclose( indexfile );
930 /*****************************************************************************
931 * Lookup frame offset in index file
932 *****************************************************************************/
933 static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame,
934 uint64_t *pi_offset, uint16_t *pi_file_num )
936 uint8_t index_record[8];
937 if( fseek( p_file, sizeof(index_record) * i_frame, SEEK_SET ) != 0 )
938 return false;
939 if( fread( &index_record, sizeof(index_record), 1, p_file ) <= 0 )
940 return false;
942 /* VDR usually (only?) runs on little endian machines, but VLC has a
943 * broader audience. See recording.* in VDR source for data layout. */
944 if( b_ts )
946 uint64_t i_index_entry = GetQWLE( &index_record );
947 *pi_offset = i_index_entry & UINT64_C(0xFFFFFFFFFF);
948 *pi_file_num = i_index_entry >> 48;
950 else
952 *pi_offset = GetDWLE( &index_record );
953 *pi_file_num = index_record[5];
956 return true;
959 /*****************************************************************************
960 * Convert time stamp from file to frame number
961 *****************************************************************************/
962 static int64_t ParseFrameNumber( const char *psz_line, float fps )
964 unsigned h, m, s, f, n;
966 /* hour:min:sec.frame (frame is optional) */
967 n = sscanf( psz_line, "%u:%u:%u.%u", &h, &m, &s, &f );
968 if( n >= 3 )
970 if( n < 4 )
971 f = 1;
972 int64_t i_seconds = (int64_t)h * 3600 + (int64_t)m * 60 + s;
973 return (int64_t)( i_seconds * (double)fps ) + __MAX(1, f) - 1;
976 /* only a frame number */
977 int64_t i_frame = strtoll( psz_line, NULL, 10 );
978 return __MAX(1, i_frame) - 1;