Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / libzmpeg3 / ifo.C
diff --git a/cinelerra-5.1/libzmpeg3/ifo.C b/cinelerra-5.1/libzmpeg3/ifo.C
new file mode 100644 (file)
index 0000000..9e7fa20
--- /dev/null
@@ -0,0 +1,815 @@
+#include "libzmpeg3.h"
+// dvd sector size
+#if defined(__x86_64__)
+#define DVD_VIDEO_LB_LEN 2048L
+#else
+#define DVD_VIDEO_LB_LEN 2048LL
+#endif
+
+int zifo_t::
+ifo_read(long pos, long count, uint8_t *data)
+{
+  int64_t ret = 0;
+  int retry = 5;
+  while( retry > 0 ) {
+    ret = lseek(fd, pos, SEEK_SET);
+    if( ret >= 0 ) break;
+    perr("ifo_seek");
+    sleep(1);
+    --retry;
+  }
+  while( retry > 0 ) {
+    ret = read(fd, data, count); 
+    if( ret > 0 ) break;
+   if( ret == 0 ) {
+      zerr("unable to read ifo\n");
+      return -1;
+    }
+    if( errno != EAGAIN ) {
+      perr("read error");
+      return -1;
+    }
+    sleep(1);
+    --retry;
+  }
+  if( retry <= 0 ) {
+    perr("timeout");
+    return -1;
+  }
+  return ret;
+}
+
+int zifo_t::
+get_table(int64_t offset, unsigned long tbl_id)
+{
+  int64_t len = 0;
+  if( !offset ) return -1;
+  uint8_t *zdata = new uint8_t[DVD_VIDEO_LB_LEN];
+  uint64_t ipos = pos + offset*DVD_VIDEO_LB_LEN;
+  int ret = ifo_read(ipos, DVD_VIDEO_LB_LEN, zdata);
+  if( ret < 0 ) return -1;
+
+  switch( tbl_id ) {
+  case id_TITLE_VOBU_ADDR_MAP:
+  case id_MENU_VOBU_ADDR_MAP:
+    len = get4bytes(zdata) + 1;
+    break;
+
+  default: 
+    len = hdr_length(zdata);
+  }
+
+  int ilen = len - DVD_VIDEO_LB_LEN;
+  if( ilen > 0 ) {
+    uint8_t *new_zdata = new uint8_t[len];
+    memcpy(new_zdata,zdata,DVD_VIDEO_LB_LEN);
+    memset(new_zdata+DVD_VIDEO_LB_LEN,0,len-DVD_VIDEO_LB_LEN);
+    delete zdata;
+    zdata = new_zdata;
+    ipos += DVD_VIDEO_LB_LEN;
+    ret = ifo_read(ipos, ilen, zdata+DVD_VIDEO_LB_LEN);
+    if( ret < 0 ) return -1;
+  }
+
+  data[tbl_id] = zdata;
+
+  if( tbl_id == id_TMT ) {
+    uint32_t *ptr = (uint32_t*)zdata;
+    len /= sizeof(*ptr);
+    for( int i=0; i < len; ++i )
+      ptr[i] = bswap_32(ptr[i]);
+  }
+
+  return 0;
+}
+
+zifo_t::
+ifo_t(int zfd, long zpos)
+{
+  pos = zpos; 
+  fd = zfd;
+  char *cp = getenv("IFO_STREAM_PROBE");
+  if( cp ) empirical = atoi(cp);
+}
+
+zifo_t::
+~ifo_t()
+{
+  if( data[id_MAT] )                 delete data[id_MAT];
+  if( data[id_PTT] )                 delete data[id_PTT];
+  if( data[id_TITLE_PGCI] )          delete data[id_TITLE_PGCI];
+  if( data[id_MENU_PGCI] )           delete data[id_MENU_PGCI];
+  if( data[id_TMT] )                 delete data[id_TMT];
+  if( data[id_MENU_CELL_ADDR] )      delete data[id_MENU_CELL_ADDR];
+  if( data[id_MENU_VOBU_ADDR_MAP] )  delete data[id_MENU_VOBU_ADDR_MAP];
+  if( data[id_TITLE_CELL_ADDR] )     delete data[id_TITLE_CELL_ADDR];
+  if( data[id_TITLE_VOBU_ADDR_MAP] ) delete data[id_TITLE_VOBU_ADDR_MAP];
+}
+
+int zifo_t::
+init_tables()
+{
+  num_menu_vobs  = vtsm_vobs();
+  vob_start = vtstt_vobs();
+//zmsgs("num of vobs: %x vob_start %x\n", num_menu_vobs, vob_start);
+  if( !type_vts() ) {
+    if( get_table(vts_ptt_srpt(), id_PTT) < 0 ||
+        get_table(vts_pgcit(), id_TITLE_PGCI) < 0 ||
+//        get_table(vtsm_pgci_ut(), id_MENU_PGCI) < 0 ||
+//        get_table(vts_tmapt(), id_TMT) < 0 ||
+//        get_table(vtsm_c_adt(), id_MENU_CELL_ADDR) < 0 ||
+//        get_table(vtsm_vobu_admap(), id_MENU_VOBU_ADDR_MAP) < 0 ||
+        get_table(vts_c_adt(), id_TITLE_CELL_ADDR) < 0 ||
+        get_table(vts_vobu_admap(), id_TITLE_VOBU_ADDR_MAP) < 0 )
+      return -1;
+  } 
+  else if( !type_vmg() ) {
+    if( get_table(tt_srpt(), id_TSP) < 0 ||
+        get_table(vmgm_pgci_ut(), id_MENU_PGCI) < 0 ||
+        get_table(vts_atrt(), id_TMT) < 0 ||
+        get_table(vmgm_c_adt(), id_TITLE_CELL_ADDR) < 0 ||
+        get_table(vmgm_vobu_admap(), id_TITLE_VOBU_ADDR_MAP) < 0 )
+      return -1;
+  }
+  return 0;
+}
+
+int zifo_t::
+read_mat()
+{
+  data[id_MAT] = new uint8_t[DVD_VIDEO_LB_LEN];
+  memset(data[id_MAT],0,DVD_VIDEO_LB_LEN);
+  return ifo_read(pos, DVD_VIDEO_LB_LEN, data[id_MAT]) < 0 ? -1 : 0;
+}
+
+zifo_t* zmpeg3_t::
+ifo_open(int fd, long pos)
+{
+  ifo_t *ifo = new ifo_t(fd,pos); 
+  if( ifo->read_mat() || ifo->init_tables() ) {
+    delete ifo;
+    ifo = NULL;
+  }
+  return ifo;
+}
+
+int zifo_t::
+ifo_close()
+{
+  delete this;
+  return 0;
+}
+
+void zifo_t::
+get_palette(zmpeg3_t *zsrc)
+{
+  if( !zsrc->have_palette ) {
+    /* subtitle color palette */
+    int ofs = 0;
+    uint8_t *ptr = palette();
+    for( int i=0; i<16; ++i ) {
+      int y = (int)ptr[ofs+1];
+      int u = (int)ptr[ofs+2];
+      int v = (int)ptr[ofs+3];
+      int k = i*4;
+      zsrc->palette[k+0] = y;
+      zsrc->palette[k+1] = u;
+      zsrc->palette[k+2] = v;
+//zmsgs("color %02d: 0x%02x 0x%02x 0x%02x\n", i, y, u, v);
+      ofs += 4;
+    }
+    zsrc->have_palette = 1;
+  }
+}
+
+void zifo_t::
+get_playlist(zmpeg3_t *zsrc)
+{
+  DIR *dirstream;
+  char directory[STRLEN];
+  char filename[STRLEN];
+  char full_path[STRLEN];
+  char title_path[STRLEN];
+  char vob_prefix[STRLEN];
+  struct dirent *new_filename;
+  demuxer_t *demux = zsrc->demuxer;
+
+  /* Get titles matching ifo file */
+  complete_path(full_path, zsrc->fs->path);
+  get_directory(directory, full_path);
+  get_filename(filename, full_path);
+  strncpy(vob_prefix, filename, 6);
+
+  dirstream = opendir(directory);
+  while( (new_filename=readdir(dirstream)) != 0 ) {
+    if( !strncasecmp(new_filename->d_name, vob_prefix, 6) ) {
+      char *ptr = strrchr(new_filename->d_name, '.');
+      if( ptr && !strncasecmp(ptr, ".vob", 4) ) {
+        /* Got a title */
+        if( atol(&new_filename->d_name[7]) > 0 ) {
+          joinpath(title_path, directory, new_filename->d_name);
+          demuxer_t::title_t *title = new demuxer_t::title_t(zsrc, title_path);
+          demux->titles[demux->total_titles++] = title;
+//zmsgs("title_path=%s\n", title_path);
+        }
+      }
+    }
+  }
+  closedir(dirstream);
+
+  int done = 0;
+  while( !done ) {
+    done = 1;
+    for( int i=1; i<demux->total_titles; ++i ) {
+      char *i0 = demux->titles[i-0]->fs->path;
+      char *i1 = demux->titles[i-1]->fs->path;
+      if( strcmp(i1, i0) > 0 ) {
+        ztitle_t *title = demux->titles[i-0];
+        demux->titles[i-0] = demux->titles[i-1];
+        demux->titles[i-1] = title;
+        done = 0;
+      }
+    }
+  }
+
+  int64_t total_bytes = 0;
+  for( int i=0; i<demux->total_titles; ++i ) {
+    ztitle_t *title = demux->titles[i-0];
+    title->total_bytes = path_total_bytes(title->fs->path);
+    title->start_byte = total_bytes;
+    title->end_byte = total_bytes + title->total_bytes;
+    total_bytes += title->total_bytes;
+  }
+}
+
+/* major kludge, but some dvds have very damaged data */
+#define TEST_START 0x1000000
+#define TEST_LEN   0x1000000
+
+void zifo_t::
+get_header(zdemuxer_t *demux)
+{
+  int i;
+  /* Video header */
+  demux->vstream_table[0] = 1;
+  demux->open_title(0);
+
+  if( empirical ) {
+    demux->seek_byte(TEST_START);
+    demux->read_all = 1;
+    int result = 0;
+    while( !result && !demux->eof() &&
+           demux->tell_byte() < TEST_START+TEST_LEN ) {
+      result = demux->read_next_packet();
+    }
+    demux->seek_byte(0);
+    demux->read_all = 0;
+  }
+  else {
+    /* Audio header */
+    if( !type_vts() ) {
+      int atracks = nr_of_vts_audio_streams();
+      if( atracks > 0 ) {
+        for( i=0; i<atracks; ++i ) {
+          uint8_t *audio_attr = vts_audio_attr(i);
+          int audio_mode = afmt_IGNORE;
+          switch( aud_audio_format(audio_attr) ) {
+          case 0:  audio_mode = afmt_AC3;   break;
+          case 1:  audio_mode = afmt_MPEG;  break;
+          case 2:  audio_mode = afmt_MPEG;  break;
+          case 3:  audio_mode = afmt_PCM;   break;
+          }
+          if( !demux->astream_table[i+0x80] )
+            demux->astream_table[i+0x80] = audio_mode;
+        }
+      }
+    }
+    else if( !type_vmg() ) {
+      int atracks = nr_of_vmgm_audio_streams();
+      if( atracks > 1 ) zerr("too many atracks for vmgm header\n");
+      if( atracks > 0 ) {
+        uint8_t *audio_attr = vmgm_audio_attr();
+        int audio_mode = afmt_IGNORE;
+        switch( aud_audio_format(audio_attr) ) {
+        case 0:  audio_mode = afmt_AC3;   break;
+        case 1:  audio_mode = afmt_MPEG;  break;
+        case 2:  audio_mode = afmt_MPEG;  break;
+        case 3:  audio_mode = afmt_PCM;   break;
+        }
+        if( !demux->astream_table[0x80] )
+          demux->astream_table[0x80] = audio_mode;
+      }
+    }
+    /* subtitle header */
+    if( !type_vts() ) {
+      int stracks = nr_of_vts_subp_streams();
+      if( stracks > 0 ) {
+        for( i=0; i<stracks; ++i )
+          demux->sstream_table[i] = 1;
+      }
+    }
+    else if( !type_vmg() ) {
+      int stracks = nr_of_vmgm_subp_streams();
+      if( stracks > 1 ) zerr("too many stracks for vmgm header\n");
+      if( stracks > 0 ) {
+        demux->sstream_table[0] = 1;
+      }
+    }
+  }
+}
+
+zicell_t* zicell_table_t::
+append_cell()
+{
+  if( !cells || total_cells >= cells_allocated ) {
+    int new_allocation = ZMAX(cells_allocated*2, 64);
+    icell_t *new_cells = new icell_t[new_allocation];
+    for( int i=0; i<total_cells; ++i )
+      new_cells[i] = cells[i];
+    delete [] cells;
+    cells = new_cells;
+    cells_allocated = new_allocation;
+  }
+  return &cells[total_cells++];
+}
+
+zicell_table_t::
+~icell_table_t()
+{
+  if( cells ) delete [] cells;
+}
+
+void zifo_t::
+get_playinfo(zmpeg3_t *zsrc, icell_table_t *icell_addrs)
+{
+  icell_table_t *pcells = new icell_table_t();
+  if( zsrc->playinfo ) delete zsrc->playinfo;
+  zsrc->playinfo = pcells;
+  int total_pcells = nr_of_cells();
+  int cur_angle = -1;
+  double cur_time = 0.;
+//zmsgs("total_pcells %d\n", total_pcells);
+
+  for( int pcell_no=0; pcell_no<total_pcells; ++pcell_no ) {
+    use_playback(pcell_no);
+    int phr = bcd(cell_playback_time()[0]);
+    int pmn = bcd(cell_playback_time()[1]);
+    int psc = bcd(cell_playback_time()[2]);
+    int pfm = bcd(cell_playback_time()[3] & 0x3f);
+    int prt = cell_playback_time()[3] >> 6;
+#if 0
+zmsgs(" cell: %d\n"
+  "   blk_ty %u, seemless %u, interlv %u, discon %u, angle %u @secs %f\n"
+  "   mode %u, restrict %u, still %u, cmd_nr %u, ptime %02u:%02u:%02u.%02u %5.5s\n"
+  "   1st sect "_LX", end sect "_LX", start sect "_LX", last sect "_LX"\n",
+  pcell_no+1,
+  cell_block_type(), cell_seamless_play(), cell_interleaved(),
+  cell_stc_discontinuity(), cell_seamless_angle(), cur_time,
+  cell_playback_mode(), cell_restricted(), cell_still_time(),
+  cell_cmd_nr(), phr, pmn, psc, pfm, &"0.0  25.00     29.97"[5*prt],
+  cell_first_sector()*DVD_VIDEO_LB_LEN,
+  cell_first_ilvu_end_sector()*DVD_VIDEO_LB_LEN,
+  cell_last_vobu_start_sector()*DVD_VIDEO_LB_LEN,
+  cell_last_sector()*DVD_VIDEO_LB_LEN);
+#endif
+    icell_t *pcell = pcells->append_cell();
+    pcell->start_byte = cell_first_sector()*DVD_VIDEO_LB_LEN;
+    pcell->end_byte = (cell_last_sector()+1)*DVD_VIDEO_LB_LEN;
+    pcell->discon = cell_stc_discontinuity();
+    pcell->inlv = cell_interleaved();
+    if( cell_block_type() == BTY_ANGLE ) {
+      switch( cell_block_mode() ) {
+      case BMD_FIRST_CELL: cur_angle=0; break;
+      case BMD_IN_BLOCK:
+      case BMD_LAST_CELL:  ++cur_angle; break;
+      }
+    }
+    else
+      cur_angle = -1;
+    pcell->angle = cur_angle;
+    if( !pcell->inlv || cur_angle == 0 )
+      cur_time += phr*3600 + pmn*60 + psc + pfm/(prt==1 ? 25.0 : 29.97);
+    use_position(pcell_no);
+    pcell->vob_id = vob_id_nr();
+    pcell->cell_id = cell_id_nr();
+//zmsgs(" %3d vob/cell %d/%d  start: "_LX" end: "_LX" discon %d inlv/ang %d/%d\n",
+//  pcell_no+1, pcell->vob_id, pcell->cell_id,
+//  pcell->start_byte, pcell->end_byte, pcell->discon, pcell->inlv,pcell->angle);
+  }
+}
+
+void zifo_t::
+icell_addresses(zicell_table_t *icell_addrs)
+{
+  int i, j;
+//zmsg("icell_addresses\n");
+  int length = cadr_length();
+//zmsgs("icells length %d\n",length);
+
+  for( i=0; i<length; ++i ) {
+    use_cell(i);
+    icell_t *cell = icell_addrs->append_cell();
+    cell->start_byte = cadr_start_sector() * DVD_VIDEO_LB_LEN;
+    cell->end_byte = (cadr_last_sector()+1) * DVD_VIDEO_LB_LEN;
+    cell->vob_id = cadr_vob_id();
+    cell->cell_id = cadr_cell_id();
+    cell->discon = i;
+    cell->inlv = 0;
+    cell->angle = 0;
+//zmsgs("cell %d, vob_id %u, cell_id %u, start %lu/%lx, last %lu/%lx\n",
+//  i, cell->vob_id, cell->cell_id, cell->start_byte, cell->start_byte,
+//  cell->end_byte, cell->end_byte);
+  }
+
+  int total_icells = icell_addrs->total_cells;
+  icell_t *icells[total_icells];
+  for( i=total_icells; --i>=0; ) icells[i] = &icell_addrs->cells[i];
+
+// Sort addresses by addr/vob_id/cell_id instead of vob id
+  int done = 0;
+  while( !done ) {
+    done = 1;
+    for( i=1; i<total_icells; ++i ) {
+      icell_t *icell0 = icells[i-0];
+      icell_t *icell1 = icells[i-1];
+      // key addr/vob_no/cell_id
+      if( icell1->start_byte > icell0->start_byte ||
+         (icell1->start_byte == icell0->start_byte &&
+          (icell1->vob_id > icell0->vob_id ||
+           (icell1->vob_id == icell0->vob_id &&
+            icell1->cell_id > icell0->cell_id))) ) {
+        icells[i-0] = icell1;
+        icells[i-1] = icell0;
+        done = 0;
+      }
+    }
+  }
+
+#if 0
+  int cur_vobid = 0x10000;
+  /* label interleave */
+  for( i=total_icells; --i>=0; ) {
+    icell_t *icell = icells[i];
+    int cur_inlv = icell->vob_id - cur_vobid;
+    /* Reduce current vobid */
+    if( cur_inlv < 0 ) {
+      cur_vobid = icell->vob_id;
+      cur_inlv = 0;
+    }
+    icell->inlv = cur_inlv;
+    if( max_inlv < cur_inlv )
+      max_inlv = cur_inlv;
+    if( cur_inlv > 0 ) {
+      int cell_id = icell->cell_id;
+      /* Get the last interleave by brute force */
+      for( j=i+1; j<total_icells && cell_id==icells[j]->cell_id; ++j ) {
+        int inlv = icells[j]->vob_id - cur_vobid;
+        if( inlv > cur_inlv ) continue;
+        icells[j]->inlv = inlv;
+      }
+    }
+    else if( cur_inlv == 0 ) {
+      int64_t start_byte = icells[i]->start_byte;
+      for( j=i+1; j<total_icells && start_byte==icells[j]->start_byte; ++j )
+        icells[j]->inlv = j-i;
+    }
+  }
+
+  /* convert int labels to bit vector */
+  i = 0;
+  while( i < total_icells ) {
+    j = i;
+    int64_t start_byte = icells[j]->start_byte;
+    uint32_t inlv = 1 << icells[j]->inlv;
+    while( ++j<total_icells && start_byte==icells[j]->start_byte )
+      inlv |= 1 << icells[j]->inlv;
+    while( i < j ) icells[i++]->inlv = inlv;
+  }
+
+#else
+  int istart = -1, istop = -1;
+// Assign interleave:
+//  there are 2 cases,
+//    interleave is known, using vob_id offset,
+//    interleave is unknown, cell may be in any program
+//  unsorted vob_ids:   1111122233
+//    sorted vob_ids:   1112121233
+//  equal start_byte:   ***xxxx***
+//        interleave:   xx010101xx
+  i = 0;
+  while( i < total_icells ) {
+    istart = i;
+    int cur_vob_id = icells[istart]->vob_id;
+    while( ++i < total_icells ) {
+      if( icells[i]->vob_id > cur_vob_id ) {
+        if( icells[i]->start_byte == icell_addrs->cells[i].start_byte ) break;
+        cur_vob_id = icells[i]->vob_id;
+      }
+    }
+    istop = i;
+    if( istop == istart+1 ) {
+      icells[istart]->inlv = ~0;
+      continue;
+    }
+    int min_vob_id = icells[istart]->vob_id;
+    int cur_inlv = cur_vob_id - min_vob_id;
+    if( cur_inlv > max_inlv ) max_inlv = cur_inlv;
+    int inlv = 0;
+    j = istart;
+    while( j < istop ) {
+      int64_t start_byte = icells[j]->start_byte;
+      if( j+1 < istop && icells[j+1]->start_byte == start_byte ) {
+        inlv = -1;
+        while( j < istop && icells[j]->start_byte == start_byte )
+          icells[j++]->inlv = ~0;
+      }
+      else {
+        int k = icells[j]->vob_id - min_vob_id;
+        if( inlv >= 0 && k != inlv )
+          zerrs("bolixed: j %d, inlv %d, k %d\n", j, inlv, k);
+        icells[j++]->inlv = 1 << k;
+        inlv = k;
+        if( ++inlv > cur_inlv ) inlv = 0;
+      }
+    }
+    if( inlv > 0 ) {
+      zerrs("bungled: istart %d, istop %d, cur_inlv %d\n",
+        istart, istop, cur_inlv);
+    }
+  }
+#endif
+
+#if 0
+zmsgs("sorted labeled icells %d\n",total_icells);
+  for( i=0; i<total_icells; ++i ) {
+    icell_t *cell = icells[i];
+    zmsgs("cell %d, vob_id %u, cell_id %u, start "_LX", last "_LX" inlv %04x\n",
+      i, cell->vob_id, cell->cell_id, cell->start_byte, cell->end_byte, cell->inlv);
+  }
+#endif
+}
+
+static int pcmpr(const void *ap, const void *bp)
+{
+  zicell_t *a = *(zicell_t**)ap, *b = *(zicell_t**)bp;
+  int v = a->vob_id - b->vob_id;
+  if( !v ) v = a->cell_id - b->cell_id;
+  return v;
+}
+
+void zifo_t::
+icell_map(zmpeg3_t *zsrc, icell_table_t *icell_addrs)
+{
+  int inlv = zsrc->interleave;
+  int angle = zsrc->angle;
+  demuxer_t *demuxer = zsrc->demuxer;
+  int64_t program_start = 0;
+  int discon = 0;
+  int total_icells = icell_addrs->total_cells;
+  icell_t *icells = &icell_addrs->cells[0];
+  int total_pcells = zsrc->playinfo->total_cells;
+  icell_t *pcells[total_pcells];
+  for( int i=0; i<total_pcells; ++i ) pcells[i] = &zsrc->playinfo->cells[i];
+  qsort(pcells, total_pcells, sizeof(pcells[0]), pcmpr);
+
+  for( int i=0; i<total_pcells; ++i ) {
+    icell_t *pcell = pcells[i];
+    int pcell_no = pcell - zsrc->playinfo->cells;
+    if( pcell->angle >= 0 && pcell->angle != angle ) continue;
+    int icell_no, pgm = pcell->angle >= 0 ? angle : inlv;
+    icell_t *cell = 0;
+    for( icell_no=0; icell_no<total_icells; ++icell_no ) {
+      cell = &icells[icell_no];
+      if( pcell->inlv && !cell->has_inlv(pgm) ) continue;
+      if( cell->vob_id != pcell->vob_id ) continue;
+      if( cell->cell_id == pcell->cell_id ) break;
+    }
+    if( !cell || icell_no >= total_icells ) {
+      zerrs("vob/cell %d/%d missed in icells\n", pcell->vob_id, pcell->cell_id);
+      continue;
+    }
+
+    if( current_vob_id != cell->vob_id ) {
+      current_vob_id = cell->vob_id;
+      // -1 resets pts to 0, 1 is just discontinuity
+      // discon = cell->cell_id == 1 ? -1 : 1;
+      discon = 1;
+    }
+    else if( pcell->discon )
+      discon = 1;
+
+    while( icell_no < total_icells ) {
+      cell = &icells[icell_no++];
+      if( pcell->inlv && !cell->has_inlv(pgm) ) continue;
+      if( cell->vob_id != pcell->vob_id ) break;
+      if( cell->cell_id != pcell->cell_id ) break;
+      if( cell->start_byte >= pcell->end_byte ) continue;
+      if( cell->end_byte <= pcell->start_byte ) continue;
+      int64_t start_byte = cell->start_byte;
+      if( start_byte < pcell->start_byte )
+        start_byte = pcell->start_byte;
+      int64_t end_byte = cell->end_byte;
+      if( end_byte > pcell->end_byte )
+        end_byte = pcell->end_byte;
+
+      int64_t length = end_byte - start_byte;
+      while( length > 0 ) {
+        /* Cell may be split by a title so handle in fragments. */
+        int title_no;
+        for( title_no=0; title_no<demuxer->total_titles; ++title_no )
+          if( demuxer->titles[title_no]->end_byte > start_byte ) break;
+        if( title_no >= demuxer->total_titles ) {
+          zerrs("cell map past titles "_LX"\n", start_byte);
+          break;
+        }
+        ztitle_t *title = demuxer->titles[title_no];
+        int64_t cell_size = length;
+        /* Clamp length to current title */
+        if( start_byte+length > title->end_byte )
+          cell_size = title->end_byte - start_byte;
+        int64_t title_start = start_byte - title->start_byte;
+        int64_t title_end = title_start + cell_size;
+        int64_t program_end = program_start + cell_size;
+//zmsgs("  %d/%d. title/cell: %d/%-2d, vob/cell: %2d/%-2d,  title 0x%012lx-0x%012lx, "
+//      " program 0x%012lx-0x%012lx discon %d\n",
+//  pcell_no+1, icell_no, title_no, title->cell_table_size, cell->vob_id, cell->cell_id,
+//  title_start, title_end, program_start, program_end, discon);
+        title->new_cell(pcell_no, title_start, title_end,
+                        program_start, program_end, discon);
+        discon = 0;
+        start_byte += cell_size;
+        program_start += cell_size;
+        length -= cell_size;
+      }
+
+      current_byte = end_byte;
+    }
+  }
+}
+
+void zifo_t::
+get_ititle(zmpeg3_t *zsrc, int chapter)
+{
+  int title = zsrc->vts_title;
+  use_ptt_info(title);
+  int program_chain = ptt_pgcn(chapter);
+  use_program_chain(program_chain-1);
+}
+
+/* Read the title information from an ifo */
+int zmpeg3_t::
+read_ifo()
+{
+  ifo_t *ifo;
+  icell_table_t icell_addrs;
+  int result = 0;
+
+  int fd = fs->get_fd();
+  if( (ifo = ifo_open(fd, 0)) == 0 ) {
+    zerr("Error opening ifo.\n");
+    result = 1;
+  }
+  if( !result ) {
+    ifo->get_playlist(this);
+    if( !demuxer->total_titles ) {
+      zerr("Error no titles in ifo.\n");
+      result = 1;
+    }
+  }
+  if( !result ) {
+    total_vts_titles = ifo->nr_of_titles();
+    if( vts_title >= total_vts_titles ) {
+      zerrs("Error only %d titles in ifo, req %d\n",
+        total_vts_titles, vts_title);
+      result = 1;
+    }
+  }
+  if( !result ) {
+    ifo->get_header(demuxer);
+    ifo->get_ititle(this);
+    ifo->get_palette(this);
+    ifo->icell_addresses(&icell_addrs);
+    total_interleaves = ifo->max_inlv + 1;
+    if( interleave >= total_interleaves ) {
+      zerrs("Error only %d interleaves in ifo, req %d\n",
+        total_interleaves, interleave);
+      result = 1;
+    }
+  }
+  if( !result ) {
+    ifo->get_playinfo(this, &icell_addrs);
+    ifo->icell_map(this, &icell_addrs);
+  }
+
+  if( ifo ) ifo->ifo_close();
+  return result;
+}
+
+/* This is not useful, since title is based on the vgm tt_srpt table */
+/*   and that table is not normally read.  written for testing purposes. */
+/*   the orignal plan was to be able to put keyframe markers at chapters */
+int64_t zifo_t::ifo_chapter_cell_time(zmpeg3_t *zsrc, int chapter)
+{
+  double result = -1;
+  int title = zsrc->vts_title;
+  if( chapter >= 0 && chapter < (int)nr_of_chapters(title) ) {
+    get_ititle(zsrc, chapter);
+    int pgn = ptt_pgn(chapter);
+    uint8_t *pmap = pgc_program_map();
+    int cell_id = pmap[pgn-1] - 1;
+    int n = zsrc->playinfo->total_cells;
+    int i = 0;
+    while( i < n ) {
+      if( zsrc->playinfo->cells[i].cell_id == cell_id ) break;
+      ++i;
+    }
+    if( i < n ) {
+      use_playback(i);
+      result = cell_first_sector() * DVD_VIDEO_LB_LEN;
+    }
+  }
+  return result;
+}
+
+/* check playcells */
+int zifo_t::
+chk(int vts_title, int chapter, int inlv, int angle,
+    icell_table_t *icell_addrs, int &sectors, int &pcells, int &max_angle)
+{
+  if( type_vts() ) return -1;
+  if( vts_title >= (int)nr_of_titles() ) return -1;
+  if( chapter >= (int)nr_of_chapters(vts_title) ) return 0;
+  use_ptt_info(vts_title);
+  int program_chain = ptt_pgcn(chapter);
+  use_program_chain(program_chain-1);
+  int total_pcells = nr_of_cells();
+  if( total_pcells <= 0 ) return 0;
+  int pgn = ptt_pgn(chapter);
+  uint8_t *pmap = pgc_program_map();
+  int pcell_no = pmap[pgn-1] - 1;
+  use_playback(pcell_no);
+  uint32_t current_sector = cell_first_sector();
+  int count = 0;
+
+  int total_icells = icell_addrs->total_cells;
+  icell_t *icells = &icell_addrs->cells[0];
+  int last_vob_id = 0;
+  int last_cell_id = 0;
+  max_angle = -1;
+  int cur_angle = -1;
+
+  for( int pcell_no=0; pcell_no<total_pcells; ++pcell_no ) {
+    // vob/cell id keys, must be increasing
+    use_position(pcell_no);
+    int vob_id = vob_id_nr();
+    int cell_id = cell_id_nr();
+    if( vob_id != last_vob_id )
+      last_vob_id = vob_id;
+    else if( cell_id < last_cell_id )
+      return 0;
+    last_cell_id = cell_id;
+
+    use_playback(pcell_no);
+    int interleaved = cell_interleaved();
+    int pgm = inlv;
+    if( cell_block_type() == BTY_ANGLE ) {
+      switch( cell_block_mode() ) {
+      case BMD_FIRST_CELL: cur_angle=0;  break;
+      case BMD_IN_BLOCK:
+      case BMD_LAST_CELL:  ++cur_angle;  break;
+      }
+      if( cur_angle > max_angle ) max_angle = cur_angle;
+      if( cur_angle != angle ) continue;
+      pgm = cur_angle;
+    }
+    else
+      cur_angle = -1;
+    // vob/cell/interleave must be in map
+    icell_t *cell = 0;
+    int icell_no;
+    for( icell_no=0; icell_no<total_icells; ++icell_no ) {
+      cell = &icells[icell_no];
+      if( interleaved && !cell->has_inlv(pgm) ) continue;
+      if( cell->vob_id != vob_id ) continue;
+      if( cell->cell_id == cell_id ) break;
+    }
+    if( !cell || icell_no >= total_icells ) return 0;
+
+    // check sectors, must be increasing
+    uint32_t first_sector = cell_first_sector();
+    if( first_sector && current_sector > first_sector ) return 0;
+    uint32_t last_sector1 = cell_last_sector()+1;
+    count += last_sector1 - first_sector;
+    current_sector = last_sector1;
+
+    // pcell must overlap cell
+    if( first_sector*DVD_VIDEO_LB_LEN >= cell->end_byte ) return 0;
+    if( cell->start_byte >= last_sector1*DVD_VIDEO_LB_LEN ) return 0;
+  }
+
+  sectors = count;
+  pcells = total_pcells;
+
+  return 1;
+}
+