[FFmpeg-devel] [PATCH 2/3] avformat/dvdvideodec: add menu demuxing support

Marth64 marth64 at proxyid.net
Wed Mar 6 18:35:55 EET 2024


Thank you Stefano for the review on the whole set. I will take the feedback
and come back with a better organized set in the next 2 or so days.

On Wed, Mar 6, 2024 at 09:42 Stefano Sabatini <stefasab at gmail.com> wrote:

> On date Wednesday 2024-03-06 01:19:12 -0600, Marth64 wrote:
> > Many DVDs have valuable assets in their menu structures, including
> background
> > video or music. Some discs also abuse the menu feature to include actual
> > feature footage that needs to change aspect ratio (in order to trick the
> DVD player).
> >
> > This patch allows extraction and archival of these assets, but does not
> enable
> > controllable playback (which needs a full-fledged player and nav VM).
> > Menus are processed directly with dvdread and the existing foundation of
> the demuxer.
> >
> > Will eventually add option to list their coordinates as well, so users
> > do not have to rely on other tools to find them.
> >
> > Signed-off-by: Marth64 <marth64 at proxyid.net>
> > ---
> >  doc/demuxers.texi         |  43 +++++-
> >  libavformat/dvdvideodec.c | 315 ++++++++++++++++++++++++++++++++++++--
> >  2 files changed, 339 insertions(+), 19 deletions(-)
> >
> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> > index 1a17c6db16..e2ea66c1a5 100644
> > --- a/doc/demuxers.texi
> > +++ b/doc/demuxers.texi
> > @@ -289,8 +289,10 @@ This demuxer accepts the following option:
> >
> >  DVD-Video demuxer, powered by libdvdnav and libdvdread.
> >
> > -Can directly ingest DVD titles, specifically sequential PGCs,
> > -into a conversion pipeline. Menus and seeking are not supported at this
> time.
> > +Can directly ingest DVD titles, specifically sequential PGCs, into
> > +a conversion pipeline. Menu assets, such as background video or audio,
> > +can also be demuxed given the menu's coordinates (at best effort).
> > +Seeking is not supported at this time.
> >
> >  Block devices (DVD drives), ISO files, and directory structures are
> accepted.
> >  Activate with @code{-f dvdvideo} in front of one of these inputs.
> > @@ -347,37 +349,56 @@ This demuxer accepts the following options:
> >
> >  @item title @var{int}
> >  The title number to play. Must be set if @option{pgc} and @option{pg}
> are not set.
> > +Not applicable to menus.
> >  Default is 0 (auto), which currently only selects the first available
> title (title 1)
> >  and notifies the user about the implications.
> >
> >  @item chapter_start @var{int}
> > -The chapter, or PTT (part-of-title), number to start at. Default is 1.
> > +The chapter, or PTT (part-of-title), number to start at. Not applicable
> to menus.
> > +Default is 1.
> >
> >  @item chapter_end @var{int}
> > -The chapter, or PTT (part-of-title), number to end at. Default is 0,
> > -which is a special value to signal end at the last possible chapter.
> > +The chapter, or PTT (part-of-title), number to end at. Not applicable
> to menus.
> > +Default is 0, which is a special value to signal end at the last
> possible chapter.
> >
> >  @item angle @var{int}
> >  The video angle number, referring to what is essentially an additional
> >  video stream that is composed from alternate frames interleaved in the
> VOBs.
> > +Not applicable to menus.
> >  Default is 1.
> >
> >  @item region @var{int}
> >  The region code to use for playback. Some discs may use this to default
> playback
> >  at a particular angle in different regions. This option will not affect
> the region code
> > -of a real DVD drive, if used as an input. Default is 0, "world".
> > +of a real DVD drive, if used as an input. Not applicable to menus.
> > +Default is 0, "world".
> > +
>
> > + at item menu @var{bool}
> > +Demux menu assets instead of navigating a title. Requires exact
> coordinates
> > +of the menu (@option{menu_lu}, @option{menu_vts}, @option{pgc},
> @option{pg}).
> > +Default is false.
> > +
>
> > + at item menu_lu @var{int}
> > +The menu language to demux. In DVD, menus are grouped by language.
> > +Default is 0, the first language unit.
> > +
> > + at item menu_vts @var{int}
> > +The VTS where the menu lives, or 0 if it is a VMG menu (root-level).
> > +Default is 0, VMG menu.
> >
> >  @item pgc @var{int}
> >  The entry PGC to start playback, in conjunction with @option{pg}.
> >  Alternative to setting @option{title}.
> >  Chapter markers are not supported at this time.
> > +Must be explicitly set for menus.
> >  Default is 0, automatically resolve from value of @option{title}.
> >
> >  @item pg @var{int}
> >  The entry PG to start playback, in conjunction with @option{pgc}.
> >  Alternative to setting @option{title}.
> >  Chapter markers are not supported at this time.
> > -Default is 0, automatically resolve from value of @option{title}.
> > +Default is 0, automatically resolve from value of @option{title}, or
> > +start from the beginning (PG 1) of the menu.
> >
> >  @item preindex @var{bool}
> >  Enable this to have accurate chapter (PTT) markers and duration
> measurement,
> > @@ -385,6 +406,7 @@ which requires a slow second pass read in order to
> index the chapter marker
> >  timestamps from NAV packets. This is non-ideal extra work for real
> optical drives.
> >  It is recommended and faster to use this option with a backup of the
> DVD structure
> >  stored on a hard drive. Not compatible with @option{pgc} and
> @option{pg}.
> > +Not applicable to menus.
> >  Default is 0, false.
> >
> >  @item trim @var{bool}
> > @@ -392,6 +414,7 @@ Skip padding cells (i.e. cells shorter than 1
> second) from the beginning.
> >  There exist many discs with filler segments at the beginning of the PGC,
> >  often with junk data intended for controlling a real DVD player's
> >  buffering speed and with no other material data value.
> > +Not applicable to menus.
> >  Default is 1, true.
> >
> >  @item clut_rgb @var{bool}
> > @@ -421,6 +444,12 @@ Open only chapter 5 from title 1 from a given DVD
> structure:
> >  @example
> >  ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to
> DVD> ...
> >  @end example
> > +
> > + at item
> > +Demux menu with language 1 from VTS 1, PGC 1, starting at PG 1:
> > + at example
> > +ffmpeg -f dvdvideo -menu 1 -menu_lu 1 -menu_vts 1 -pgc 1 -pg 1 -i <path
> to DVD> ...
> > + at end example
> >  @end itemize
> >
> >  @section ea
> > diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
> > index 3bc76f5c65..2c7ffdd148 100644
> > --- a/libavformat/dvdvideodec.c
> > +++ b/libavformat/dvdvideodec.c
> > @@ -57,9 +57,11 @@
> >  #define DVDVIDEO_BLOCK_SIZE                             2048
> >  #define DVDVIDEO_TIME_BASE_Q                            (AVRational) {
> 1, 90000 }
> >  #define DVDVIDEO_PTS_WRAP_BITS                          64 /* VOBUs use
> 32 (PES allows 33) */
> > -
> >  #define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE                1024
> >
> > +#define PCI_START_BYTE                                  45 /*
> complement dvdread's DSI_START_BYTE */
> > +static const uint8_t dvdvideo_nav_header[4] =           { 0x00, 0x00,
> 0x01, 0xBF };
> > +
> >  enum DVDVideoSubpictureViewport {
> >      DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> >      DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> > @@ -121,6 +123,15 @@ typedef struct DVDVideoPlaybackState {
> >      uint64_t                    *pgc_pg_times_est;  /* PG start times
> as reported by IFO */
> >      pgc_t                       *pgc;               /* handle to the
> active PGC */
> >      dvdnav_t                    *dvdnav;            /* handle to the
> dvdnav VM */
> > +
> > +    /* the following fields are only used for menu playback */
> > +    int                         celln_start;        /* starting cell
> number */
> > +    int                         celln_end;          /* ending cell
> number */
> > +    int                         sector_offset;      /* current sector
> relative to the current VOB */
> > +    uint32_t                    sector_end;         /* end sector
> relative to the current VOBU */
> > +    uint32_t                    vobu_next;          /* the next VOBU
> pointer */
> > +    uint32_t                    vobu_remaining;     /* remaining blocks
> for current VOBU */
> > +    dvd_file_t                  *vob_file;          /* handle to the
> menu VOB (VMG or VTS) */
> >  } DVDVideoPlaybackState;
> >
> >  typedef struct DVDVideoDemuxContext {
> > @@ -131,6 +142,9 @@ typedef struct DVDVideoDemuxContext {
> >      int                         opt_chapter_end;    /* the
> user-provided exit PTT (0 for last) */
> >      int                         opt_chapter_start;  /* the
> user-provided entry PTT (1-indexed) */
> >      int                         opt_clut_rgb;       /* output subtitle
> palette (CLUT) as RGB */
> > +    int                         opt_menu;           /* demux menu
> domain instead of title domain */
> > +    int                         opt_menu_lu;        /* the menu
> language unit (logical grouping) */
> > +    int                         opt_menu_vts;       /* the menu VTS, or
> 0 for VMG (main menu) */
> >      int                         opt_pg;             /* the
> user-provided PG number (1-indexed) */
> >      int                         opt_pgc;            /* the
> user-provided PGC number (1-indexed) */
> >      int                         opt_preindex;       /* pre-indexing
> mode (2-pass read) */
> > @@ -227,6 +241,16 @@ static int dvdvideo_ifo_open(AVFormatContext *s)
> >          return AVERROR_EXTERNAL;
> >      }
> >
> > +    if (c->opt_menu) {
> > +        if (c->opt_menu_vts > 0 && !(c->vts_ifo = ifoOpen(c->dvdread,
> c->opt_menu_vts))) {
> > +            av_log(s, AV_LOG_ERROR, "Unable to open IFO structure for
> VTS %d\n", c->opt_menu_vts);
> > +
> > +            return AVERROR_EXTERNAL;
> > +        }
> > +
> > +        return 0;
> > +    }
> > +
> >      if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> >          av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> >
> > @@ -290,6 +314,182 @@ static int
> dvdvideo_is_pgc_promising(AVFormatContext *s, pgc_t *pgc)
> >      return 0;
> >  }
> >
> > +static void dvdvideo_menu_close(AVFormatContext *s,
> DVDVideoPlaybackState *state)
> > +{
> > +    if (state->vob_file)
> > +        DVDCloseFile(state->vob_file);
> > +}
> > +
> > +static int dvdvideo_menu_open(AVFormatContext *s, DVDVideoPlaybackState
> *state)
> > +{
> > +    DVDVideoDemuxContext *c = s->priv_data;
> > +    pgci_ut_t *pgci_ut;
> > +
> > +    pgci_ut = c->opt_menu_vts ? c->vts_ifo->pgci_ut :
> c->vmg_ifo->pgci_ut;
> > +    if (!pgci_ut) {
> > +        av_log(s, AV_LOG_ERROR, "Invalid PGC table for menu [LU %d, PGC
> %d]\n",
> > +                                c->opt_menu_lu, c->opt_pgc);
> > +
> > +        return AVERROR_INVALIDDATA;
> > +    }
> > +
> > +    if (c->opt_pgc < 1                      ||
> > +        c->opt_menu_lu < 1                  ||
> > +        c->opt_menu_lu > pgci_ut->nr_of_lus ||
> > +        c->opt_pgc > pgci_ut->lu[c->opt_menu_lu -
> 1].pgcit->nr_of_pgci_srp) {
> > +
> > +        av_log(s, AV_LOG_ERROR, "Menu [LU %d, PGC %d] not found\n",
> c->opt_menu_lu, c->opt_pgc);
> > +
> > +        return AVERROR(EINVAL);
> > +    }
> > +
> > +    /* make sure the PGC is valid */
> > +    state->pgcn          = c->opt_pgc - 1;
> > +    state->pgc           = pgci_ut->lu[c->opt_menu_lu -
> 1].pgcit->pgci_srp[c->opt_pgc - 1].pgc;
> > +
> > +    if (!state->pgc || !state->pgc->program_map ||
> !state->pgc->cell_playback) {
> > +        av_log(s, AV_LOG_ERROR, "Invalid PGC structure for menu [LU %d,
> PGC %d]\n",
> > +                                c->opt_menu_lu, c->opt_pgc);
> > +
> > +        return AVERROR_INVALIDDATA;
> > +    }
> > +
> > +    /* make sure the PG is valid */
> > +    state->entry_pgn     = c->opt_pg;
> > +    if (state->entry_pgn < 1 || state->entry_pgn >
> state->pgc->nr_of_programs) {
> > +        av_log(s, AV_LOG_ERROR, "Entry PG %d not found\n",
> state->entry_pgn);
> > +
> > +        return AVERROR(EINVAL);
> > +    }
> > +
> > +    /* make sure the program map isn't leading us to nowhere */
> > +    state->celln_start   = state->pgc->program_map[state->entry_pgn -
> 1];
> > +    state->celln_end     = state->pgc->nr_of_cells;
> > +    state->celln         = state->celln_start;
> > +    if (state->celln_start > state->pgc->nr_of_cells) {
> > +        av_log(s, AV_LOG_ERROR, "Invalid PGC structure: program map
> points to unknown cell\n");
> > +
> > +        return AVERROR_INVALIDDATA;
> > +    }
> > +
> > +    state->sector_end    = state->pgc->cell_playback[state->celln -
> 1].last_sector;
> > +    state->vobu_next     = state->pgc->cell_playback[state->celln -
> 1].first_sector;
> > +    state->sector_offset = state->vobu_next;
> > +
> > +    if (c->opt_menu_vts > 0)
> > +        state->in_vts    = 1;
> > +
> > +    if (!(state->vob_file = DVDOpenFile(c->dvdread, c->opt_menu_vts,
> DVD_READ_MENU_VOBS))) {
> > +        av_log(s, AV_LOG_ERROR, !c->opt_menu_vts ?
> > +                                "Unable to open main menu VOB
> (VIDEO_TS.VOB)\n" :
> > +                                "Unable to open menu VOBs for VTS
> %d\n", c->opt_menu_vts);
> > +
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static int dvdvideo_menu_next_ps_block(AVFormatContext *s,
> DVDVideoPlaybackState *state,
> > +                                       uint8_t *buf, int buf_size,
> > +                                       void (*flush_cb)(AVFormatContext
> *s))
> > +{
> > +    ssize_t blocks_read                   = 0;
> > +    uint8_t read_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> > +    pci_t pci                             = (pci_t) {0};
> > +    dsi_t dsi                             = (dsi_t) {0};
> > +
> > +    if (buf_size != DVDVIDEO_BLOCK_SIZE) {
> > +        av_log(s, AV_LOG_ERROR, "Invalid buffer size (expected=%d
> actual=%d)\n",
> > +                                DVDVIDEO_BLOCK_SIZE, buf_size);
> > +
> > +        return AVERROR(EINVAL);
> > +    }
> > +
> > +    /* we were at the end of a vobu, so now go to the next one or EOF */
> > +    if (!state->vobu_remaining && state->in_pgc) {
> > +        if (state->vobu_next == SRI_END_OF_CELL) {
> > +            if (state->celln == state->celln_end &&
> state->sector_offset > state->sector_end)
> > +                return AVERROR_EOF;
> > +
> > +            state->celln++;
> > +            state->sector_offset =
> state->pgc->cell_playback[state->celln - 1].first_sector;
> > +            state->sector_end    =
> state->pgc->cell_playback[state->celln - 1].last_sector;
> > +        } else {
> > +            state->sector_offset = state->vobu_next;
> > +        }
> > +    }
> > +
> > +    /* continue reading the VOBU */
> > +    av_log(s, AV_LOG_TRACE, "reading block at offset %d\n",
> state->sector_offset);
> > +
> > +    blocks_read = DVDReadBlocks(state->vob_file, state->sector_offset,
> 1, read_buf);
>
> > +    if (blocks_read != 1) {
> > +        av_log(s, AV_LOG_ERROR, "Unable to read VOB block at offset
> %d\n", state->sector_offset);
>
> nit: you might show blocks_read in case it is an error message to aid
> debugging
>
> > +
> > +        return AVERROR_INVALIDDATA;
> > +    }
> > +
> > +    /* we are at the start of a VOBU, so we are expecting a NAV packet
> */
> > +    if (!state->vobu_remaining) {
> > +        if (!memcmp(&read_buf[PCI_START_BYTE - 4], dvdvideo_nav_header,
> 4) ||
> > +            !memcmp(&read_buf[DSI_START_BYTE - 4], dvdvideo_nav_header,
> 4) ||
> > +            read_buf[PCI_START_BYTE - 1] != 0x00
>    ||
> > +            read_buf[DSI_START_BYTE - 1] != 0x01) {
> > +
> > +            av_log(s, AV_LOG_ERROR, "Invalid NAV packet at offset %d:
> PCI or DSI header mismatch\n",
> > +                                    state->sector_offset);
> > +
> > +            return AVERROR_INVALIDDATA;
> > +        }
> > +
> > +        navRead_PCI(&pci, &read_buf[PCI_START_BYTE]);
> > +        navRead_DSI(&dsi, &read_buf[DSI_START_BYTE]);
> > +
> > +        if (!pci.pci_gi.vobu_s_ptm                          ||
> > +            !pci.pci_gi.vobu_e_ptm                          ||
> > +            pci.pci_gi.vobu_s_ptm > pci.pci_gi.vobu_e_ptm) {
> > +
> > +            av_log(s, AV_LOG_ERROR, "Invalid NAV packet at offset %d:
> PCI header is invalid\n",
> > +                                    state->sector_offset);
> > +
> > +            return AVERROR_INVALIDDATA;
> > +        }
> > +
> > +        state->vobu_remaining    = dsi.dsi_gi.vobu_ea;
> > +        state->vobu_next         = dsi.vobu_sri.next_vobu ==
> SRI_END_OF_CELL ? SRI_END_OF_CELL :
> > +                                   dsi.dsi_gi.nv_pck_lbn +
> (dsi.vobu_sri.next_vobu & 0x7FFFFFFF);
> > +        state->sector_offset++;
> > +
> > +        if (state->in_pgc) {
> > +            if (state->vobu_e_ptm != pci.pci_gi.vobu_s_ptm) {
> > +                if (flush_cb)
> > +                    flush_cb(s);
> > +
> > +                state->ts_offset += state->vobu_e_ptm -
> pci.pci_gi.vobu_s_ptm;
> > +            }
> > +        } else {
> > +            state->in_pgc        = 1;
> > +        }
> > +
> > +        state->vobu_e_ptm        = pci.pci_gi.vobu_e_ptm;
> > +
> > +        av_log(s, AV_LOG_DEBUG, "NAV packet: sector=%d "
> > +                                "vobu_s_ptm=%d vobu_e_ptm=%d
> ts_offset=%ld\n",
> > +                                dsi.dsi_gi.nv_pck_lbn,
> > +                                pci.pci_gi.vobu_s_ptm,
> pci.pci_gi.vobu_e_ptm, state->ts_offset);
> > +
> > +        return FFERROR_REDO;
> > +    }
> > +
> > +    /* we are in the middle of a VOBU, so pass on the PS packet */
> > +    memcpy(buf, &read_buf, DVDVIDEO_BLOCK_SIZE);
> > +    state->sector_offset++;
> > +    state->vobu_remaining--;
> > +
> > +    return DVDVIDEO_BLOCK_SIZE;
> > +}
> > +
> >  static void dvdvideo_play_close(AVFormatContext *s,
> DVDVideoPlaybackState *state)
> >  {
> >      if (!state->dvdnav)
> > @@ -716,7 +916,7 @@ static int
> dvdvideo_chapters_setup_preindex(AVFormatContext *s)
> >          goto end_close;
> >
> >      av_log(s, AV_LOG_INFO,
> > -           "Indexing chapter markers, this will take a long time.
> Please wait...\n");
>
> > +           "Indexing chapter markers, this may take a long time. Please
> wait...\n");
>
> nit++: unrelated
>
> >
> >      while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
> >          ret = dvdvideo_play_next_ps_block(s, &state, nav_buf,
> DVDVIDEO_BLOCK_SIZE,
> > @@ -858,8 +1058,15 @@ static int
> dvdvideo_video_stream_setup(AVFormatContext *s)
> >
> >      int ret = 0;
> >      DVDVideoVTSVideoStreamEntry entry = {0};
> > +    video_attr_t video_attr;
> >
> > -    if ((ret = dvdvideo_video_stream_analyze(s,
> c->vts_ifo->vtsi_mat->vts_video_attr, &entry)) < 0 ||
> > +    if (c->opt_menu)
> > +        video_attr = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->vmgm_video_attr :
> > +
> c->vts_ifo->vtsi_mat->vtsm_video_attr;
> > +    else
> > +        video_attr = c->vts_ifo->vtsi_mat->vts_video_attr;
> > +
> > +    if ((ret = dvdvideo_video_stream_analyze(s, video_attr, &entry)) <
> 0 ||
> >          (ret = dvdvideo_video_stream_add(s, &entry,
> AVSTREAM_PARSE_HEADERS)) < 0) {
> >
> >          av_log(s, AV_LOG_ERROR, "Unable to add video stream\n");
> > @@ -1009,15 +1216,29 @@ static int
> dvdvideo_audio_stream_add_all(AVFormatContext *s)
> >      DVDVideoDemuxContext *c = s->priv_data;
> >
> >      int ret = 0;
> > +    int nb_streams;
> >
> > -    for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams;
> i++) {
> > +    if (c->opt_menu)
> > +        nb_streams = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->nr_of_vmgm_audio_streams :
> > +
> c->vts_ifo->vtsi_mat->nr_of_vtsm_audio_streams;
> > +    else
> > +        nb_streams = c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams;
> > +
> > +    for (int i = 0; i < nb_streams; i++) {
> >          DVDVideoPGCAudioStreamEntry entry = {0};
> > +        audio_attr_t audio_attr;
> > +
> > +        if (c->opt_menu)
> > +            audio_attr = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->vmgm_audio_attr :
> > +
> c->vts_ifo->vtsi_mat->vtsm_audio_attr;
> > +        else
> > +            audio_attr = c->vts_ifo->vtsi_mat->vts_audio_attr[i];
> >
> >          if (!(c->play_state.pgc->audio_control[i] & 0x8000))
> >              continue;
> >
> > -        if ((ret = dvdvideo_audio_stream_analyze(s,
> c->vts_ifo->vtsi_mat->vts_audio_attr[i],
> > -
>  c->play_state.pgc->audio_control[i], &entry)) < 0)
> > +        if ((ret = dvdvideo_audio_stream_analyze(s, audio_attr,
> c->play_state.pgc->audio_control[i],
> > +                                                 &entry)) < 0)
> >              goto break_error;
> >
> >          /* IFO structures can declare duplicate entries for the same
> startcode */
> > @@ -1127,7 +1348,16 @@ static int
> dvdvideo_subp_stream_add_all(AVFormatContext *s)
> >  {
> >      DVDVideoDemuxContext *c = s->priv_data;
> >
> > -    for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_subp_streams;
> i++) {
> > +    int nb_streams;
> > +
> > +    if (c->opt_menu)
> > +        nb_streams = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->nr_of_vmgm_subp_streams :
> > +
> c->vts_ifo->vtsi_mat->nr_of_vtsm_subp_streams;
> > +    else
> > +        nb_streams = c->vts_ifo->vtsi_mat->nr_of_vts_subp_streams;
> > +
> > +
> > +    for (int i = 0; i < nb_streams; i++) {
> >          int ret = 0;
> >          uint32_t subp_control;
> >          subp_attr_t subp_attr;
> > @@ -1139,8 +1369,16 @@ static int
> dvdvideo_subp_stream_add_all(AVFormatContext *s)
> >
> >          /* there can be several presentations for one SPU */
> >          /* the DAR check is flexible in order to support weird
> authoring */
> > -        video_attr = c->vts_ifo->vtsi_mat->vts_video_attr;
> > -        subp_attr = c->vts_ifo->vtsi_mat->vts_subp_attr[i];
> > +        if (c->opt_menu) {
> > +            video_attr = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->vmgm_video_attr :
> > +
> c->vts_ifo->vtsi_mat->vtsm_video_attr;
> > +
> > +            subp_attr  = !c->opt_menu_vts ?
> c->vmg_ifo->vmgi_mat->vmgm_subp_attr :
> > +
> c->vts_ifo->vtsi_mat->vtsm_subp_attr;
> > +        } else {
> > +            video_attr = c->vts_ifo->vtsi_mat->vts_video_attr;
> > +            subp_attr = c->vts_ifo->vtsi_mat->vts_subp_attr[i];
> > +        }
> >
> >          /* 4:3 */
> >          if (!video_attr.display_aspect_ratio) {
> > @@ -1196,8 +1434,12 @@ static int dvdvideo_subdemux_read_data(void
> *opaque, uint8_t *buf, int buf_size)
> >      if (c->play_end)
> >          return AVERROR_EOF;
> >
> > -    ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf,
> buf_size,
> > -                                      &nav_event,
> dvdvideo_subdemux_flush);
> > +    if (c->opt_menu)
> > +        ret = dvdvideo_menu_next_ps_block(s, &c->play_state, buf,
> buf_size,
> > +                                          dvdvideo_subdemux_flush);
> > +    else
> > +        ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf,
> buf_size,
> > +                                          &nav_event,
> dvdvideo_subdemux_flush);
> >
> >      if (ret == AVERROR_EOF) {
> >          c->mpeg_pb.pub.eof_reached = 1;
> > @@ -1261,6 +1503,47 @@ static int dvdvideo_read_header(AVFormatContext
> *s)
> >
> >      int ret = 0;
> >
> > +    if (c->opt_menu) {
> > +        if (c->opt_region               ||
> > +            c->opt_title > 1            ||
> > +            c->opt_preindex             ||
> > +            c->opt_chapter_start > 1    ||
> > +            c->opt_chapter_end > 0) {
>
> > +            av_log(s, AV_LOG_ERROR, "-menu is not compatible with the
> -region, -title, "
> > +                                    "-preindex, or
> -chapter_start/-chapter_end options\n");
>
> unrelated note: I wonder if we should use "-foo" for mentioning
> options, since this in theory might be used also from the API,
> probably we should just say: the menu option is not compatible with
> ... (this is not blocking and might be addressed by a further patch).
>
> > +            return AVERROR(EINVAL);
> > +        }
> > +
> > +        if (!c->opt_pgc) {
> > +            av_log(s, AV_LOG_ERROR, "If -menu is enabled, -pgc must be
> set to a non-zero value\n");
> > +
> > +            return AVERROR(EINVAL);
> > +        }
> > +
> > +        if (!c->opt_menu_lu) {
> > +            av_log(s, AV_LOG_INFO, "Defaulting to menu language unit
> #1. "
> > +                                   "This is not always desirable,
> validation suggested.\n");
> > +
> > +            c->opt_menu_lu = 1;
> > +        }
> > +
> > +        if (!c->opt_pg) {
> > +            av_log(s, AV_LOG_INFO, "Defaulting to menu PG #1. "
> > +                                   "This is not always desirable,
> validation suggested.\n");
> > +
> > +            c->opt_pg = 1;
> > +        }
> > +
> > +        if ((ret = dvdvideo_ifo_open(s)) < 0                    ||
> > +            (ret = dvdvideo_menu_open(s, &c->play_state)) < 0   ||
> > +            (ret = dvdvideo_subdemux_open(s)) < 0               ||
> > +            (ret = dvdvideo_video_stream_setup(s)) < 0          ||
> > +            (ret = dvdvideo_audio_stream_add_all(s)) < 0)
> > +        return ret;
> > +
> > +        return 0;
> > +    }
> > +
> >      if (c->opt_title == 0) {
> >          av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
> >                                 "This is not always the main feature,
> validation suggested.\n");
> > @@ -1376,7 +1659,12 @@ static int dvdvideo_close(AVFormatContext *s)
> >      DVDVideoDemuxContext *c = s->priv_data;
> >
> >      dvdvideo_subdemux_close(s);
> > -    dvdvideo_play_close(s, &c->play_state);
> > +
> > +    if (c->opt_menu)
> > +        dvdvideo_menu_close(s, &c->play_state);
> > +    else
> > +        dvdvideo_play_close(s, &c->play_state);
> > +
> >      dvdvideo_ifo_close(s);
> >
> >      return 0;
> > @@ -1388,6 +1676,9 @@ static const AVOption dvdvideo_options[] = {
> >      {"chapter_end",     "exit chapter (PTT) number (0=end)",
>             OFFSET(opt_chapter_end),    AV_OPT_TYPE_INT,    { .i64=0 },
>  0,          99,        AV_OPT_FLAG_DECODING_PARAM },
> >      {"chapter_start",   "entry chapter (PTT) number",
>              OFFSET(opt_chapter_start),  AV_OPT_TYPE_INT,    { .i64=1 },
>  1,          99,        AV_OPT_FLAG_DECODING_PARAM },
> >      {"clut_rgb",        "output subtitle palette (CLUT) as RGB",
>             OFFSET(opt_clut_rgb),       AV_OPT_TYPE_BOOL,   { .i64=1 },
>  0,          1,         AV_OPT_FLAG_DECODING_PARAM },
> > +    {"menu",            "demux menu domain",
>             OFFSET(opt_menu),           AV_OPT_TYPE_BOOL,   { .i64=0 },
>  0,          1,         AV_OPT_FLAG_DECODING_PARAM },
> > +    {"menu_lu",         "menu language unit (0=auto)",
>             OFFSET(opt_menu_lu),        AV_OPT_TYPE_INT,    { .i64=0 },
>  0,          99,        AV_OPT_FLAG_DECODING_PARAM },
> > +    {"menu_vts",        "menu VTS (0=VMG main menu)",
>              OFFSET(opt_menu_vts),       AV_OPT_TYPE_INT,    { .i64=0 },
>  0,          99,        AV_OPT_FLAG_DECODING_PARAM },
> >      {"pg",              "entry PG number (0=auto)",
>              OFFSET(opt_pg),             AV_OPT_TYPE_INT,    { .i64=0 },
>  0,          255,       AV_OPT_FLAG_DECODING_PARAM },
> >      {"pgc",             "entry PGC number (0=auto)",
>             OFFSET(opt_pgc),            AV_OPT_TYPE_INT,    { .i64=0 },
>  0,          999,       AV_OPT_FLAG_DECODING_PARAM },
> >      {"preindex",        "enable for accurate chapter markers, slow
> (2-pass read)",  OFFSET(opt_preindex),       AV_OPT_TYPE_BOOL,   { .i64=0
> },     0,          1,         AV_OPT_FLAG_DECODING_PARAM },
>
> No more comments from me, nice feature!!
>


More information about the ffmpeg-devel mailing list