HandBrake/libhb/dvdnav.c

2198 lines
69 KiB
C

/* dvdnav.c
Copyright (c) 2003-2025 HandBrake Team
This file is part of the HandBrake source code
Homepage: <http://handbrake.fr/>.
It may be used under the terms of the GNU General Public License v2.
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
#include "libavcodec/avcodec.h"
#include "handbrake/handbrake.h"
#include "handbrake/lang.h"
#include "handbrake/dvd.h"
#include "dvdnav/dvdnav.h"
#include "dvdread/ifo_read.h"
#include "dvdread/ifo_print.h"
#include "dvdread/nav_read.h"
#define DVD_READ_CACHE 1
static char * hb_dvdnav_name( char * path );
static hb_dvd_t * hb_dvdnav_init( hb_handle_t * h, const char * path );
static int hb_dvdnav_title_count( hb_dvd_t * d );
static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * d, int t, uint64_t min_duration, uint64_t max_duration );
static int hb_dvdnav_start( hb_dvd_t * d, hb_title_t *title, int chapter );
static void hb_dvdnav_stop( hb_dvd_t * d );
static int hb_dvdnav_seek( hb_dvd_t * d, float f );
static hb_buffer_t * hb_dvdnav_read( hb_dvd_t * d );
static int hb_dvdnav_chapter( hb_dvd_t * d );
static void hb_dvdnav_close( hb_dvd_t ** _d );
static int hb_dvdnav_angle_count( hb_dvd_t * d );
static void hb_dvdnav_set_angle( hb_dvd_t * d, int angle );
static int hb_dvdnav_main_feature( hb_dvd_t * d, hb_list_t * list_title );
hb_dvd_func_t hb_dvdnav_func =
{
hb_dvdnav_init,
hb_dvdnav_close,
hb_dvdnav_name,
hb_dvdnav_title_count,
hb_dvdnav_title_scan,
hb_dvdnav_start,
hb_dvdnav_stop,
hb_dvdnav_seek,
hb_dvdnav_read,
hb_dvdnav_chapter,
hb_dvdnav_angle_count,
hb_dvdnav_set_angle,
hb_dvdnav_main_feature
};
// there can be at most 999 PGCs per title. round that up to the nearest
// power of two.
#define MAX_PGCN 1024
/***********************************************************************
* Local prototypes
**********************************************************************/
static void PgcWalkInit( uint32_t pgcn_map[MAX_PGCN/32] );
static int FindChapterIndex( hb_list_t * list, int pgcn, int pgn );
static int NextPgcn( ifo_handle_t *ifo, int pgcn, uint32_t pgcn_map[MAX_PGCN/32] );
static int FindNextCell( pgc_t *pgc, int cell_cur );
static int dvdtime2msec( dvd_time_t * );
static int TitleOpenIfo(hb_dvdnav_t * d, int t);
static void TitleCloseIfo(hb_dvdnav_t * d);
hb_dvd_func_t * hb_dvdnav_methods( void )
{
return &hb_dvdnav_func;
}
static char * hb_dvdnav_name( char * path )
{
static char name[1024];
unsigned char unused[1024];
dvd_reader_t * reader;
reader = DVDOpen( path );
if( !reader )
{
return NULL;
}
if( DVDUDFVolumeInfo( reader, name, sizeof( name ),
unused, sizeof( unused ) ) )
{
DVDClose( reader );
return NULL;
}
DVDClose( reader );
return name;
}
/***********************************************************************
* hb_dvdnav_reset
***********************************************************************
* Once dvdnav has entered the 'stopped' state, it can not be revived
* dvdnav_reset doesn't work because it doesn't remember the path
* So this function re-opens dvdnav
**********************************************************************/
static int hb_dvdnav_reset( hb_dvdnav_t * d )
{
if ( d->dvdnav )
dvdnav_close( d->dvdnav );
/* Open device */
if( dvdnav_open(&d->dvdnav, d->path) != DVDNAV_STATUS_OK )
{
/*
* Not an error, may be a stream - which we'll try in a moment.
*/
hb_log( "dvd: not a dvd - trying as a stream/file instead" );
goto fail;
}
if (dvdnav_set_readahead_flag(d->dvdnav, DVD_READ_CACHE) !=
DVDNAV_STATUS_OK)
{
hb_error("Error: dvdnav_set_readahead_flag: %s\n",
dvdnav_err_to_string(d->dvdnav));
goto fail;
}
/*
** set the PGC positioning flag to have position information
** relatively to the whole feature instead of just relatively to the
** current chapter
**/
if (dvdnav_set_PGC_positioning_flag(d->dvdnav, 1) != DVDNAV_STATUS_OK)
{
hb_error("Error: dvdnav_set_PGC_positioning_flag: %s\n",
dvdnav_err_to_string(d->dvdnav));
goto fail;
}
return 1;
fail:
if( d->dvdnav ) dvdnav_close( d->dvdnav );
return 0;
}
/***********************************************************************
* hb_dvdnav_init
***********************************************************************
*
**********************************************************************/
static hb_dvd_t * hb_dvdnav_init( hb_handle_t * h, const char * path )
{
hb_dvd_t * e;
hb_dvdnav_t * d;
int region_mask;
e = calloc( sizeof( hb_dvd_t ), 1 );
d = &(e->dvdnav);
d->h = h;
/* Log DVD drive region code */
if ( hb_dvd_region( path, &region_mask ) == 0 )
{
hb_log( "dvd: Region mask 0x%02x", region_mask );
if ( region_mask == 0xFF )
{
hb_log( "dvd: Warning, DVD device has no region set" );
}
}
/* Open device */
if( dvdnav_open(&d->dvdnav, path) != DVDNAV_STATUS_OK )
{
/*
* Not an error, may be a stream - which we'll try in a moment.
*/
hb_log( "dvd: not a dvd - trying as a stream/file instead" );
goto fail;
}
if (dvdnav_set_readahead_flag(d->dvdnav, DVD_READ_CACHE) !=
DVDNAV_STATUS_OK)
{
hb_error("Error: dvdnav_set_readahead_flag: %s\n",
dvdnav_err_to_string(d->dvdnav));
goto fail;
}
/*
** set the PGC positioning flag to have position information
** relatively to the whole feature instead of just relatively to the
** current chapter
**/
if (dvdnav_set_PGC_positioning_flag(d->dvdnav, 1) != DVDNAV_STATUS_OK)
{
hb_error("Error: dvdnav_set_PGC_positioning_flag: %s\n",
dvdnav_err_to_string(d->dvdnav));
goto fail;
}
/* Open device */
if( !( d->reader = DVDOpen( path ) ) )
{
/*
* Not an error, may be a stream - which we'll try in a moment.
*/
hb_log( "dvd: not a dvd - trying as a stream/file instead" );
goto fail;
}
/* Open main IFO */
if( !( d->vmg = ifoOpen( d->reader, 0 ) ) )
{
hb_error( "dvd: ifoOpen failed" );
goto fail;
}
d->path = strdup( path );
return e;
fail:
if( d->dvdnav ) dvdnav_close( d->dvdnav );
if( d->vmg ) ifoClose( d->vmg );
if( d->reader ) DVDClose( d->reader );
free( e );
return NULL;
}
/***********************************************************************
* hb_dvdnav_title_count
**********************************************************************/
static int hb_dvdnav_title_count( hb_dvd_t * e )
{
int titles = 0;
hb_dvdnav_t * d = &(e->dvdnav);
dvdnav_get_number_of_titles(d->dvdnav, &titles);
return titles;
}
static uint64_t
PttDuration(ifo_handle_t *ifo, int ttn, int pttn, int *blocks, int *last_pgcn)
{
int pgcn, pgn;
pgc_t * pgc;
uint64_t duration = 0;
int cell_start, cell_end;
int i;
*blocks = 0;
// Initialize map of visited pgc's to prevent loops
uint32_t pgcn_map[MAX_PGCN/32];
PgcWalkInit( pgcn_map );
pgcn = ifo->vts_ptt_srpt->title[ttn-1].ptt[pttn-1].pgcn;
pgn = ifo->vts_ptt_srpt->title[ttn-1].ptt[pttn-1].pgn;
if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp || pgcn >= MAX_PGCN)
{
hb_log( "invalid PGC ID %d, skipping", pgcn );
return 0;
}
if( pgn <= 0 || pgn > 99 )
{
hb_log( "scan: pgn %d not valid, skipping", pgn );
return 0;
}
do
{
pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
if (!pgc)
{
*blocks = 0;
duration = 0;
hb_log( "scan: pgc not valid, skipping" );
break;
}
if (pgc->cell_playback == NULL)
{
*blocks = 0;
duration = 0;
hb_log("invalid PGC cell_playback table, skipping");
break;
}
if (pgn > pgc->nr_of_programs)
{
pgn = 1;
continue;
}
duration += 90LL * dvdtime2msec( &pgc->playback_time );
cell_start = pgc->program_map[pgn-1] - 1;
cell_end = pgc->nr_of_cells - 1;
for(i = cell_start; i <= cell_end; i = FindNextCell(pgc, i))
{
*blocks += pgc->cell_playback[i].last_sector + 1 -
pgc->cell_playback[i].first_sector;
}
*last_pgcn = pgcn;
pgn = 1;
} while((pgcn = NextPgcn(ifo, pgcn, pgcn_map)) != 0);
return duration;
}
static void add_subtitle( hb_list_t * list_subtitle, int position,
iso639_lang_t * lang, int lang_extension,
uint8_t * palette, int style )
{
hb_subtitle_t * subtitle;
int ii, count;
count = hb_list_count(list_subtitle);
for (ii = 0; ii < count; ii++)
{
subtitle = hb_list_item(list_subtitle, ii);
if (((subtitle->id >> 8) & 0x1f) == position)
{
// The subtitle is already in the list
return;
}
}
subtitle = calloc(sizeof(hb_subtitle_t), 1);
subtitle->track = count;
subtitle->id = ((0x20 + position) << 8) | 0xbd;
snprintf(subtitle->lang, sizeof( subtitle->lang ), "%s",
strlen(lang->native_name) ? lang->native_name : lang->eng_name);
snprintf(subtitle->iso639_2, sizeof( subtitle->iso639_2 ), "%s",
lang->iso639_2);
subtitle->format = PICTURESUB;
subtitle->source = VOBSUB;
subtitle->config.dest = RENDERSUB;
subtitle->stream_type = 0xbd;
subtitle->substream_type = 0x20 + position;
subtitle->codec = WORK_DECAVSUB;
subtitle->codec_param = AV_CODEC_ID_DVD_SUBTITLE;
subtitle->timebase.num = 1;
subtitle->timebase.den = 90000;
memcpy(subtitle->palette, palette, 16 * sizeof(uint32_t));
subtitle->palette_set = 1;
const char * name = NULL;
switch (lang_extension)
{
case 1:
subtitle->attributes = HB_SUBTITLE_ATTR_NORMAL;
break;
case 2:
subtitle->attributes = HB_SUBTITLE_ATTR_LARGE;
strcat(subtitle->lang, " Large Type");
name = "Large Type";
break;
case 3:
subtitle->attributes = HB_SUBTITLE_ATTR_CHILDREN;
strcat(subtitle->lang, " Children");
name = "Children";
break;
case 5:
subtitle->attributes = HB_SUBTITLE_ATTR_CC;
strcat(subtitle->lang, " Closed Caption");
name = "Closed Caption";
break;
case 6:
subtitle->attributes = HB_SUBTITLE_ATTR_CC | HB_SUBTITLE_ATTR_LARGE;
strcat(subtitle->lang, " Closed Caption, Large Type");
name = "Closed Caption, Large Type";
break;
case 7:
subtitle->attributes = HB_SUBTITLE_ATTR_CC |
HB_SUBTITLE_ATTR_CHILDREN;
strcat(subtitle->lang, " Closed Caption, Children");
name = "Closed Caption, Children";
break;
case 9:
subtitle->attributes = HB_SUBTITLE_ATTR_FORCED;
strcat(subtitle->lang, " Forced");
break;
case 13:
subtitle->attributes = HB_SUBTITLE_ATTR_COMMENTARY;
strcat(subtitle->lang, " Director's Commentary");
name = "Commentary";
break;
case 14:
subtitle->attributes = HB_SUBTITLE_ATTR_COMMENTARY |
HB_SUBTITLE_ATTR_LARGE;
strcat(subtitle->lang, " Director's Commentary, Large Type");
name = "Commentary, Large Type";
break;
case 15:
subtitle->attributes = HB_SUBTITLE_ATTR_COMMENTARY |
HB_SUBTITLE_ATTR_CHILDREN;
strcat(subtitle->lang, " Director's Commentary, Children");
name = "Commentary, Children";
default:
subtitle->attributes = HB_SUBTITLE_ATTR_UNKNOWN;
break;
}
if (name != NULL)
{
subtitle->name = strdup(name);
}
switch (style)
{
case HB_VOBSUB_STYLE_4_3:
subtitle->attributes |= HB_SUBTITLE_ATTR_4_3;
strcat(subtitle->lang, " (4:3)");
break;
case HB_VOBSUB_STYLE_WIDE:
subtitle->attributes |= HB_SUBTITLE_ATTR_WIDE;
strcat(subtitle->lang, " (Wide Screen)");
break;
case HB_VOBSUB_STYLE_LETTERBOX:
subtitle->attributes |= HB_SUBTITLE_ATTR_LETTERBOX;
strcat(subtitle->lang, " (Letterbox)");
break;
case HB_VOBSUB_STYLE_PANSCAN:
subtitle->attributes |= HB_SUBTITLE_ATTR_PANSCAN;
strcat(subtitle->lang, " (Pan & Scan)");
break;
}
strcat(subtitle->lang, " [");
strcat(subtitle->lang, hb_subsource_name(subtitle->source));
strcat(subtitle->lang, "]");
hb_log("scan: id=0x%x, lang=%s, 3cc=%s ext=%i", subtitle->id,
subtitle->lang, subtitle->iso639_2, lang_extension);
hb_list_add(list_subtitle, subtitle);
}
/***********************************************************************
* hb_dvdnav_title_scan
**********************************************************************/
static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t, uint64_t min_duration, uint64_t max_duration )
{
hb_dvdnav_t * d = &(e->dvdnav);
hb_title_t * title;
int pgcn, i;
pgc_t * pgc;
hb_chapter_t * chapter;
hb_dvd_chapter_t * dvd_chapter;
int count;
const char * title_string;
char name[1024];
unsigned char unused[1024];
const char * codec_name;
hb_log( "scan: scanning title %d", t );
title = hb_title_init( d->path, t );
title->type = HB_DVD_TYPE;
if (dvdnav_get_title_string(d->dvdnav, &title_string) == DVDNAV_STATUS_OK)
{
title->name = strdup(title_string);
}
if (title->name == NULL || title->name[0] == 0)
{
free((char*)title->name);
if (DVDUDFVolumeInfo(d->reader, name, sizeof(name),
unused, sizeof(unused)))
{
char * p_cur, * p_last = d->path;
for( p_cur = d->path; *p_cur; p_cur++ )
{
if( IS_DIR_SEP(p_cur[0]) && p_cur[1] )
{
p_last = &p_cur[1];
}
}
title->name = strdup(p_last);
char *dot_term = strrchr(title->name, '.');
if (dot_term)
*dot_term = '\0';
}
else
{
title->name = strdup(name);
}
}
if (!TitleOpenIfo(d, t))
{
goto fail;
}
/* ignore titles with bogus cell addresses so we don't abort later
** in libdvdread. */
for ( i = 0; i < d->ifo->vts_c_adt->nr_of_vobs; ++i)
{
if( (d->ifo->vts_c_adt->cell_adr_table[i].start_sector & 0xffffff ) ==
0xffffff )
{
hb_log( "scan: cell_adr_table[%d].start_sector invalid (0x%x) "
"- skipping title", i,
d->ifo->vts_c_adt->cell_adr_table[i].start_sector );
goto fail;
}
if( (d->ifo->vts_c_adt->cell_adr_table[i].last_sector & 0xffffff ) ==
0xffffff )
{
hb_log( "scan: cell_adr_table[%d].last_sector invalid (0x%x) "
"- skipping title", i,
d->ifo->vts_c_adt->cell_adr_table[i].last_sector );
goto fail;
}
}
if (global_verbosity_level == 4)
{
ifo_print( d->reader, d->vts );
}
/* Get duration */
title->duration = d->duration;
title->hours = title->duration / 90000 / 3600;
title->minutes = ((title->duration / 90000) % 3600) / 60;
title->seconds = ( title->duration / 90000) % 60;
hb_log( "scan: duration is %02d:%02d:%02d (%"PRId64" ms)",
title->hours, title->minutes, title->seconds,
title->duration / 90 );
/* ignore titles under 10 seconds because they're often stills or
* clips with no audio & our preview code doesn't currently handle
* either of these. */
if (title->duration < min_duration)
{
hb_log( "scan: ignoring title (too short)" );
goto fail;
}
if (max_duration > 0 && title->duration > max_duration )
{
hb_log( "scan: ignoring title (too long)" );
goto fail;
}
/* Get pgc */
if (d->pgcn < 1 ||
d->pgcn > d->ifo->vts_pgcit->nr_of_pgci_srp ||
d->pgcn >= MAX_PGCN)
{
hb_log( "invalid PGC ID %d for title %d, skipping", d->pgcn, t );
goto fail;
}
// Check all pgc's for validity
uint32_t pgcn_map[MAX_PGCN/32];
pgcn = d->pgcn;
PgcWalkInit( pgcn_map );
do
{
pgc = d->ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
if (!pgc || !pgc->program_map)
{
hb_log( "scan: pgc not valid, skipping" );
goto fail;
}
if (pgc->cell_playback == NULL)
{
hb_log( "invalid PGC cell_playback table for title %d, skipping", t );
goto fail;
}
} while ((pgcn = NextPgcn(d->ifo, pgcn, pgcn_map)) != 0);
pgc = d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc;
hb_log("pgc_id: %d, pgn: %d: pgc: %p", d->pgcn, d->pgn, pgc);
if (d->pgn > pgc->nr_of_programs)
{
hb_log( "invalid PGN %d for title %d, skipping", d->pgn, t );
goto fail;
}
/* Detect languages */
for (i = 0; i < d->ifo->vtsi_mat->nr_of_vts_audio_streams; i++)
{
int audio_format, lang_code, lang_extension, audio_control, position, j;
hb_audio_t * audio, * audio_tmp;
iso639_lang_t * lang;
hb_log( "scan: checking audio %d", i + 1 );
audio = calloc( sizeof( hb_audio_t ), 1 );
audio_format = d->ifo->vtsi_mat->vts_audio_attr[i].audio_format;
lang_code = d->ifo->vtsi_mat->vts_audio_attr[i].lang_code;
lang_extension = d->ifo->vtsi_mat->vts_audio_attr[i].code_extension;
audio_control =
d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->audio_control[i];
if (!(audio_control & 0x8000))
{
hb_log( "scan: audio channel is not active" );
free( audio );
continue;
}
position = ( audio_control & 0x7F00 ) >> 8;
switch( audio_format )
{
case 0x00:
audio->id = ( ( 0x80 + position ) << 8 ) | 0xbd;
audio->config.in.codec = HB_ACODEC_AC3;
audio->config.in.codec_param = AV_CODEC_ID_AC3;
codec_name = "AC3";
break;
case 0x02:
case 0x03:
audio->id = 0xc0 + position;
audio->config.in.codec = HB_ACODEC_FFMPEG;
audio->config.in.codec_param = AV_CODEC_ID_MP2;
codec_name = "MPEG";
break;
case 0x04:
audio->id = ( ( 0xa0 + position ) << 8 ) | 0xbd;
audio->config.in.codec = HB_ACODEC_LPCM;
codec_name = "LPCM";
break;
case 0x06:
audio->id = ( ( 0x88 + position ) << 8 ) | 0xbd;
audio->config.in.codec = HB_ACODEC_DCA;
audio->config.in.codec_param = AV_CODEC_ID_DTS;
codec_name = "DTS";
break;
default:
audio->id = 0;
audio->config.in.codec = 0;
codec_name = "Unknown";
hb_log( "scan: unknown audio codec (%x)",
audio_format );
break;
}
if( !audio->id )
{
free(audio);
continue;
}
const char * name = NULL;
switch ( lang_extension )
{
case 1:
audio->config.lang.attributes = HB_AUDIO_ATTR_NORMAL;
break;
case 2:
audio->config.lang.attributes = HB_AUDIO_ATTR_VISUALLY_IMPAIRED;
name = "Visually Impaired";
break;
case 3:
audio->config.lang.attributes = HB_AUDIO_ATTR_COMMENTARY;
name = "Commentary";
break;
case 4:
audio->config.lang.attributes = HB_AUDIO_ATTR_ALT_COMMENTARY;
name = "Commentary";
break;
default:
audio->config.lang.attributes = HB_AUDIO_ATTR_NONE;
break;
}
if (name != NULL)
{
audio->config.in.name = strdup(name);
}
/* Check for duplicate tracks */
audio_tmp = NULL;
for( j = 0; j < hb_list_count( title->list_audio ); j++ )
{
audio_tmp = hb_list_item( title->list_audio, j );
if( audio->id == audio_tmp->id )
{
break;
}
audio_tmp = NULL;
}
if( audio_tmp )
{
hb_log( "scan: duplicate audio track" );
free( audio );
continue;
}
lang = lang_for_code( lang_code );
snprintf( audio->config.lang.simple,
sizeof( audio->config.lang.simple ), "%s",
strlen( lang->native_name ) ? lang->native_name : lang->eng_name );
snprintf( audio->config.lang.iso639_2,
sizeof( audio->config.lang.iso639_2 ), "%s", lang->iso639_2 );
hb_log("scan: id=0x%x, lang=%s (%s), 3cc=%s ext=%i", audio->id,
audio->config.lang.simple, codec_name,
audio->config.lang.iso639_2, lang_extension);
audio->config.index = hb_list_count(title->list_audio);
audio->config.in.track = i;
audio->config.in.timebase.num = 1;
audio->config.in.timebase.den = 90000;
hb_list_add( title->list_audio, audio );
}
/* Check for subtitles */
for( i = 0; i < d->ifo->vtsi_mat->nr_of_vts_subp_streams; i++ )
{
int spu_control, pos, lang_ext = 0;
iso639_lang_t * lang;
hb_log( "scan: checking subtitle %d", i + 1 );
// spu_control
// 0x80000000 - Subtitle enabled
// 0x1f000000 - Position mask for 4:3 aspect subtitle track
// 0x001f0000 - Position mask for Wide Screen subtitle track
// 0x00001f00 - Position mask for Letterbox subtitle track
// 0x0000001f - Position mask for Pan&Scan subtitle track
spu_control =
d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->subp_control[i];
if( !( spu_control & 0x80000000 ) )
{
hb_log( "scan: subtitle channel is not active" );
continue;
}
lang_ext = d->ifo->vtsi_mat->vts_subp_attr[i].code_extension;
lang = lang_for_code(d->ifo->vtsi_mat->vts_subp_attr[i].lang_code);
// display_aspect_ratio
// 0 = 4:3
// 3 = 16:9
// other = invalid
if (d->ifo->vtsi_mat->vts_video_attr.display_aspect_ratio)
{
// Add Wide Screen subtitle.
pos = (spu_control >> 16) & 0x1F;
add_subtitle(title->list_subtitle, pos, lang, lang_ext,
(uint8_t*)d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->palette,
HB_VOBSUB_STYLE_WIDE);
// permitted_df
// 1 - Letterbox not permitted
// 2 - Pan&Scan not permitted
// 3 - Letterbox and Pan&Scan not permitted
if (!(d->ifo->vtsi_mat->vts_video_attr.permitted_df & 1))
{
// Letterbox permitted. Add Letterbox subtitle.
pos = (spu_control >> 8) & 0x1F;
add_subtitle(title->list_subtitle, pos, lang, lang_ext,
(uint8_t*)d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->palette,
HB_VOBSUB_STYLE_LETTERBOX);
}
if (!(d->ifo->vtsi_mat->vts_video_attr.permitted_df & 2))
{
// Pan&Scan permitted. Add Pan&Scan subtitle.
pos = spu_control & 0x1F;
add_subtitle(title->list_subtitle, pos, lang, lang_ext,
(uint8_t*)d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->palette,
HB_VOBSUB_STYLE_PANSCAN);
}
}
else
{
pos = (spu_control >> 24) & 0x1F;
add_subtitle(title->list_subtitle, pos, lang, lang_ext,
(uint8_t*)d->ifo->vts_pgcit->pgci_srp[d->pgcn-1].pgc->palette,
HB_VOBSUB_STYLE_4_3);
}
}
/* Chapters */
count = hb_list_count(d->list_dvd_chapter);
hb_log( "scan: title %d has %d chapters", t, count );
for (i = 0; i < count; i++)
{
char chapter_title[80];
dvd_chapter = hb_list_item(d->list_dvd_chapter, i);
chapter = calloc(sizeof( hb_chapter_t ), 1);
chapter->index = i + 1;
chapter->duration = dvd_chapter->duration;
snprintf(chapter_title, sizeof(chapter_title), "Chapter %d", chapter->index);
hb_chapter_set_title(chapter, chapter_title);
hb_list_add( title->list_chapter, chapter );
int seconds = ( chapter->duration + 45000 ) / 90000;
chapter->hours = ( seconds / 3600 );
chapter->minutes = ( seconds % 3600 ) / 60;
chapter->seconds = ( seconds % 60 );
hb_log( "scan: chap %d, %"PRId64" ms",
chapter->index, chapter->duration / 90 );
}
/* Get aspect. We don't get width/height/rate infos here as
they tend to be wrong */
switch (d->ifo->vtsi_mat->vts_video_attr.display_aspect_ratio)
{
case 0:
title->container_dar.num = 4;
title->container_dar.den = 3;
break;
case 3:
title->container_dar.num = 16;
title->container_dar.den = 9;
break;
default:
hb_log( "scan: unknown aspect" );
goto fail;
}
switch (d->ifo->vtsi_mat->vts_video_attr.mpeg_version)
{
case 0:
title->video_codec = WORK_DECAVCODECV;
title->video_codec_param = AV_CODEC_ID_MPEG1VIDEO;
break;
case 1:
title->video_codec = WORK_DECAVCODECV;
title->video_codec_param = AV_CODEC_ID_MPEG2VIDEO;
break;
default:
hb_log("scan: unknown/reserved MPEG version %d",
d->ifo->vtsi_mat->vts_video_attr.mpeg_version);
title->video_codec = WORK_DECAVCODECV;
title->video_codec_param = AV_CODEC_ID_MPEG2VIDEO;
break;
}
hb_log("scan: aspect = %d:%d",
title->container_dar.num, title->container_dar.den);
/* This title is ok so far */
goto cleanup;
fail:
hb_title_close( &title );
cleanup:
TitleCloseIfo(d);
return title;
}
/***********************************************************************
* hb_dvdnav_title_scan
**********************************************************************/
static int find_title( hb_list_t * list_title, int title )
{
int ii;
for ( ii = 0; ii < hb_list_count( list_title ); ii++ )
{
hb_title_t * hbtitle = hb_list_item( list_title, ii );
if ( hbtitle->index == title )
return ii;
}
return -1;
}
static int skip_to_menu( dvdnav_t * dvdnav, int blocks )
{
int ii;
int result, event, len;
uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
for ( ii = 0; ii < blocks; ii++ )
{
result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(dvdnav));
return 0;
}
switch ( event )
{
case DVDNAV_BLOCK_OK:
break;
case DVDNAV_CELL_CHANGE:
{
} break;
case DVDNAV_STILL_FRAME:
{
dvdnav_still_event_t *event;
event = (dvdnav_still_event_t*)buf;
dvdnav_still_skip( dvdnav );
if ( event->length == 255 )
{
// Infinite still. Can't be the main feature unless
// you like watching paint dry.
return 0;
}
} break;
case DVDNAV_WAIT:
dvdnav_wait_skip( dvdnav );
break;
case DVDNAV_STOP:
return 0;
case DVDNAV_HOP_CHANNEL:
break;
case DVDNAV_NAV_PACKET:
{
pci_t *pci = dvdnav_get_current_nav_pci( dvdnav );
if ( pci == NULL ) break;
int buttons = pci->hli.hl_gi.btn_ns;
int title, part;
result = dvdnav_current_title_info( dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
{
hb_log("dvdnav title info: %s", dvdnav_err_to_string(dvdnav));
}
else if ( title == 0 && buttons > 0 )
{
// Check button activation duration to see if this
// isn't another fake menu.
if ( pci->hli.hl_gi.btn_se_e_ptm - pci->hli.hl_gi.hli_s_ptm >
15 * 90000 )
{
// Found what appears to be a good menu.
return 1;
}
}
} break;
case DVDNAV_VTS_CHANGE:
{
dvdnav_vts_change_event_t *event;
event = (dvdnav_vts_change_event_t*)buf;
// Some discs initialize the vts with the "first play" item
// and some don't seem to. So if we see it is uninitialized,
// set it.
if ( event->new_vtsN <= 0 )
dvdnav_title_play( dvdnav, 1 );
} break;
case DVDNAV_HIGHLIGHT:
break;
case DVDNAV_AUDIO_STREAM_CHANGE:
break;
case DVDNAV_SPU_STREAM_CHANGE:
break;
case DVDNAV_SPU_CLUT_CHANGE:
break;
case DVDNAV_NOP:
break;
default:
break;
}
}
return 0;
}
static int try_button( dvdnav_t * dvdnav, int button, hb_list_t * list_title )
{
int result, event, len;
uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
int ii, jj;
int32_t cur_title = 0, title, part;
uint64_t longest_duration = 0;
int longest = -1;
pci_t *pci = dvdnav_get_current_nav_pci( dvdnav );
result = dvdnav_button_select_and_activate( dvdnav, pci, button + 1 );
if (result != DVDNAV_STATUS_OK)
{
hb_log("dvdnav_button_select_and_activate: %s", dvdnav_err_to_string(dvdnav));
}
result = dvdnav_current_title_info( dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav cur title info: %s", dvdnav_err_to_string(dvdnav));
cur_title = title;
for (jj = 0; jj < 10; jj++)
{
for (ii = 0; ii < 2000; ii++)
{
result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(dvdnav));
goto done;
}
switch ( event )
{
case DVDNAV_BLOCK_OK:
break;
case DVDNAV_CELL_CHANGE:
{
result = dvdnav_current_title_info( dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav title info: %s", dvdnav_err_to_string(dvdnav));
cur_title = title;
// Note, some "fake" titles have long advertised durations
// but then jump to the real title early in playback.
// So keep reading after finding a long title to detect
// such cases.
} break;
case DVDNAV_STILL_FRAME:
{
dvdnav_still_event_t *event;
event = (dvdnav_still_event_t*)buf;
dvdnav_still_skip( dvdnav );
if ( event->length == 255 )
{
// Infinite still. Can't be the main feature unless
// you like watching paint dry.
goto done;
}
} break;
case DVDNAV_WAIT:
dvdnav_wait_skip( dvdnav );
break;
case DVDNAV_STOP:
goto done;
case DVDNAV_HOP_CHANNEL:
break;
case DVDNAV_NAV_PACKET:
{
} break;
case DVDNAV_VTS_CHANGE:
{
result = dvdnav_current_title_info( dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav title info: %s", dvdnav_err_to_string(dvdnav));
cur_title = title;
// Note, some "fake" titles have long advertised durations
// but then jump to the real title early in playback.
// So keep reading after finding a long title to detect
// such cases.
} break;
case DVDNAV_HIGHLIGHT:
break;
case DVDNAV_AUDIO_STREAM_CHANGE:
break;
case DVDNAV_SPU_STREAM_CHANGE:
break;
case DVDNAV_SPU_CLUT_CHANGE:
break;
case DVDNAV_NOP:
break;
default:
break;
}
}
// Check if the current title is long enough to qualify
// as the main feature.
if ( cur_title > 0 )
{
hb_title_t * hbtitle;
int index;
index = find_title( list_title, cur_title );
hbtitle = hb_list_item( list_title, index );
if ( hbtitle != NULL )
{
if ( hbtitle->duration / 90000 > 10 * 60 )
{
hb_deep_log( 3, "dvdnav: Found candidate feature title %d duration %02d:%02d:%02d on button %d",
cur_title, hbtitle->hours, hbtitle->minutes,
hbtitle->seconds, button+1 );
return cur_title;
}
if ( hbtitle->duration > longest_duration )
{
longest_duration = hbtitle->duration;
longest = title;
}
}
// Some titles have long lead-ins. Try skipping it.
dvdnav_next_pg_search( dvdnav );
}
}
done:
if ( longest != -1 )
{
hb_title_t * hbtitle;
int index;
index = find_title( list_title, longest );
hbtitle = hb_list_item( list_title, index );
if ( hbtitle != NULL )
{
hb_deep_log( 3, "dvdnav: Found candidate feature title %d duration %02d:%02d:%02d on button %d",
longest, hbtitle->hours, hbtitle->minutes,
hbtitle->seconds, button+1 );
}
}
return longest;
}
static int try_menu(
hb_dvdnav_t * d,
hb_list_t * list_title,
DVDMenuID_t menu,
uint64_t fallback_duration )
{
int result, event, len;
uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
int ii, jj;
int32_t cur_title, title, part;
uint64_t longest_duration = 0;
int longest = -1;
// A bit of a hack here. Abusing Escape menu to mean use whatever
// current menu is already set.
if ( menu != DVD_MENU_Escape )
{
result = dvdnav_menu_call( d->dvdnav, menu );
if ( result != DVDNAV_STATUS_OK )
{
// Sometimes the "first play" item doesn't initialize the
// initial VTS. So do it here.
dvdnav_title_play( d->dvdnav, 1 );
result = dvdnav_menu_call( d->dvdnav, menu );
if ( result != DVDNAV_STATUS_OK )
{
hb_error("dvdnav: Can not set dvd menu, %s", dvdnav_err_to_string(d->dvdnav));
goto done;
}
}
}
result = dvdnav_current_title_info( d->dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav title info: %s", dvdnav_err_to_string(d->dvdnav));
cur_title = title;
for (jj = 0; jj < 4; jj++)
{
for (ii = 0; ii < 4000; ii++)
{
result = dvdnav_get_next_block( d->dvdnav, buf, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
goto done;
}
switch ( event )
{
case DVDNAV_BLOCK_OK:
break;
case DVDNAV_CELL_CHANGE:
{
result = dvdnav_current_title_info( d->dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav title info: %s", dvdnav_err_to_string(d->dvdnav));
cur_title = title;
} break;
case DVDNAV_STILL_FRAME:
{
dvdnav_still_event_t *event;
event = (dvdnav_still_event_t*)buf;
dvdnav_still_skip( d->dvdnav );
if ( event->length == 255 )
{
// Infinite still. There won't be any menus after this.
goto done;
}
} break;
case DVDNAV_WAIT:
dvdnav_wait_skip( d->dvdnav );
break;
case DVDNAV_STOP:
goto done;
case DVDNAV_HOP_CHANNEL:
break;
case DVDNAV_NAV_PACKET:
{
pci_t *pci = dvdnav_get_current_nav_pci( d->dvdnav );
int kk;
int buttons;
if ( pci == NULL ) break;
buttons = pci->hli.hl_gi.btn_ns;
// If we are on a menu that has buttons and
// the button activation duration is long enough
// that this isn't another fake menu.
if ( cur_title == 0 && buttons > 0 &&
pci->hli.hl_gi.btn_se_e_ptm - pci->hli.hl_gi.hli_s_ptm >
15 * 90000 )
{
for (kk = 0; kk < buttons; kk++)
{
dvdnav_t *dvdnav_copy;
result = dvdnav_dup( &dvdnav_copy, d->dvdnav );
if (result != DVDNAV_STATUS_OK)
{
hb_log("dvdnav dup failed: %s", dvdnav_err_to_string(d->dvdnav));
goto done;
}
title = try_button( dvdnav_copy, kk, list_title );
dvdnav_free_dup( dvdnav_copy );
if ( title >= 0 )
{
hb_title_t * hbtitle;
int index;
index = find_title( list_title, title );
hbtitle = hb_list_item( list_title, index );
if ( hbtitle != NULL )
{
if ( hbtitle->duration > longest_duration )
{
longest_duration = hbtitle->duration;
longest = title;
if ((float)fallback_duration * 0.75 < longest_duration)
goto done;
}
}
}
}
goto done;
}
} break;
case DVDNAV_VTS_CHANGE:
{
result = dvdnav_current_title_info( d->dvdnav, &title, &part );
if (result != DVDNAV_STATUS_OK)
hb_log("dvdnav title info: %s", dvdnav_err_to_string(d->dvdnav));
cur_title = title;
} break;
case DVDNAV_HIGHLIGHT:
break;
case DVDNAV_AUDIO_STREAM_CHANGE:
break;
case DVDNAV_SPU_STREAM_CHANGE:
break;
case DVDNAV_SPU_CLUT_CHANGE:
break;
case DVDNAV_NOP:
break;
default:
break;
}
}
// Sometimes the menu is preceded by a intro that just
// gets restarted when hitting the menu button. So
// try skipping with the skip forward button. Then
// try hitting the menu again.
if ( !(jj & 1) )
{
dvdnav_next_pg_search( d->dvdnav );
}
else
{
dvdnav_menu_call( d->dvdnav, menu );
}
}
done:
return longest;
}
static int hb_dvdnav_main_feature( hb_dvd_t * e, hb_list_t * list_title )
{
hb_dvdnav_t * d = &(e->dvdnav);
int longest_root = -1;
int longest_title = -1;
int longest_fallback = 0;
int ii;
uint64_t longest_duration_root = 0;
uint64_t longest_duration_title = 0;
uint64_t longest_duration_fallback = 0;
uint64_t avg_duration = 0;
int avg_cnt = 0;
hb_title_t * title;
int index;
hb_deep_log( 2, "dvdnav: Searching menus for main feature" );
for ( ii = 0; ii < hb_list_count( list_title ); ii++ )
{
title = hb_list_item( list_title, ii );
if ( title->duration > longest_duration_fallback )
{
longest_duration_fallback = title->duration;
longest_fallback = title->index;
}
if ( title->duration > 90000LL * 60 * 30 )
{
avg_duration += title->duration;
avg_cnt++;
}
}
if ( avg_cnt )
avg_duration /= avg_cnt;
index = find_title( list_title, longest_fallback );
title = hb_list_item( list_title, index );
if ( title )
{
hb_deep_log( 2, "dvdnav: Longest title %d duration %02d:%02d:%02d",
longest_fallback, title->hours, title->minutes,
title->seconds );
}
dvdnav_reset( d->dvdnav );
if ( skip_to_menu( d->dvdnav, 2000 ) )
{
longest_root = try_menu( d, list_title, DVD_MENU_Escape, longest_duration_fallback );
if ( longest_root >= 0 )
{
index = find_title( list_title, longest_root );
title = hb_list_item( list_title, index );
if ( title )
{
longest_duration_root = title->duration;
hb_deep_log( 2, "dvdnav: Found first-play title %d duration %02d:%02d:%02d",
longest_root, title->hours, title->minutes, title->seconds );
}
}
else
{
hb_deep_log( 2, "dvdnav: No first-play menu title found" );
}
}
if ( longest_root < 0 ||
(float)longest_duration_fallback * 0.7 > longest_duration_root)
{
longest_root = try_menu( d, list_title, DVD_MENU_Root, longest_duration_fallback );
if ( longest_root >= 0 )
{
index = find_title( list_title, longest_root );
title = hb_list_item( list_title, index );
if ( title )
{
longest_duration_root = title->duration;
hb_deep_log( 2, "dvdnav: Found root title %d duration %02d:%02d:%02d",
longest_root, title->hours, title->minutes, title->seconds );
}
}
else
{
hb_deep_log( 2, "dvdnav: No root menu title found" );
}
}
if ( longest_root < 0 ||
(float)longest_duration_fallback * 0.7 > longest_duration_root)
{
longest_title = try_menu( d, list_title, DVD_MENU_Title, longest_duration_fallback );
if ( longest_title >= 0 )
{
index = find_title( list_title, longest_title );
title = hb_list_item( list_title, index );
if ( title )
{
longest_duration_title = title->duration;
hb_deep_log( 2, "dvdnav: found title %d duration %02d:%02d:%02d",
longest_title, title->hours, title->minutes,
title->seconds );
}
}
else
{
hb_deep_log( 2, "dvdnav: No title menu title found" );
}
}
uint64_t longest_duration;
int longest;
if ( longest_duration_root > longest_duration_title )
{
longest_duration = longest_duration_root;
longest = longest_root;
}
else
{
longest_duration = longest_duration_title;
longest = longest_title;
}
if ((float)longest_duration_fallback * 0.7 > longest_duration &&
longest_duration < 90000LL * 60 * 30 )
{
float factor = (float)avg_duration / longest_duration;
if ( factor > 1 )
factor = 1 / factor;
if ( avg_cnt > 10 && factor < 0.7 )
{
longest = longest_fallback;
hb_deep_log( 2, "dvdnav: Using longest title %d", longest );
}
}
return longest;
}
/***********************************************************************
* hb_dvdnav_start
***********************************************************************
* Title and chapter start at 1
**********************************************************************/
static int hb_dvdnav_start( hb_dvd_t * e, hb_title_t *title, int c )
{
hb_dvdnav_t * d = &(e->dvdnav);
int t = title->index;
hb_dvd_chapter_t *chapter;
dvdnav_status_t result;
if ( d->stopped && !hb_dvdnav_reset(d) )
{
return 0;
}
if (!TitleOpenIfo(d, t))
{
return 0;
}
dvdnav_reset( d->dvdnav );
chapter = hb_list_item( d->list_dvd_chapter, c - 1);
if (chapter != NULL)
result = dvdnav_program_play(d->dvdnav, t, chapter->pgcn, chapter->pgn);
else
result = dvdnav_part_play(d->dvdnav, t, 1);
if (result != DVDNAV_STATUS_OK)
{
hb_error( "dvd: dvdnav_*_play failed - %s",
dvdnav_err_to_string(d->dvdnav) );
return 0;
}
d->stopped = 0;
d->chapter = 0;
d->cell = 0;
return 1;
}
/***********************************************************************
* hb_dvdnav_stop
***********************************************************************
*
**********************************************************************/
static void hb_dvdnav_stop( hb_dvd_t * e )
{
}
/***********************************************************************
* hb_dvdnav_seek
***********************************************************************
*
**********************************************************************/
static int hb_dvdnav_seek( hb_dvd_t * e, float f )
{
hb_dvdnav_t * d = &(e->dvdnav);
uint64_t sector = f * d->title_block_count;
int result, event, len;
uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
int done = 0, ii;
if (d->stopped)
{
return 0;
}
// XXX the current version of libdvdnav can't seek outside the current
// PGC. Check if the place we're seeking to is in a different
// PGC. Position there & adjust the offset if so.
uint64_t pgc_offset = 0;
uint64_t chap_offset = 0;
hb_dvd_chapter_t *pgc_change = hb_list_item(d->list_dvd_chapter, 0 );
for ( ii = 0; ii < hb_list_count( d->list_dvd_chapter ); ++ii )
{
hb_dvd_chapter_t *chapter = hb_list_item( d->list_dvd_chapter, ii );
uint64_t chap_len = chapter->block_end - chapter->block_start + 1;
if ( chapter->pgcn != pgc_change->pgcn )
{
// this chapter's in a different pgc from the previous - note the
// change so we can make sector offset's be pgc relative.
pgc_offset = chap_offset;
pgc_change = chapter;
}
if ( chap_offset <= sector && sector < chap_offset + chap_len )
{
// this chapter contains the sector we want - see if it's in a
// different pgc than the one we're currently in.
int32_t title, pgcn, pgn;
if (dvdnav_current_title_program( d->dvdnav, &title, &pgcn, &pgn ) != DVDNAV_STATUS_OK)
hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
// If we find ourselves in a new title, it means a title
// transition was made while reading data. Jumping between
// titles can cause the vm to get into a bad state. So
// reset the vm in this case.
if ( d->title != title )
dvdnav_reset( d->dvdnav );
if ( d->title != title || chapter->pgcn != pgcn )
{
// this chapter is in a different pgc - switch to it.
if (dvdnav_program_play(d->dvdnav, d->title, chapter->pgcn, chapter->pgn) != DVDNAV_STATUS_OK)
hb_log("dvdnav prog play err: %s", dvdnav_err_to_string(d->dvdnav));
}
// seek sectors are pgc-relative so remove the pgc start sector.
sector -= pgc_offset;
break;
}
chap_offset += chap_len;
}
// dvdnav will not let you seek or poll current position
// till it reaches a certain point in parsing. so we
// have to get blocks until we reach a cell
// Put an arbitrary limit of 100 blocks on how long we search
for (ii = 0; ii < 100 && !done; ii++)
{
result = dvdnav_get_next_block( d->dvdnav, buf, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
return 0;
}
switch ( event )
{
case DVDNAV_BLOCK_OK:
case DVDNAV_CELL_CHANGE:
done = 1;
break;
case DVDNAV_STILL_FRAME:
dvdnav_still_skip( d->dvdnav );
break;
case DVDNAV_WAIT:
dvdnav_wait_skip( d->dvdnav );
break;
case DVDNAV_STOP:
hb_log("dvdnav: stop encountered during seek");
d->stopped = 1;
return 0;
case DVDNAV_HOP_CHANNEL:
case DVDNAV_NAV_PACKET:
case DVDNAV_VTS_CHANGE:
case DVDNAV_HIGHLIGHT:
case DVDNAV_AUDIO_STREAM_CHANGE:
case DVDNAV_SPU_STREAM_CHANGE:
case DVDNAV_SPU_CLUT_CHANGE:
case DVDNAV_NOP:
default:
break;
}
}
if (dvdnav_sector_search(d->dvdnav, sector, SEEK_SET) != DVDNAV_STATUS_OK)
{
hb_error( "dvd: dvdnav_sector_search failed - %s",
dvdnav_err_to_string(d->dvdnav) );
return 0;
}
d->chapter = 0;
d->cell = 0;
return 1;
}
/***********************************************************************
* hb_dvdnav_read
***********************************************************************
*
**********************************************************************/
static hb_buffer_t * hb_dvdnav_read( hb_dvd_t * e )
{
hb_dvdnav_t * d = &(e->dvdnav);
int result, event, len;
int chapter = 0;
int error_count = 0;
hb_buffer_t *b = hb_buffer_init( HB_DVD_READ_BUFFER_SIZE );
while ( 1 )
{
if (d->stopped)
{
hb_buffer_close( &b );
return NULL;
}
result = dvdnav_get_next_block( d->dvdnav, b->data, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
if (dvdnav_sector_search(d->dvdnav, 1, SEEK_CUR) != DVDNAV_STATUS_OK)
{
hb_error( "dvd: dvdnav_sector_search failed - %s",
dvdnav_err_to_string(d->dvdnav) );
hb_buffer_close( &b );
hb_set_work_error(d->h, HB_ERROR_READ);
return NULL;
}
error_count++;
if (error_count > 500)
{
hb_error("dvdnav: Error, too many consecutive read errors");
hb_buffer_close( &b );
hb_set_work_error(d->h, HB_ERROR_READ);
return NULL;
}
continue;
}
switch ( event )
{
case DVDNAV_BLOCK_OK:
// We have received a regular block of the currently playing
// MPEG stream.
b->s.new_chap = chapter;
return b;
case DVDNAV_NOP:
/*
* Nothing to do here.
*/
break;
case DVDNAV_STILL_FRAME:
/*
* We have reached a still frame. A real player application
* would wait the amount of time specified by the still's
* length while still handling user input to make menus and
* other interactive stills work. A length of 0xff means an
* indefinite still which has to be skipped indirectly by some
* user interaction.
*/
dvdnav_still_skip( d->dvdnav );
break;
case DVDNAV_WAIT:
/*
* We have reached a point in DVD playback, where timing is
* critical. Player application with internal fifos can
* introduce state inconsistencies, because libdvdnav is
* always the fifo's length ahead in the stream compared to
* what the application sees. Such applications should wait
* until their fifos are empty when they receive this type of
* event.
*/
dvdnav_wait_skip( d->dvdnav );
break;
case DVDNAV_SPU_CLUT_CHANGE:
/*
* Player applications should pass the new colour lookup table
* to their SPU decoder
*/
break;
case DVDNAV_SPU_STREAM_CHANGE:
/*
* Player applications should inform their SPU decoder to
* switch channels
*/
break;
case DVDNAV_AUDIO_STREAM_CHANGE:
/*
* Player applications should inform their audio decoder to
* switch channels
*/
break;
case DVDNAV_HIGHLIGHT:
/*
* Player applications should inform their overlay engine to
* highlight the given button
*/
break;
case DVDNAV_VTS_CHANGE:
/*
* Some status information like video aspect and video scale
* permissions do not change inside a VTS. Therefore this
* event can be used to query such information only when
* necessary and update the decoding/displaying accordingly.
*/
{
int tt = 0, pgcn = 0, pgn = 0;
dvdnav_current_title_program(d->dvdnav, &tt, &pgcn, &pgn);
if (tt != d->title)
{
// Transition to another title signals that we are done.
hb_buffer_close( &b );
hb_deep_log(2, "dvdnav: vts change, found next title");
if (error_count > 0)
{
// Last read attempt failed
hb_set_work_error(d->h, HB_ERROR_READ);
}
return NULL;
}
}
break;
case DVDNAV_CELL_CHANGE:
/*
* Some status information like the current Title and Part
* numbers do not change inside a cell. Therefore this event
* can be used to query such information only when necessary
* and update the decoding/displaying accordingly.
*/
{
dvdnav_cell_change_event_t * cell_event;
int tt = 0, pgcn = 0, pgn = 0, c;
cell_event = (dvdnav_cell_change_event_t*)b->data;
dvdnav_current_title_program(d->dvdnav, &tt, &pgcn, &pgn);
if (tt != d->title)
{
// Transition to another title signals that we are done.
hb_buffer_close( &b );
hb_deep_log(2, "dvdnav: cell change, found next title");
if (error_count > 0)
{
// Last read attempt failed
hb_set_work_error(d->h, HB_ERROR_READ);
}
return NULL;
}
c = FindChapterIndex(d->list_dvd_chapter, pgcn, pgn);
if (c != d->chapter)
{
if (c < d->chapter)
{
// Some titles end with a 'link' back to the beginning so
// a transition to an earlier chapter means we're done.
hb_buffer_close( &b );
hb_deep_log(2, "dvdnav: cell change, previous chapter");
if (error_count > 0)
{
// Last read attempt failed
hb_set_work_error(d->h, HB_ERROR_READ);
}
return NULL;
}
chapter = d->chapter = c;
}
else if ( cell_event->cellN <= d->cell )
{
hb_buffer_close( &b );
hb_deep_log(2, "dvdnav: cell change, previous cell");
if (error_count > 0)
{
// Last read attempt failed
hb_set_work_error(d->h, HB_ERROR_READ);
}
return NULL;
}
d->cell = cell_event->cellN;
}
break;
case DVDNAV_NAV_PACKET:
/*
* A NAV packet provides PTS discontinuity information, angle
* linking information and button definitions for DVD menus.
* Angles are handled completely inside libdvdnav. For the
* menus to work, the NAV packet information has to be passed
* to the overlay engine of the player so that it knows the
* dimensions of the button areas.
*/
// mpegdemux expects to get these. I don't think it does
// anything useful with them however.
b->s.new_chap = chapter;
return b;
break;
case DVDNAV_HOP_CHANNEL:
/*
* This event is issued whenever a non-seamless operation has
* been executed. Applications with fifos should drop the
* fifos content to speed up responsiveness.
*/
break;
case DVDNAV_STOP:
/*
* Playback should end here.
*/
d->stopped = 1;
hb_buffer_close( &b );
hb_deep_log(2, "dvdnav: stop");
if (error_count > 0)
{
// Last read attempt failed
hb_set_work_error(d->h, HB_ERROR_READ);
}
return NULL;
default:
break;
}
}
hb_buffer_close( &b );
return NULL;
}
/***********************************************************************
* hb_dvdnav_chapter
***********************************************************************
* Returns in which chapter the next block to be read is.
* Chapter numbers start at 1.
**********************************************************************/
static int hb_dvdnav_chapter( hb_dvd_t * e )
{
hb_dvdnav_t * d = &(e->dvdnav);
int32_t t, pgcn, pgn;
int32_t c;
if (dvdnav_current_title_program(d->dvdnav, &t, &pgcn, &pgn) != DVDNAV_STATUS_OK)
{
return -1;
}
c = FindChapterIndex( d->list_dvd_chapter, pgcn, pgn );
return c;
}
/***********************************************************************
* hb_dvdnav_close
***********************************************************************
* Closes and frees everything
**********************************************************************/
static void hb_dvdnav_close( hb_dvd_t ** _d )
{
hb_dvdnav_t * d = &((*_d)->dvdnav);
if (d->dvdnav) dvdnav_close( d->dvdnav );
if (d->vmg) ifoClose( d->vmg );
TitleCloseIfo(d);
if (d->reader) DVDClose( d->reader );
free(d->path);
free( d );
*_d = NULL;
}
/***********************************************************************
* hb_dvdnav_angle_count
***********************************************************************
* Returns the number of angles supported.
**********************************************************************/
static int hb_dvdnav_angle_count( hb_dvd_t * e )
{
hb_dvdnav_t * d = &(e->dvdnav);
int current, angle_count;
if (dvdnav_get_angle_info( d->dvdnav, &current, &angle_count) != DVDNAV_STATUS_OK)
{
hb_log("dvdnav_get_angle_info %s", dvdnav_err_to_string(d->dvdnav));
angle_count = 1;
}
return angle_count;
}
/***********************************************************************
* hb_dvdnav_set_angle
***********************************************************************
* Sets the angle to read
**********************************************************************/
static void hb_dvdnav_set_angle( hb_dvd_t * e, int angle )
{
hb_dvdnav_t * d = &(e->dvdnav);
if (dvdnav_angle_change( d->dvdnav, angle) != DVDNAV_STATUS_OK)
{
hb_log("dvdnav_angle_change %s", dvdnav_err_to_string(d->dvdnav));
}
}
/***********************************************************************
* FindChapterIndex
***********************************************************************
* Assumes pgc and cell_cur are correctly set, and sets cell_next to the
* cell to be read when we will be done with cell_cur.
**********************************************************************/
static int FindChapterIndex( hb_list_t * list, int pgcn, int pgn )
{
int count, ii;
hb_dvd_chapter_t * chapter;
count = hb_list_count( list );
for (ii = 0; ii < count; ii++)
{
chapter = hb_list_item( list, ii );
if (chapter->pgcn == pgcn && chapter->pgn == pgn)
return chapter->index;
}
return 0;
}
/***********************************************************************
* FindNextCell
***********************************************************************
* Assumes pgc and cell_cur are correctly set, and sets cell_next to the
* cell to be read when we will be done with cell_cur.
**********************************************************************/
static int FindNextCell( pgc_t *pgc, int cell_cur )
{
int i = 0;
int cell_next;
if( pgc->cell_playback[cell_cur].block_type ==
BLOCK_TYPE_ANGLE_BLOCK )
{
while( pgc->cell_playback[cell_cur+i].block_mode !=
BLOCK_MODE_LAST_CELL )
{
i++;
}
cell_next = cell_cur + i + 1;
hb_log( "dvd: Skipping multi-angle cells %d-%d",
cell_cur,
cell_next - 1 );
}
else
{
cell_next = cell_cur + 1;
}
return cell_next;
}
/***********************************************************************
* NextPgcn
***********************************************************************
* Assumes pgc and cell_cur are correctly set, and sets cell_next to the
* cell to be read when we will be done with cell_cur.
* Since pg chains can be circularly linked (either from a read error or
* deliberately) pgcn_map tracks program chains we've already seen.
**********************************************************************/
static int NextPgcn( ifo_handle_t *ifo, int pgcn, uint32_t pgcn_map[MAX_PGCN/32] )
{
int next_pgcn;
pgc_t *pgc;
pgcn_map[pgcn >> 5] |= (1 << (pgcn & 31));
pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
next_pgcn = pgc->next_pgc_nr;
if ( next_pgcn < 1 || next_pgcn >= MAX_PGCN || next_pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
return 0;
return pgcn_map[next_pgcn >> 5] & (1 << (next_pgcn & 31))? 0 : next_pgcn;
}
/***********************************************************************
* PgcWalkInit
***********************************************************************
* Pgc links can loop. I track which have been visited in a bit vector
* Initialize the bit vector to empty.
**********************************************************************/
static void PgcWalkInit( uint32_t pgcn_map[MAX_PGCN/32] )
{
memset(pgcn_map, 0, sizeof(uint32_t) * MAX_PGCN/32);
}
/***********************************************************************
* dvdtime2msec
***********************************************************************
* From lsdvd
**********************************************************************/
static int dvdtime2msec(dvd_time_t * dt)
{
double frames_per_s[4] = {-1.0, 25.00, -1.0, 29.97};
double fps = frames_per_s[(dt->frame_u & 0xc0) >> 6];
long ms;
ms = (((dt->hour & 0xf0) >> 3) * 5 + (dt->hour & 0x0f)) * 3600000;
ms += (((dt->minute & 0xf0) >> 3) * 5 + (dt->minute & 0x0f)) * 60000;
ms += (((dt->second & 0xf0) >> 3) * 5 + (dt->second & 0x0f)) * 1000;
if( fps > 0 )
{
ms += (((dt->frame_u & 0x30) >> 3) * 5 +
(dt->frame_u & 0x0f)) * 1000.0 / fps;
}
return ms;
}
static int TitleOpenIfo(hb_dvdnav_t * d, int t)
{
int pgcn, pgn, pgcn_end, i, c;
pgc_t * pgc;
int cell_cur;
hb_dvd_chapter_t * dvd_chapter;
uint64_t duration;
if (d->title == t && d->ifo != NULL)
{
// Already opened
return 0;
}
// Close previous if open
TitleCloseIfo(d);
/* VTS which our title is in */
d->vts = d->vmg->tt_srpt->title[t-1].title_set_nr;
if (!d->vts)
{
/* A VTS of 0 means the title wasn't found in the title set */
hb_log("Invalid VTS (title set) number: %i", d->vts);
goto fail;
}
if(!(d->ifo = ifoOpen(d->reader, d->vts)))
{
hb_log( "ifoOpen failed" );
goto fail;
}
int title_ttn = d->vmg->tt_srpt->title[t-1].vts_ttn;
if ( title_ttn < 1 || title_ttn > d->ifo->vts_ptt_srpt->nr_of_srpts )
{
hb_log( "invalid VTS PTT offset %d for title %d, skipping", title_ttn, t );
goto fail;
}
d->duration = 0LL;
d->pgcn = -1;
d->pgn = 1;
for (i = 0; i < d->ifo->vts_ptt_srpt->title[title_ttn-1].nr_of_ptts; i++)
{
int blocks = 0;
duration = PttDuration(d->ifo, title_ttn, i+1, &blocks, &pgcn_end);
pgcn = d->ifo->vts_ptt_srpt->title[title_ttn-1].ptt[i].pgcn;
pgn = d->ifo->vts_ptt_srpt->title[title_ttn-1].ptt[i].pgn;
if (duration > d->duration)
{
d->pgcn = pgcn;
d->pgn = pgn;
d->duration = duration;
d->title_block_count = blocks;
}
else if (pgcn == d->pgcn && pgn < d->pgn)
{
d->pgn = pgn;
d->title_block_count = blocks;
}
}
/* Check pgc */
if ( d->pgcn < 1 || d->pgcn > d->ifo->vts_pgcit->nr_of_pgci_srp || d->pgcn >= MAX_PGCN)
{
hb_log( "invalid PGC ID %d for title %d, skipping", d->pgcn, t );
goto fail;
}
/* Chapters */
d->list_dvd_chapter = hb_list_init();
uint32_t pgcn_map[MAX_PGCN/32];
PgcWalkInit( pgcn_map );
pgcn = d->pgcn;
pgn = d->pgn;
c = 0;
do
{
pgc = d->ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
for (i = pgn; i <= pgc->nr_of_programs; i++)
{
int cell_start, cell_end;
dvd_chapter = calloc(sizeof(hb_dvd_chapter_t), 1);
dvd_chapter->pgcn = pgcn;
dvd_chapter->pgn = i;
dvd_chapter->index = c + 1;
cell_start = pgc->program_map[i-1] - 1;
dvd_chapter->block_start = pgc->cell_playback[cell_start].first_sector;
// if there are no more programs in this pgc, the end cell is the
// last cell. Otherwise it's the cell before the start cell of the
// next program.
if (i == pgc->nr_of_programs)
{
cell_end = pgc->nr_of_cells - 1;
}
else
{
cell_end = pgc->program_map[i] - 2;
}
dvd_chapter->block_end = pgc->cell_playback[cell_end].last_sector;
/* duration */
dvd_chapter->duration = 0;
cell_cur = cell_start;
while( cell_cur <= cell_end )
{
#define cp pgc->cell_playback[cell_cur]
dvd_chapter->duration += 90LL * dvdtime2msec(&cp.playback_time);
#undef cp
cell_cur = FindNextCell( pgc, cell_cur );
}
hb_list_add(d->list_dvd_chapter, dvd_chapter);
c++;
}
pgn = 1;
} while ((pgcn = NextPgcn(d->ifo, pgcn, pgcn_map)) != 0);
d->title = t;
return 1;
fail:
TitleCloseIfo(d);
return 0;
}
static void TitleCloseIfo(hb_dvdnav_t * d)
{
hb_dvd_chapter_t * chapter;
while ((chapter = hb_list_item(d->list_dvd_chapter, 0)))
{
hb_list_rem(d->list_dvd_chapter, chapter );
free(chapter);
}
hb_list_close(&d->list_dvd_chapter);
if (d->ifo)
{
ifoClose(d->ifo);
}
d->ifo = NULL;
d->title = 0;
d->vts = 0;
d->pgcn = 0;
d->pgn = 0;
}