Commit 59045c47 authored by Tobias Güntner's avatar Tobias Güntner Committed by Jean-Baptiste Kempf

Added support for VDR recordings

Signed-off-by: default avatarJean-Baptiste Kempf <jb@videolan.org>
parent a3ff19cd
...@@ -12,6 +12,7 @@ Access ...@@ -12,6 +12,7 @@ Access
* BDA: experimental support for ClearQam devices * BDA: experimental support for ClearQam devices
* DVB-S scanning support * DVB-S scanning support
* DShow: support for freq and video standard selection * DShow: support for freq and video standard selection
* Support for VDR recordings
Codecs Codecs
* You can now use ffmpeg-mt in conjunction with vlc * You can now use ffmpeg-mt in conjunction with vlc
......
...@@ -34,6 +34,7 @@ $Id$ ...@@ -34,6 +34,7 @@ $Id$
* access_smb: SMB shares access module * access_smb: SMB shares access module
* access_tcp: TCP Network access module * access_tcp: TCP Network access module
* access_udp: UDP Network access module * access_udp: UDP Network access module
* access_vdr: VDR access module
* adjust: Contrast/Hue/saturation/Brightness adjust module * adjust: Contrast/Hue/saturation/Brightness adjust module
* adpcm: ADPCM audio decoder * adpcm: ADPCM audio decoder
* aes3: aes3 decoder/packetizer * aes3: aes3 decoder/packetizer
......
...@@ -50,6 +50,7 @@ SOURCES_access_sftp = sftp.c ...@@ -50,6 +50,7 @@ SOURCES_access_sftp = sftp.c
SOURCES_access_imem = imem.c SOURCES_access_imem = imem.c
SOURCES_access_avio = avio.c avio.h SOURCES_access_avio = avio.c avio.h
SOURCES_access_attachment = attachment.c SOURCES_access_attachment = attachment.c
SOURCES_access_vdr = vdr.c
SOURCES_access_rar = rar/rar.c rar/rar.h rar/access.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 SOURCES_stream_filter_rar = rar/rar.c rar/rar.h rar/stream.c
...@@ -74,6 +75,7 @@ libvlc_LTLIBRARIES += \ ...@@ -74,6 +75,7 @@ libvlc_LTLIBRARIES += \
libaccess_attachment_plugin.la \ libaccess_attachment_plugin.la \
libaccess_rar_plugin.la \ libaccess_rar_plugin.la \
libstream_filter_rar_plugin.la \ libstream_filter_rar_plugin.la \
libaccess_vdr_plugin.la \
$(NULL) $(NULL)
libaccess_alsa_plugin_la_SOURCES = alsa.c libaccess_alsa_plugin_la_SOURCES = alsa.c
......
/*****************************************************************************
* 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 <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#elif defined( WIN32 ) && !defined( UNDER_CE )
# include <io.h>
#endif
#include <ctype.h>
#include <time.h>
#include <errno.h>
#if defined( WIN32 ) && !defined( UNDER_CE )
# undef lseek
# define lseek _lseeki64
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_access.h>
#include <vlc_input.h>
#include <vlc_fs.h>
#include <vlc_charset.h>
#include <vlc_dialog.h>
#include <vlc_configuration.h>
/*****************************************************************************
* 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;
if( !p_access->psz_filepath )
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;
}
/* Only directories can be recordings */
struct stat st;
if( vlc_stat( p_access->psz_filepath, &st ) ||
!S_ISDIR( st.st_mode ) )
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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment