[vlc-devel] [PATCH] Added support for VDR recordings.

Jean-Baptiste Kempf jb at videolan.org
Sat Apr 24 17:32:32 CEST 2010


Ping ? Review ?

On Sat, Apr 17, 2010 at 03:14:18PM +0200, Tobias Güntner wrote :
> Hello!
>
> Third attempt. O_NONBLOCK has been removed and Open() does more tests.
>
> Regards,
> Tobias Güntner

> From e3fee79c80cc63c208a13fb39a8f4fd57b06b181 Mon Sep 17 00:00:00 2001
> From: =?UTF-8?q?Tobias=20G=C3=BCntner?= <fatbull at web.de>
> Date: Sat, 17 Apr 2010 14:30:16 +0200
> Subject: [PATCH] Added support for VDR recordings.
> 
> ---
>  modules/access/Modules.am |    2 +
>  modules/access/vdr.c      |  937 +++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 939 insertions(+), 0 deletions(-)
>  create mode 100644 modules/access/vdr.c
> 
> diff --git a/modules/access/Modules.am b/modules/access/Modules.am
> index 8cf53ea..bee75d6 100644
> --- a/modules/access/Modules.am
> +++ b/modules/access/Modules.am
> @@ -53,6 +53,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
>  
>  libaccess_rtmp_plugin_la_SOURCES = \
>          rtmp/access.c \
> @@ -73,6 +74,7 @@ libvlc_LTLIBRARIES += \
>  	libaccess_fake_plugin.la \
>  	libaccess_imem_plugin.la \
>  	libaccess_attachment_plugin.la \
> +	libaccess_vdr_plugin.la \
>  	$(NULL)
>  
>  libxcb_screen_plugin_la_SOURCES = screen/xcb.c
> diff --git a/modules/access/vdr.c b/modules/access/vdr.c
> new file mode 100644
> index 0000000..641724a
> --- /dev/null
> +++ b/modules/access/vdr.c
> @@ -0,0 +1,937 @@
> +/*****************************************************************************
> + * 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 video 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 )
> +#   ifdef lseek
> +#      undef lseek
> +#   endif
> +#   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 video files */
> +    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 ImportNextVideo( access_t *p_access );
> +static bool SwitchVideoFile( access_t *p_access, unsigned i_file );
> +static void UpdateVideoFileSize( 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,
> +                              bool b_text_mode );
> +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: open the directory
> + *****************************************************************************/
> +static int Open( vlc_object_t *p_this )
> +{
> +    access_t *p_access = (access_t*)p_this;
> +    access_sys_t *p_sys;
> +
> +    if( !p_access->psz_path )
> +        return VLC_EGENERIC;
> +
> +    if( !strcmp( p_access->psz_path, "-" ) )
> +        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 = !p_access->psz_access ||
> +        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 video files. */
> +    if( b_strict )
> +    {
> +        const char *psz_ext = strrchr( p_access->psz_path, '.' );
> +        if( !psz_ext || strcasecmp( psz_ext, ".rec" ) )
> +            return VLC_EGENERIC;
> +    }
> +
> +    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 ) ||
> +        !SwitchVideoFile( p_access, 0 ) )
> +    {
> +        Close( p_this );
> +        return VLC_EGENERIC;
> +    }
> +
> +    return VLC_SUCCESS;
> +}
> +
> +/*****************************************************************************
> + * Close: close the target
> + *****************************************************************************/
> +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 );
> +}
> +
> +/*****************************************************************************
> + * ScanDirectory: 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 video file and determine directory format */
> +    p_sys->b_ts_format = true;
> +    if( !ImportNextVideo( p_access ) )
> +    {
> +        p_sys->b_ts_format = !p_sys->b_ts_format;
> +        if( !ImportNextVideo( 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 video files */
> +    while( ImportNextVideo( 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:
> + *****************************************************************************/
> +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_FASTSEEK:
> +        case ACCESS_CAN_PAUSE:
> +        case ACCESS_CAN_CONTROL_PACE:
> +            *va_arg( args, bool* ) = true;
> +            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: concatenate all 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;
> +
> +    while( p_sys->fd != -1 )
> +    {
> +        ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );
> +
> +        if( i_ret > 0 )
> +        {
> +            /* success */
> +            p_access->info.i_pos += i_ret;
> +            UpdateVideoFileSize( 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 )
> +                ImportNextVideo( p_access );
> +            /* play next file or stop on "real" eof */
> +            if( SwitchVideoFile( p_access, p_sys->i_current_file + 1 ) )
> +                continue;
> +            else
> +                break;
> +        }
> +        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.") );
> +            break;
> +        }
> +    }
> +
> +    /* stop further reading */
> +    p_access->info.b_eof = true;
> +    return 0;
> +}
> +
> +/*****************************************************************************
> + * Seek: 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( !SwitchVideoFile( 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;
> +}
> +
> +/*****************************************************************************
> + * FindSeekpoint: Changes the chapter index if necessary
> + *****************************************************************************/
> +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;
> +    }
> +}
> +
> +/*****************************************************************************
> + * GetFilePath: Returns the path to 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_path, i_file + 1 ) == -1 )
> +        return NULL;
> +    else
> +        return psz_path;
> +}
> +
> +/*****************************************************************************
> + * ImportNextVideo: Find another video file
> + *****************************************************************************/
> +static bool ImportNextVideo( 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;
> +}
> +
> +/*****************************************************************************
> + * SwitchVideoFile: Close the current file and open another
> + *****************************************************************************/
> +static bool SwitchVideoFile( 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 );
> +        dialog_Fatal (p_access, _("File reading failed"), _("VLC could not"
> +            " open the file \"%s\"."), psz_path);
> +        free( psz_path );
> +        return false;
> +    }
> +
> +    /* 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 );
> +        dialog_Fatal (p_access, _("File reading failed"), _("VLC could not"
> +            " open the file \"%s\"."), psz_path);
> +        free( psz_path );
> +        close( p_sys->fd );
> +        p_sys->fd = -1;
> +        return false;
> +    }
> +
> +    msg_Dbg( p_access, "opened %s", psz_path );
> +    free( psz_path );
> +
> +    /* some tweaks */
> +#ifdef HAVE_FCNTL
> +#ifdef F_RDAHEAD
> +    fcntl( p_sys->fd, F_RDAHEAD, 1 ); /* enable readahead */
> +#endif
> +#ifdef F_NOCACHE
> +    fcntl( p_sys->fd, F_NOCACHE, 1 ); /* disable caching */
> +#endif
> +#endif
> +
> +    return true;
> +}
> +
> +/*****************************************************************************
> + * UpdateVideoFileSize: Fix size if the (last) video file is still growing
> + *****************************************************************************/
> +static void UpdateVideoFileSize( 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;
> +}
> +
> +/*****************************************************************************
> + * StatRelativeFile: 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_path, 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;
> +}
> +
> +/*****************************************************************************
> + * OpenRelativeFile: Open file relative to base directory for reading.
> + *****************************************************************************/
> +static FILE *OpenRelativeFile( access_t *p_access, const char *psz_file,
> +                              bool b_text_mode )
> +{
> +    /* build path and add extension */
> +    char *psz_path;
> +    if( asprintf( &psz_path, "%s" DIR_SEP "%s%s",
> +        p_access->psz_path, psz_file,
> +        p_access->p_sys->b_ts_format ? "" : ".vdr" ) == -1 )
> +        return NULL;
> +
> +    FILE *file = vlc_fopen( psz_path, b_text_mode ? "rt" : "rb" );
> +    if( !file )
> +        msg_Warn( p_access, "Failed to open %s: %m", psz_path );
> +    free( psz_path );
> +
> +    return file;
> +}
> +
> +/*****************************************************************************
> + * ReadLine: 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;
> +}
> +
> +/*****************************************************************************
> + * ImportMeta: 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", true );
> +    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;
> +
> +        if( line[0] == 'C' )
> +        {
> +            char *psz_name = strchr( line + 2, ' ' );
> +            if( psz_name )
> +            {
> +                *psz_name = '\0';
> +                vlc_meta_AddExtra( p_meta, "Channel", psz_name + 1 );
> +            }
> +            vlc_meta_AddExtra( p_meta, "Transponder", line + 2 );
> +        }
> +
> +        if( line[0] == 'E' )
> +        {
> +            unsigned long i_id, i_start, i_length;
> +            if( sscanf( line + 2, "%lu %lu %lu", &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, 50, "%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;
> +                sprintf( str, "%u:%02u", i_length / 60, i_length % 60 );
> +                vlc_meta_AddExtra( p_meta, "Duration", str );
> +            }
> +        }
> +
> +        if( line[0] == 'T' )
> +        {
> +            free( psz_title );
> +            psz_title = strdup( line + 2 );
> +            vlc_meta_AddExtra( p_meta, "Title", line + 2 );
> +        }
> +
> +        if( line[0] == 'S' )
> +        {
> +            free( psz_smalltext );
> +            psz_smalltext = strdup( line + 2 );
> +            vlc_meta_AddExtra( p_meta, "Info", line + 2 );
> +        }
> +
> +        if( line[0] == 'D' )
> +        {
> +            for( char *p = line + 2; *p; ++p )
> +            {
> +                if( *p == '|' )
> +                    *p = '\n';
> +            }
> +            vlc_meta_SetDescription( p_meta, line + 2 );
> +        }
> +
> +        /* FPS are required to convert between timestamps and frames */
> +        if( line[0] == 'F' )
> +        {
> +            float fps = atof( line + 2 );
> +            if( fps >= 1 )
> +                p_sys->fps = fps;
> +            vlc_meta_AddExtra( p_meta, "Frame Rate", line + 2 );
> +        }
> +
> +        if( line[0] == 'P' )
> +        {
> +            vlc_meta_AddExtra( p_meta, "Priority", line + 2 );
> +        }
> +
> +        if( line[0] == 'L' )
> +        {
> +            vlc_meta_AddExtra( p_meta, "Lifetime", line + 2 );
> +        }
> +    }
> +
> +    /* 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 );
> +}
> +
> +/*****************************************************************************
> + * ImportMarks: Import cut marks and convert them to seekpoints
> + *****************************************************************************/
> +static void ImportMarks( access_t *p_access )
> +{
> +    access_sys_t *p_sys = p_access->p_sys;
> +
> +    FILE *marksfile = OpenRelativeFile( p_access, "marks", true );
> +    if( !marksfile )
> +        return;
> +
> +    FILE *indexfile = OpenRelativeFile( p_access, "index", false );
> +    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->b_menu = true;
> +    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 all chapters as requested */
> +        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 );
> +        }
> +    }
> +
> +    if( p_marks->i_seekpoint != 0 )
> +        p_sys->p_marks = p_marks;
> +    else
> +        vlc_input_title_Delete( p_marks );
> +
> +    fclose( marksfile );
> +    fclose( indexfile );
> +}
> +
> +/*****************************************************************************
> + * ReadIndexRecord: 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 & 0xFFFFFFFFFF;
> +        *pi_file_num = i_index_entry >> 48;
> +    }
> +    else
> +    {
> +        *pi_offset = GetDWLE( &index_record );
> +        *pi_file_num = index_record[5];
> +    }
> +
> +    return true;
> +}
> +
> +/*****************************************************************************
> + * ParseFrameNumber: Convert time stamp from file to frame number
> + *****************************************************************************/
> +static int64_t ParseFrameNumber( const char *psz_line, float fps )
> +{
> +    unsigned int 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
> 

> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> http://mailman.videolan.org/listinfo/vlc-devel


-- 
Best Regards,

-- 
Jean-Baptiste Kempf
http://www.jbkempf.com/



More information about the vlc-devel mailing list