Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  vdr.c   Sprache: 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 Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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/
            00001.ts, 00002.ts, 00003.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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <ctype.h>
#include <time.h>
#include <errno.h>

#include <vlc_common.h>
#include <vlc_arrays.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>
#include <vlc_memstream.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 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_shortname( N_("VDR") )
    set_help( HELP_TEXT )
    set_subcategory( SUBCAT_INPUT_ACCESS )
    set_description( N_("VDR recordings") )
    add_integer( "vdr-chapter-offset", 0,
        CHAPTER_OFFSET_TEXT, CHAPTER_OFFSET_LONGTEXT )
    add_float_with_range( "vdr-fps", 25, 1, 1000,
        FPS_TEXT, FPS_LONGTEXT )
    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
 *****************************************************************************/


/* minimum chapter size in seconds */
#define MIN_CHAPTER_SIZE 5

TYPEDEF_ARRAY( uint64_t, size_array_t )

typedef struct
{
    /* file sizes of all parts */
    size_array_t file_sizes;
    uint64_t offset;
    uint64_t size; /* total size */

    /* 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;
    uint64_t *offsets;
    unsigned cur_seekpoint;
    float fps;

    /* file format: true=TS, false=PES */
    bool b_ts_format;
} access_sys_t;

#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( stream_t *, int, va_list );
static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len );
static int Seek( stream_t *p_access, uint64_t i_pos);
static void FindSeekpoint( stream_t *p_access );
static bool ScanDirectory( stream_t *p_access );
static char *GetFilePath( stream_t *p_access, unsigned i_file );
static bool ImportNextFile( stream_t *p_access );
static bool SwitchFile( stream_t *p_access, unsigned i_file );
static void OptimizeForRead( int fd );
static void UpdateFileSize( stream_t *p_access );
static FILE *OpenRelativeFile( stream_t *p_access, const char *psz_file );
static bool ReadLine( char **ppsz_line, size_t *pi_size, FILE *p_file );
static void ImportMeta( stream_t *p_access );
static void ImportMarks( stream_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 );
static const char *BaseName( const char *psz_path );

/*****************************************************************************
 * Open a directory
 *****************************************************************************/

static int Open( vlc_object_t *p_this )
{
    stream_t *p_access = (stream_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_name, "vdr" );

    /* Do a quick test based on the directory name to see if this
     * directory might contain a VDR recording. We can be reasonably
     * sure if ScanDirectory() actually finds files. */

    if( b_strict )
    {
        char psz_extension[4];
        int i_length = 0;
        const char *psz_name = BaseName( p_access->psz_filepath );
        if( sscanf( psz_name, "%*u-%*u-%*u.%*u.%*u.%*u%*[-.]%*u.%3s%n",
            psz_extension, &i_length ) != 1 || strcasecmp( psz_extension, "rec" ) ||
            ( psz_name[i_length] != DIR_SEP_CHAR && psz_name[i_length] != '\0' ) )
            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 = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );

    if( unlikely(p_sys == NULL) )
        return VLC_ENOMEM;

    p_access->p_sys = p_sys;
    p_sys->fd = -1;
    p_sys->cur_seekpoint = 0;
    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 ) ||
        !SwitchFile( p_access, 0 ) )
    {
        Close( p_this );
        return VLC_EGENERIC;
    }

    ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close files and free resources
 *****************************************************************************/

static void Close( vlc_object_t * p_this )
{
    stream_t *p_access = (stream_t*)p_this;
    access_sys_t *p_sys = p_access->p_sys;

    if( p_sys->fd != -1 )
        vlc_close( p_sys->fd );
    ARRAY_RESET( p_sys->file_sizes );

    if( p_sys->p_meta )
        vlc_meta_Delete( p_sys->p_meta );

    free( p_sys->offsets );
    vlc_input_title_Delete( p_sys->p_marks );
}

/*****************************************************************************
 * Determine format and import files
 *****************************************************************************/

static bool ScanDirectory( stream_t *p_access )
{
    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;
    }

    /* 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( stream_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;
    vlc_meta_t *p_meta;

    switch( i_query )
    {
        case STREAM_CAN_SEEK:
        case STREAM_CAN_FASTSEEK:
        case STREAM_CAN_PAUSE:
        case STREAM_CAN_CONTROL_PACE:
            *va_arg( args, bool* ) = true;
            break;

        case STREAM_GET_SIZE:
            *va_arg( args, uint64_t* ) = p_sys->size;
            break;

        case STREAM_GET_PTS_DELAY:
            *va_arg( args, vlc_tick_t * ) =
                VLC_TICK_FROM_MS(var_InheritInteger( p_access, "file-caching" ));
            break;

        case STREAM_SET_PAUSE_STATE:
            /* nothing to do */
            break;

        case STREAM_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( **ppp_title ) );
            if( !*ppp_title )
                return VLC_ENOMEM;
            **ppp_title = vlc_input_title_Duplicate( p_sys->p_marks );
            break;

        case STREAM_GET_TITLE:
            *va_arg( args, unsigned * ) = 0;
            break;

        case STREAM_GET_SEEKPOINT:
            *va_arg( args, unsigned * ) = p_sys->cur_seekpoint;
            break;

        case STREAM_GET_CONTENT_TYPE:
            *va_arg( args, char ** ) =
                strdup( p_sys->b_ts_format ? "video/MP2T" : "video/MP2P" );
            break;

        case STREAM_SET_TITLE:
            /* ignore - only one title */
            break;

        case STREAM_SET_SEEKPOINT:
            i = va_arg( args, int );
            if( i < 0 || !p_sys->p_marks || i >= p_sys->p_marks->i_seekpoint )
                return VLC_EGENERIC;
            return Seek( p_access, p_sys->offsets[i] );

        case STREAM_GET_META:
            p_meta = va_arg( args, vlc_meta_t* );
            vlc_meta_Merge( p_meta, p_sys->p_meta );
            break;

        default:
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Read and concatenate files
 *****************************************************************************/

