From 647393a412f0e6822fe8ce3f16ddc6e0fffb5fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20G=C3=BCntner?= Date: Wed, 30 Jun 2010 01:36:13 +0200 Subject: [PATCH] Added support for VDR recordings --- modules/access/Modules.am | 2 + modules/access/vdr.c | 957 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 959 insertions(+), 0 deletions(-) create mode 100644 modules/access/vdr.c diff --git a/modules/access/Modules.am b/modules/access/Modules.am index 7aa0b99..fba6dac 100644 --- a/modules/access/Modules.am +++ b/modules/access/Modules.am @@ -50,6 +50,7 @@ SOURCES_access_sftp = sftp.c SOURCES_access_imem = imem.c SOURCES_access_avio = avio.c avio.h SOURCES_access_attachment = attachment.c +SOURCES_access_vdr = vdr.c SOURCES_access_rar = rar/rar.c rar/rar.h rar/access.c SOURCES_stream_filter_rar = rar/rar.c rar/rar.h rar/stream.c @@ -74,6 +75,7 @@ libvlc_LTLIBRARIES += \ libaccess_attachment_plugin.la \ libaccess_rar_plugin.la \ libstream_filter_rar_plugin.la \ + libaccess_vdr_plugin.la \ $(NULL) libaccess_alsa_plugin_la_SOURCES = alsa.c diff --git a/modules/access/vdr.c b/modules/access/vdr.c new file mode 100644 index 0000000..c83ef88 --- /dev/null +++ b/modules/access/vdr.c @@ -0,0 +1,957 @@ +/***************************************************************************** + * vdr.c: VDR recordings access plugin + ***************************************************************************** + * Copyright (C) 2010 Tobias Güntner + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/*** +VDR splits recordings into multiple files and stores each recording in a +separate directory. If VLC opens a normal directory, the filesystem module +will add all files to the playlist. If, however, VLC opens a VDR recording, +this module will join all files within that directory and provide a single +continuous stream instead. + +VDR recordings have either of two directory layouts: + 1) PES format: + /path/to/0000-00-00.00.00.00.00.rec/ + 001.vdr, 002.vdr, 003.vdr, ... + index.vdr, info.vdr, marks.vdr, ... + 2) TS format: + /path/to/0000-00-00.00.00.0.0.rec/ + 001.ts, 002.ts, 003.ts, ... + index, info, marks, ... +See http://www.vdr-wiki.de/ and http://www.tvdr.de/ for more information. +***/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#elif defined( WIN32 ) && !defined( UNDER_CE ) +# include +#endif + +#include +#include +#include + +#if defined( WIN32 ) && !defined( UNDER_CE ) +# ifdef lseek +# undef lseek +# endif +# define lseek _lseeki64 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int Open ( vlc_object_t * ); +static void Close( vlc_object_t * ); + +#define HELP_TEXT N_("Support for VDR recordings (http://www.tvdr.de/).") + +#define CACHING_TEXT N_("Caching value in ms") +#define CACHING_LONGTEXT N_( \ + "Caching value for files. This value should be set in milliseconds." ) + +#define CHAPTER_OFFSET_TEXT N_("Chapter offset in ms") +#define CHAPTER_OFFSET_LONGTEXT N_( \ + "Move all chapters. This value should be set in milliseconds." ) + +#define FPS_TEXT N_("Frame rate") +#define FPS_LONGTEXT N_( \ + "Default frame rate for chapter import." ) + +vlc_module_begin () + set_category( CAT_INPUT ) + set_shortname( N_("VDR") ) + set_help( HELP_TEXT ) + set_subcategory( SUBCAT_INPUT_ACCESS ) + set_description( N_("VDR recordings") ) + add_integer( "vdr-caching", 5 * DEFAULT_PTS_DELAY / 1000, NULL, + CACHING_TEXT, CACHING_LONGTEXT, true ) + add_integer( "vdr-chapter-offset", 0, NULL, + CHAPTER_OFFSET_TEXT, CHAPTER_OFFSET_LONGTEXT, true ) + add_float_with_range( "vdr-fps", 25, 1, 1000, NULL, + FPS_TEXT, FPS_LONGTEXT, true ) + set_capability( "access", 60 ) + add_shortcut( "vdr" ) + add_shortcut( "directory" ) + add_shortcut( "dir" ) + add_shortcut( "file" ) + set_callbacks( Open, Close ) +vlc_module_end () + +/***************************************************************************** + * Local prototypes, constants, structures + *****************************************************************************/ + +TYPEDEF_ARRAY( uint64_t, size_array_t ); + +struct access_sys_t +{ + /* file sizes of all parts */ + size_array_t file_sizes; + + /* index and fd of current open file */ + unsigned i_current_file; + int fd; + + /* meta data */ + vlc_meta_t *p_meta; + + /* cut marks */ + input_title_t *p_marks; + float fps; + + /* file format: true=TS, false=PES */ + bool b_ts_format; +}; + +#define CURRENT_FILE_SIZE ARRAY_VAL(p_sys->file_sizes, p_sys->i_current_file) +#define FILE_SIZE(pos) ARRAY_VAL(p_sys->file_sizes, pos) +#define FILE_COUNT (unsigned)p_sys->file_sizes.i_size + +static int Control( access_t *, int, va_list ); +static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len ); +static int Seek( access_t *p_access, uint64_t i_pos); +static void FindSeekpoint( access_t *p_access ); +static bool ScanDirectory( access_t *p_access, bool b_strict ); +static char *GetFilePath( access_t *p_access, unsigned i_file ); +static bool ImportNextFile( access_t *p_access ); +static bool SwitchFile( access_t *p_access, unsigned i_file ); +static void OptimizeForRead( int fd ); +static void UpdateFileSize( access_t *p_access ); +static int StatRelativeFile( access_t *p_access, const char *psz_file, + struct stat *p_stat ); +static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file ); +static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file ); +static void ImportMeta( access_t *p_access ); +static void ImportMarks( access_t *p_access ); +static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame, + uint64_t *pi_offset, uint16_t *pi_file_num ); +static int64_t ParseFrameNumber( const char *psz_line, float fps ); + +/***************************************************************************** + * Open a directory + *****************************************************************************/ +static int Open( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t*)p_this; + + /* Ensure path points to a directory */ + struct stat st; + if( !p_access->psz_filepath || + vlc_stat( p_access->psz_filepath, &st ) || + !S_ISDIR( st.st_mode ) ) + return VLC_EGENERIC; + + /* Some tests can be skipped if this module was explicitly requested. + * That way, the user can play "corrupt" recordings if necessary + * and we can avoid false positives in the general case. */ + bool b_strict = strcmp( p_access->psz_access, "vdr" ); + + /* Do a quick test based on the directory extension to see if this + * directory might contain a VDR recording. We can be reasonably + * sure if ScanDirectory() actually finds files. */ + if( b_strict ) + { + const char *psz_ext = strrchr( p_access->psz_filepath, '.' ); + if( !psz_ext || strcasecmp( psz_ext, ".rec" ) ) + return VLC_EGENERIC; + } + + access_sys_t *p_sys; + STANDARD_READ_ACCESS_INIT; + p_sys->fd = -1; + p_sys->fps = var_InheritFloat( p_access, "vdr-fps" ); + ARRAY_INIT( p_sys->file_sizes ); + + /* Import all files and prepare playback. */ + if( !ScanDirectory( p_access, b_strict ) || + !SwitchFile( p_access, 0 ) ) + { + Close( p_this ); + return VLC_EGENERIC; + } + + return VLC_SUCCESS; +} + +/***************************************************************************** + * Close files and free resources + *****************************************************************************/ +static void Close( vlc_object_t * p_this ) +{ + access_t *p_access = (access_t*)p_this; + access_sys_t *p_sys = p_access->p_sys; + + if( p_sys->fd != -1 ) + close( p_sys->fd ); + ARRAY_RESET( p_sys->file_sizes ); + + if( p_sys->p_meta ) + vlc_meta_Delete( p_sys->p_meta ); + + vlc_input_title_Delete( p_sys->p_marks ); + free( p_sys ); +} + +/***************************************************************************** + * Determine format and import files + *****************************************************************************/ +static bool ScanDirectory( access_t *p_access, bool b_strict ) +{ + access_sys_t *p_sys = p_access->p_sys; + + /* find first part and determine directory format */ + p_sys->b_ts_format = true; + if( !ImportNextFile( p_access ) ) + { + p_sys->b_ts_format = !p_sys->b_ts_format; + if( !ImportNextFile( p_access ) ) + return false; + } + + /* meta data and index should exist */ + if( b_strict ) + { + struct stat st; + if( StatRelativeFile( p_access, "info", &st ) || + StatRelativeFile( p_access, "index", &st ) ) + return false; + } + + /* get all remaining parts */ + while( ImportNextFile( p_access ) ) + continue; + + /* import meta data etc. */ + ImportMeta( p_access ); + + /* cut marks depend on meta data and file sizes */ + ImportMarks( p_access ); + + return true; +} + +/***************************************************************************** + * Control input stream + *****************************************************************************/ +static int Control( access_t *p_access, int i_query, va_list args ) +{ + access_sys_t *p_sys = p_access->p_sys; + input_title_t ***ppp_title; + int i; + int64_t *pi64; + vlc_meta_t *p_meta; + + switch( i_query ) + { + case ACCESS_CAN_SEEK: + case ACCESS_CAN_PAUSE: + case ACCESS_CAN_CONTROL_PACE: + *va_arg( args, bool* ) = true; + break; + + case ACCESS_CAN_FASTSEEK: + /* Seek() can open files, so it might be "too slow" */ + *va_arg( args, bool* ) = false; + break; + + case ACCESS_GET_PTS_DELAY: + pi64 = va_arg( args, int64_t * ); + *pi64 = var_InheritInteger( p_access, "vdr-caching" ) * INT64_C(1000); + break; + + case ACCESS_SET_PAUSE_STATE: + /* nothing to do */ + break; + + case ACCESS_GET_TITLE_INFO: + /* return a copy of our seek points */ + if( !p_sys->p_marks ) + return VLC_EGENERIC; + ppp_title = va_arg( args, input_title_t*** ); + *va_arg( args, int* ) = 1; + *ppp_title = malloc( sizeof( input_title_t** ) ); + if( !*ppp_title ) + return VLC_ENOMEM; + **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks ); + break; + + case ACCESS_SET_TITLE: + /* ignore - only one title */ + break; + + case ACCESS_SET_SEEKPOINT: + i = va_arg( args, int ); + /* Seek updates p_access->info */ + return Seek( p_access, p_sys->p_marks->seekpoint[i]->i_byte_offset ); + + case ACCESS_GET_META: + if( !p_sys->p_meta ) + return VLC_EGENERIC; + p_meta = va_arg( args, vlc_meta_t* ); + vlc_meta_Merge( p_meta, p_sys->p_meta ); + break; + + case ACCESS_SET_PRIVATE_ID_STATE: + case ACCESS_GET_CONTENT_TYPE: + return VLC_EGENERIC; + + default: + msg_Warn( p_access, "unimplemented query in control" ); + return VLC_EGENERIC; + } + return VLC_SUCCESS; +} + +/***************************************************************************** + * Read and concatenate files + *****************************************************************************/ +static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len ) +{ + access_sys_t *p_sys = p_access->p_sys; + + if( p_sys->fd == -1 ) + { + /* no more data */ + p_access->info.b_eof = true; + return 0; + } + + ssize_t i_ret = read( p_sys->fd, p_buffer, i_len ); + + if( i_ret > 0 ) + { + /* success */ + p_access->info.i_pos += i_ret; + UpdateFileSize( p_access ); + FindSeekpoint( p_access ); + return i_ret; + } + else if( i_ret == 0 ) + { + /* check for new files in case the recording is still active */ + if( p_sys->i_current_file >= FILE_COUNT - 1 ) + ImportNextFile( p_access ); + /* play next file */ + SwitchFile( p_access, p_sys->i_current_file + 1 ); + return -1; + } + else if( errno == EINTR ) + { + /* try again later */ + return -1; + } + else + { + /* abort on read error */ + msg_Err( p_access, "failed to read (%m)" ); + dialog_Fatal( p_access, _("File reading failed"), "%s", + _("VLC could not read the file.") ); + SwitchFile( p_access, -1 ); + return 0; + } +} + +/***************************************************************************** + * Seek to a specific location in a file + *****************************************************************************/ +static int Seek( access_t *p_access, uint64_t i_pos ) +{ + access_sys_t *p_sys = p_access->p_sys; + + /* might happen if called by ACCESS_SET_SEEKPOINT */ + i_pos = __MIN( i_pos, p_access->info.i_size ); + + p_access->info.i_pos = i_pos; + p_access->info.b_eof = false; + + /* find correct chapter */ + FindSeekpoint( p_access ); + + /* find correct file */ + unsigned i_file = 0; + while( i_pos >= FILE_SIZE( i_file ) && + i_file < FILE_COUNT - 1 ) + { + i_pos -= FILE_SIZE( i_file ); + i_file++; + } + if( !SwitchFile( p_access, i_file ) ) + return VLC_EGENERIC; + + /* adjust position within that file */ + return lseek( p_sys->fd, i_pos, SEEK_SET ) != -1 ? + VLC_SUCCESS : VLC_EGENERIC; +} + +/***************************************************************************** + * Change the chapter index to match the current position + *****************************************************************************/ +static void FindSeekpoint( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + if( !p_sys->p_marks ) + return; + + int i_new_seekpoint = p_access->info.i_seekpoint; + if( p_access->info.i_pos < (uint64_t)p_sys->p_marks-> + seekpoint[ p_access->info.i_seekpoint ]->i_byte_offset ) + { + /* i_pos moved backwards, start fresh */ + i_new_seekpoint = 0; + } + + /* only need to check the following seekpoints */ + while( i_new_seekpoint + 1 < p_sys->p_marks->i_seekpoint && + p_access->info.i_pos >= (uint64_t)p_sys->p_marks-> + seekpoint[ i_new_seekpoint + 1 ]->i_byte_offset ) + { + i_new_seekpoint++; + } + + /* avoid unnecessary events */ + if( p_access->info.i_seekpoint != i_new_seekpoint ) + { + p_access->info.i_seekpoint = i_new_seekpoint; + p_access->info.i_update |= INPUT_UPDATE_SEEKPOINT; + } +} + +/***************************************************************************** + * Returns the path of a certain part + *****************************************************************************/ +static char *GetFilePath( access_t *p_access, unsigned i_file ) +{ + char *psz_path; + if( asprintf( &psz_path, p_access->p_sys->b_ts_format ? + "%s" DIR_SEP "%05u.ts" : "%s" DIR_SEP "%03u.vdr", + p_access->psz_filepath, i_file + 1 ) == -1 ) + return NULL; + else + return psz_path; +} + +/***************************************************************************** + * Check if another part exists and import it + *****************************************************************************/ +static bool ImportNextFile( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + + char *psz_path = GetFilePath( p_access, FILE_COUNT ); + if( !psz_path ) + return false; + + struct stat st; + if( vlc_stat( psz_path, &st ) ) + { + msg_Dbg( p_access, "could not stat %s: %m", psz_path ); + free( psz_path ); + return false; + } + if( !S_ISREG( st.st_mode ) ) + { + msg_Dbg( p_access, "%s is not a regular file", psz_path ); + free( psz_path ); + return false; + } + msg_Dbg( p_access, "%s exists", psz_path ); + free( psz_path ); + + ARRAY_APPEND( p_sys->file_sizes, st.st_size ); + p_access->info.i_size += st.st_size; + p_access->info.i_update |= INPUT_UPDATE_SIZE; + + return true; +} + +/***************************************************************************** + * Close the current file and open another + *****************************************************************************/ +static bool SwitchFile( access_t *p_access, unsigned i_file ) +{ + access_sys_t *p_sys = p_access->p_sys; + + /* requested file already open? */ + if( p_sys->fd != -1 && p_sys->i_current_file == i_file ) + return true; + + /* close old file */ + if( p_sys->fd != -1 ) + { + close( p_sys->fd ); + p_sys->fd = -1; + } + + /* switch */ + if( i_file >= FILE_COUNT ) + return false; + p_sys->i_current_file = i_file; + + /* open new file */ + char *psz_path = GetFilePath( p_access, i_file ); + if( !psz_path ) + return false; + p_sys->fd = vlc_open( psz_path, O_RDONLY ); + + if( p_sys->fd == -1 ) + { + msg_Err( p_access, "Failed to open %s: %m", psz_path ); + goto error; + } + + /* cannot handle anything except normal files */ + struct stat st; + if( fstat( p_sys->fd, &st ) || !S_ISREG( st.st_mode ) ) + { + msg_Err( p_access, "%s is not a regular file", psz_path ); + goto error; + } + + OptimizeForRead( p_sys->fd ); + + msg_Dbg( p_access, "opened %s", psz_path ); + free( psz_path ); + return true; + +error: + dialog_Fatal (p_access, _("File reading failed"), _("VLC could not" + " open the file \"%s\"."), psz_path); + if( p_sys->fd != -1 ) + { + close( p_sys->fd ); + p_sys->fd = -1; + } + free( psz_path ); + return false; +} + +/***************************************************************************** + * Some tweaks to speed up read() + *****************************************************************************/ +static void OptimizeForRead( int fd ) +{ + /* cf. Open() in file access module */ + VLC_UNUSED(fd); +#ifdef HAVE_POSIX_FADVISE + posix_fadvise( fd, 0, 4096, POSIX_FADV_WILLNEED ); + posix_fadvise( fd, 0, 0, POSIX_FADV_NOREUSE ); +#endif +#ifdef HAVE_FCNTL +#ifdef F_RDAHEAD + fcntl( fd, F_RDAHEAD, 1 ); +#endif +#ifdef F_NOCACHE + fcntl( fd, F_NOCACHE, 1 ); +#endif +#endif +} + +/***************************************************************************** + * Fix size if the (last) part is still growing + *****************************************************************************/ +static void UpdateFileSize( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + struct stat st; + + if( p_access->info.i_size >= p_access->info.i_pos ) + return; + + /* TODO: not sure if this can happen or what to do in this case */ + if( fstat( p_sys->fd, &st ) ) + return; + if( (uint64_t)st.st_size <= CURRENT_FILE_SIZE ) + return; + + p_access->info.i_size -= CURRENT_FILE_SIZE; + CURRENT_FILE_SIZE = st.st_size; + p_access->info.i_size += CURRENT_FILE_SIZE; + p_access->info.i_update |= INPUT_UPDATE_SIZE; +} + +/***************************************************************************** + * Stat file relative to base directory + *****************************************************************************/ +static int StatRelativeFile( access_t *p_access, const char *psz_file, + struct stat *p_stat ) +{ + /* build path and add extension */ + char *psz_path; + if( asprintf( &psz_path, "%s" DIR_SEP "%s%s", + p_access->psz_filepath, psz_file, + p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 ) + return -1; + + int ret = vlc_stat( psz_path, p_stat ); + if( ret ) + msg_Dbg( p_access, "could not stat %s: %m", psz_path ); + free( psz_path ); + + return ret; +} + +/***************************************************************************** + * Open file relative to base directory for reading. + *****************************************************************************/ +static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file ) +{ + /* build path and add extension */ + char *psz_path; + if( asprintf( &psz_path, "%s" DIR_SEP "%s%s", + p_access->psz_filepath, psz_file, + p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 ) + return NULL; + + FILE *file = vlc_fopen( psz_path, "rb" ); + if( !file ) + msg_Warn( p_access, "Failed to open %s: %m", psz_path ); + free( psz_path ); + + return file; +} + +/***************************************************************************** + * Read a line of text. Returns false on error or EOF. + *****************************************************************************/ +static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file ) +{ + ssize_t read = getline( ppsz_line, pi_size, p_file ); + + if( read == -1 ) + { + /* automatically free buffer on eof */ + free( *ppsz_line ); + *ppsz_line = NULL; + return false; + } + + if( read > 0 && (*ppsz_line)[ read - 1 ] == '\n' ) + (*ppsz_line)[ read - 1 ] = '\0'; + EnsureUTF8( *ppsz_line ); + + return true; +} + +/***************************************************************************** + * Import meta data + *****************************************************************************/ +static void ImportMeta( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + + FILE *infofile = OpenRelativeFile( p_access, "info" ); + if( !infofile ) + return; + + vlc_meta_t *p_meta = vlc_meta_New(); + p_sys->p_meta = p_meta; + if( !p_meta ) + { + fclose( infofile ); + return; + } + + char *line = NULL; + size_t line_len; + char *psz_title = NULL, *psz_smalltext = NULL, *psz_date = NULL; + + while( ReadLine( &line, &line_len, infofile ) ) + { + if( !isalpha( (unsigned char)line[0] ) || line[1] != ' ' ) + continue; + + char tag = line[0]; + char *text = line + 2; + + if( tag == 'C' ) + { + char *psz_name = strchr( text, ' ' ); + if( psz_name ) + { + *psz_name = '\0'; + vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 ); + } + vlc_meta_AddExtra( p_meta, "Transponder", text ); + } + + else if( tag == 'E' ) + { + unsigned i_id, i_start, i_length; + if( sscanf( text, "%u %u %u", &i_id, &i_start, &i_length ) == 3 ) + { + char str[50]; + struct tm tm; + time_t start = i_start; + localtime_r( &start, &tm ); + + /* TODO: locale */ + strftime( str, sizeof(str), "%Y-%m-%d %H:%M", &tm ); + vlc_meta_AddExtra( p_meta, "Date", str ); + free( psz_date ); + psz_date = strdup( str ); + + /* display in minutes */ + i_length = ( i_length + 59 ) / 60; + snprintf( str, sizeof(str), "%u:%02u", i_length / 60, i_length % 60 ); + vlc_meta_AddExtra( p_meta, "Duration", str ); + } + } + + else if( tag == 'T' ) + { + free( psz_title ); + psz_title = strdup( text ); + vlc_meta_AddExtra( p_meta, "Title", text ); + } + + else if( tag == 'S' ) + { + free( psz_smalltext ); + psz_smalltext = strdup( text ); + vlc_meta_AddExtra( p_meta, "Info", text ); + } + + else if( tag == 'D' ) + { + for( char *p = text; *p; ++p ) + { + if( *p == '|' ) + *p = '\n'; + } + vlc_meta_SetDescription( p_meta, text ); + } + + /* FPS are required to convert between timestamps and frames */ + else if( tag == 'F' ) + { + float fps = atof( text ); + if( fps >= 1 ) + p_sys->fps = fps; + vlc_meta_AddExtra( p_meta, "Frame Rate", text ); + } + + else if( tag == 'P' ) + { + vlc_meta_AddExtra( p_meta, "Priority", text ); + } + + else if( tag == 'L' ) + { + vlc_meta_AddExtra( p_meta, "Lifetime", text ); + } + } + + /* create a meaningful title */ + int i_len = 10 + + ( psz_title ? strlen( psz_title ) : 0 ) + + ( psz_smalltext ? strlen( psz_smalltext ) : 0 ) + + ( psz_date ? strlen( psz_date ) : 0 ); + char *psz_display = malloc( i_len ); + + if( psz_display ) + { + *psz_display = '\0'; + if( psz_title ) + strcat( psz_display, psz_title ); + if( psz_title && psz_smalltext ) + strcat( psz_display, " - " ); + if( psz_smalltext ) + strcat( psz_display, psz_smalltext ); + if( ( psz_title || psz_smalltext ) && psz_date ) + { + strcat( psz_display, " (" ); + strcat( psz_display, psz_date ); + strcat( psz_display, ")" ); + } + if( *psz_display ) + vlc_meta_SetTitle( p_meta, psz_display ); + } + + free( psz_display ); + free( psz_title ); + free( psz_smalltext ); + free( psz_date ); + + fclose( infofile ); +} + +/***************************************************************************** + * Import cut marks and convert them to seekpoints (chapters). + *****************************************************************************/ +static void ImportMarks( access_t *p_access ) +{ + access_sys_t *p_sys = p_access->p_sys; + + FILE *marksfile = OpenRelativeFile( p_access, "marks" ); + if( !marksfile ) + return; + + FILE *indexfile = OpenRelativeFile( p_access, "index" ); + if( !indexfile ) + { + fclose( marksfile ); + return; + } + + /* Put all cut marks in a "dummy" title */ + input_title_t *p_marks = vlc_input_title_New(); + if( !p_marks ) + { + fclose( marksfile ); + fclose( indexfile ); + return; + } + p_marks->psz_name = strdup( _("VDR Cut Marks") ); + + /* offset for chapter positions */ + int i_chapter_offset = p_sys->fps / 1000 * + var_InheritInteger( p_access, "vdr-chapter-offset" ); + + /* parse lines of the form "0:00:00.00 foobar" */ + char *line = NULL; + size_t line_len; + while( ReadLine( &line, &line_len, marksfile ) ) + { + int64_t i_frame = ParseFrameNumber( line, p_sys->fps ); + + /* move chapters (simple workaround for inaccurate cut marks) */ + if( i_frame > -i_chapter_offset ) + i_frame += i_chapter_offset; + else + i_frame = 0; + + uint64_t i_offset; + uint16_t i_file_number; + if( !ReadIndexRecord( indexfile, p_sys->b_ts_format, + i_frame, &i_offset, &i_file_number ) ) + continue; + if( i_file_number < 1 || i_file_number > FILE_COUNT ) + continue; + + /* add file sizes to get the "global" offset */ + seekpoint_t *sp = vlc_seekpoint_New(); + if( !sp ) + continue; + sp->i_time_offset = i_frame * (int64_t)( CLOCK_FREQ / p_sys->fps ); + sp->i_byte_offset = i_offset; + for( int i = 0; i + 1 < i_file_number; ++i ) + sp->i_byte_offset += FILE_SIZE( i ); + sp->psz_name = strdup( line ); + + TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp ); + } + + if( p_marks->i_seekpoint > 0 ) + { + seekpoint_t *sp = vlc_seekpoint_New(); + if( sp ) + { + sp->i_byte_offset = 0; + sp->i_time_offset = 0; + sp->psz_name = strdup( _("Start") ); + TAB_INSERT( p_marks->i_seekpoint, p_marks->seekpoint, sp, 0 ); + } + p_sys->p_marks = p_marks; + } + else + { + vlc_input_title_Delete( p_marks ); + } + + fclose( marksfile ); + fclose( indexfile ); +} + +/***************************************************************************** + * Lookup frame offset in index file + *****************************************************************************/ +static bool ReadIndexRecord( FILE *p_file, bool b_ts, int64_t i_frame, + uint64_t *pi_offset, uint16_t *pi_file_num ) +{ + uint8_t index_record[8]; + if( fseek( p_file, sizeof(index_record) * i_frame, SEEK_SET ) != 0 ) + return false; + if( fread( &index_record, sizeof(index_record), 1, p_file ) <= 0 ) + return false; + + /* VDR usually (only?) runs on little endian machines, but VLC has a + * broader audience. See recording.* in VDR source for data layout. */ + if( b_ts ) + { + uint64_t i_index_entry = GetQWLE( &index_record ); + *pi_offset = i_index_entry & UINT64_C(0xFFFFFFFFFF); + *pi_file_num = i_index_entry >> 48; + } + else + { + *pi_offset = GetDWLE( &index_record ); + *pi_file_num = index_record[5]; + } + + return true; +} + +/***************************************************************************** + * Convert time stamp from file to frame number + *****************************************************************************/ +static int64_t ParseFrameNumber( const char *psz_line, float fps ) +{ + unsigned h, m, s, f, n; + + /* hour:min:sec.frame (frame is optional) */ + n = sscanf( psz_line, "%u:%u:%u.%u", &h, &m, &s, &f ); + if( n >= 3 ) + { + if( n < 4 ) + f = 1; + int64_t i_seconds = (int64_t)h * 3600 + (int64_t)m * 60 + s; + return (int64_t)( i_seconds * (double)fps ) + __MAX(1, f) - 1; + } + + /* only a frame number */ + int64_t i_frame = strtoll( psz_line, NULL, 10 ); + return __MAX(1, i_frame) - 1; +} -- 1.6.5.1.1367.gcd48