static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len )
{
    access_sys_t *p_sys = p_access->p_sys;

    if( p_sys->fd == -1 )
        /* no more data */
        return 0;

    ssize_t i_ret = read( p_sys->fd, p_buffer, i_len );

    if( i_ret > 0 )
    {
        /* success */
        p_sys->offset += 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 (%s)", vlc_strerror_c(errno) );
        vlc_dialog_display_error( p_access, _("File reading failed"),
            _("VLC could not read the file (%s)."),
            vlc_strerror(errno) );
        SwitchFile( p_access, -1 );
        return 0;
    }
}

/*****************************************************************************
 * Seek to a specific location in a file
 *****************************************************************************/

static int Seek( stream_t *p_access, uint64_t i_pos )
{
    access_sys_t *p_sys = p_access->p_sys;

    /* might happen if called by STREAM_SET_SEEKPOINT */
    i_pos = __MIN( i_pos, p_sys->size );

    p_sys->offset = i_pos;

    /* find correct chapter */
    FindSeekpoint( p_access );

    /* find correct file */
    unsigned i_file = 0;
    while( i_file < FILE_COUNT - 1 &&
           i_pos >= FILE_SIZE( i_file ) )
    {
        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( stream_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;
    if( !p_sys->p_marks )
        return;

    int new_seekpoint = p_sys->cur_seekpoint;
    if( p_sys->offset < p_sys->offsets[p_sys->cur_seekpoint] )
    {
        /* i_pos moved backwards, start fresh */
        new_seekpoint = 0;
    }

    /* only need to check the following seekpoints */
    while( new_seekpoint + 1 < p_sys->p_marks->i_seekpoint &&
        p_sys->offset >= p_sys->offsets[new_seekpoint + 1] )
    {
        new_seekpoint++;
    }

    p_sys->cur_seekpoint = new_seekpoint;
}

/*****************************************************************************
 * Returns the path of a certain part
 *****************************************************************************/

static char *GetFilePath( stream_t *p_access, unsigned i_file )
{
    access_sys_t *sys = p_access->p_sys;
    char *psz_path;

    if( asprintf( &psz_path, 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( stream_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: %s", psz_path,
                 vlc_strerror_c(errno) );
        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_sys->size += st.st_size;

    return true;
}

/*****************************************************************************
 * Close the current file and open another
 *****************************************************************************/

static bool SwitchFile( stream_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 )
    {
        vlc_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: %s", psz_path,
                 vlc_strerror_c(errno) );
        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:
    vlc_dialog_display_error (p_access, _("File reading failed"), _("VLC could not"
        " open the file \"%s\" (%s)."), psz_path, vlc_strerror(errno) );
    if( p_sys->fd != -1 )
    {
        vlc_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 F_RDAHEAD
    fcntl( fd, F_RDAHEAD, 1 );
#endif
#ifdef F_NOCACHE
    fcntl( fd, F_NOCACHE, 0 );
#endif
}

/*****************************************************************************
 * Fix size if the (last) part is still growing
 *****************************************************************************/

static void UpdateFileSize( stream_t *p_access )
{
    access_sys_t *p_sys = p_access->p_sys;
    struct stat st;

    if( p_sys->size >= p_sys->offset )
        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_sys->size -= CURRENT_FILE_SIZE;
    CURRENT_FILE_SIZE = st.st_size;
    p_sys->size += CURRENT_FILE_SIZE;
}

/*****************************************************************************
 * Open file relative to base directory for reading.
 *****************************************************************************/

static FILE *OpenRelativeFile( stream_t *p_access, const char *psz_file )
{
    access_sys_t *sys = p_access->p_sys;

    /* build path and add extension */
    char *psz_path;
    if( asprintf( &psz_path, "%s" DIR_SEP "%s%s", p_access->psz_filepath,
                  psz_file, 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: %s", psz_path,
                  vlc_strerror_c(errno) );
    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( stream_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;

    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_SetExtra( p_meta, "Channel", psz_name + 1 );
            }
            vlc_meta_SetExtra( 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_SetExtra( p_meta, "Date", str );

                /* display in minutes */
                i_length = ( i_length + 59 ) / 60;
                snprintf( str, sizeof(str), "%u:%02u", i_length / 60, i_length % 60 );
                vlc_meta_SetExtra( p_meta, "Duration", str );
            }
        }

        else if( tag == 'T' )
        {
            vlc_meta_SetExtra( p_meta, "Title", text );
        }

        else if( tag == 'S' )
        {
            vlc_meta_SetExtra( 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_SetExtra( p_meta, "Frame Rate", text );
        }

        else if( tag == 'P' )
        {
            vlc_meta_SetExtra( p_meta, "Priority", text );
        }

        else if( tag == 'L' )
        {
            vlc_meta_SetExtra( p_meta, "Lifetime", text );
        }
    }

    /* create a meaningful title */
    struct vlc_memstream memstream;
    if( vlc_memstream_open( &memstream ) == 0 )
    {
        const char *psz_title = vlc_meta_GetExtra( p_meta, "Title" );
        const char *psz_smalltext = vlc_meta_GetExtra( p_meta, "Info" );
        const char *psz_date = vlc_meta_GetExtra( p_meta, "Date" );

        if( psz_title )
        {
            vlc_memstream_puts( &memstream, psz_title );
            if( psz_smalltext )
                vlc_memstream_puts( &memstream, " - " );
        }
        if( psz_smalltext )
            vlc_memstream_puts( &memstream, psz_smalltext );
        if( ( psz_title || psz_smalltext ) && psz_date )
            vlc_memstream_printf( &memstream, " (%s)", psz_date );
        if( vlc_memstream_close( &memstream ) == 0 )
        {
            if( memstream.length )
                vlc_meta_SetTitle( p_meta, memstream.ptr );
            free( memstream.ptr );
        }
    }

    fclose( infofile );
}

/*****************************************************************************
 * Import cut marks and convert them to seekpoints (chapters).
 *****************************************************************************/

static void ImportMarks( stream_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;
    }

    /* get the length of this recording (index stores 8 bytes per frame) */
    struct stat st;
    if( fstat( fileno( indexfile ), &st ) )
    {
        fclose( marksfile );
        fclose( indexfile );
        return;
    }
    int64_t i_frame_count = st.st_size / 8;

    /* 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") );
    p_marks->i_length = i_frame_count * (vlc_tick_t)( CLOCK_FREQ / p_sys->fps );

    uint64_t *offsetv = NULL;

    /* offset for chapter positions */
    int i_chapter_offset = p_sys->fps / 1000 *
        var_InheritInteger( p_access, "vdr-chapter-offset" );

    /* minimum chapter size in frames */
    int i_min_chapter_size = p_sys->fps * MIN_CHAPTER_SIZE;

    /* the last chapter started at this frame (init to 0 so
     * we skip useless chapters near the beginning as well) */

    int64_t i_prev_chapter = 0;

    /* 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 );

        /* skip chapters which are near the end or too close to each other */
        if( i_frame - i_prev_chapter < i_min_chapter_size ||
            i_frame >= i_frame_count - i_min_chapter_size )
            continue;
        i_prev_chapter = i_frame;

        /* 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 * (vlc_tick_t)( CLOCK_FREQ / p_sys->fps );
        sp->psz_name = strdup( line );

        TAB_APPEND( p_marks->i_seekpoint, p_marks->seekpoint, sp );
        offsetv = xrealloc(offsetv, p_marks->i_seekpoint * sizeof (*offsetv));

        for( int i = 0; i + 1 < i_file_number; ++i )
            i_offset += FILE_SIZE(i);

        offsetv[p_marks->i_seekpoint - 1] = i_offset;
    }

    /* add a chapter at the beginning if missing */
    if( p_marks->i_seekpoint > 0 && offsetv[0] > 0 )
    {
        seekpoint_t *sp = vlc_seekpoint_New();
        if( sp )
        {
            sp->i_time_offset = 0;
            sp->psz_name = strdup( _("Start") );
            TAB_INSERT( p_marks->i_seekpoint, p_marks->seekpoint, sp, 0 );
            offsetv = xrealloc(offsetv,
                               p_marks->i_seekpoint * sizeof (*offsetv));
            memmove(offsetv + 1, offsetv,
                    (p_marks->i_seekpoint - 1) * sizeof (*offsetv));
            offsetv[0] = 0;
        }
    }

    if( p_marks->i_seekpoint > 0 )
    {
        p_sys->p_marks = p_marks;
        p_sys->offsets = offsetv;
    }
    else
    {
        vlc_input_title_Delete( p_marks );
        free(offsetv);
    }

    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 ) < 1 )
        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;
}

/*****************************************************************************
 * Return the last path component (including trailing separators)
 *****************************************************************************/

static const char *BaseName( const char *psz_path )
{
    const char *psz_name = psz_path + strlen( psz_path );

    /* skip superfluous separators at the end */
    while( psz_name > psz_path && psz_name[-1] == DIR_SEP_CHAR )
        --psz_name;

    /* skip last component */
    while( psz_name > psz_path && psz_name[-1] != DIR_SEP_CHAR )
        --psz_name;

    return psz_name;
}

Messung
CHZG
85989693

¤ Dauer der Verarbeitung: 0.6 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104852
#Domains=417161