/** * Reads a single VTS file. * * @param vtsnTitles {@link DvdTitle} belonging to this VTS file. When this method returns, the * {@link DvdTitle} will contain detailed data. * @param dvdDir Mount directory of the DVD * @param vtsFile actual VTS file to be read */ private void readVtsFile(List<DvdTitle> vtsnTitles, File dvdDir, String vtsFile) throws IOException { try (IfoRandomAccessFile vts = new IfoRandomAccessFile(dvdDir, vtsFile)) { if (!"DVDVIDEO-VTS".equals(vts.readFixedString(12))) { throw new IfoException("No VTS file"); } Map<Integer, Long> pgcOffsets = vts.readPgcOffsets(); DvdTitleSet titleSet = vts.readTitleSet(); for (int ix = 0; ix < vtsnTitles.size(); ix++) { DvdTitle title = vtsnTitles.get(ix); title.setTitleSet(titleSet); readVtsPgc(vts, title, titleSet, pgcOffsets.get(title.getVts())); } } }
/** * Reads the given VMG file. * * @param dvdDir Mount directory of the DVD * @param vmgName Path and name of the VMG file */ private void readVmgFile(File dvdDir, String vmgName) throws IOException { LOG.info("Reading VMG file %s", vmgName); try (IfoRandomAccessFile vmg = new IfoRandomAccessFile(dvdDir, vmgName)) { if (!"DVDVIDEO-VMG".equals(vmg.readFixedString(12))) { throw new IfoException("No VMG file"); } long tt_srpt = vmg.at(0xC4).readOffset(); LOG.debug("tt_srpt: 0x%08X", tt_srpt); vmg.at(tt_srpt); int titlesCount = vmg.readu16(); LOG.debug("titlesCount: %d", titlesCount); long endAddress = vmg.skip(2).readu32(); long computedTitles = endAddress / 12; LOG.debug("computedTitles: %d", computedTitles); if (computedTitles != titlesCount) { LOG.warn( "Different number of titles: %d != %d, using the latter one", titlesCount, computedTitles); titlesCount = (int) computedTitles; } int lastVtsn = -1; List<DvdTitle> vtsnTitles = new ArrayList<>(); for (int ix = 0; ix < titlesCount; ix++) { DvdTitle title = new DvdTitle(); title.setTitle(ix + 1); title.setAngles(vmg.skip(1).readu8()); title.setChapters(vmg.readu16()); title.setVtsn(vmg.skip(2).readu8()); title.setVts(vmg.readu8()); vmg.skip(4); titles.add(title); LOG.debug("Title %2d: vtsn=%d, vts=%d", title.getTitle(), title.getVtsn(), title.getVts()); if (title.getVtsn() != lastVtsn) { if (!vtsnTitles.isEmpty()) { completeTitles(dvdDir, lastVtsn, vtsnTitles); } vtsnTitles.clear(); lastVtsn = title.getVtsn(); } vtsnTitles.add(title); } if (!vtsnTitles.isEmpty()) { completeTitles(dvdDir, lastVtsn, vtsnTitles); } } }
/** * Reads a PGC structure and completes the {@link DvdTitle} with the data found there. * * @param vts random access to the VTS file * @param title {@link DvdTitle} to complete * @param offset Offset of the PGC structure to be read */ private void readVtsPgc( IfoRandomAccessFile vts, DvdTitle title, DvdTitleSet titleSet, long offset) throws IOException { vts.at(offset).skip(2); int chapters = vts.readu8(); int cellCount = vts.readu8(); LOG.debug(" chapters: %d, cells: %d", chapters, cellCount); title.setTotalTimeMs(vts.readBcdTimeMs()); vts.skip(4); for (int ix = 0; ix < 8; ix++) { int snr = vts.readu8(); if ((snr & 0x80) != 0) { DvdAudio audio = titleSet.getAudios().get(ix); int streamId = snr & 0x07; if (streamId == 0) { streamId = ix; } streamId += audio.getMode().getBaseStreamId(); audio.setStreamId(streamId); } vts.skip(1); } for (int ix = 0; ix < 32; ix++) { int snr = vts.readu8(); int sWide = vts.readu8(); int sLetterbox = vts.readu8(); int sPanScan = vts.readu8(); if ((snr & 0x80) != 0) { snr &= 0x1F; sWide &= 0x1F; sLetterbox &= 0x1F; sPanScan &= 0x1F; DvdSubtitle sub = titleSet.getSubs().get(ix); if (titleSet.getAspect() == Aspect.ASPECT_16_9) { sub.setStreamWideId((sWide > 0 ? sWide : ix) + 0x20); } else { sub.setStream43Id((snr > 0 ? snr : ix) + 0x20); } if (titleSet.isLetterboxEnabled()) { sub.setStreamLetterboxId((sLetterbox > 0 ? sLetterbox : ix) + 0x20); } if (titleSet.isPanScanEnabled()) { sub.setStreamPanScanId((sPanScan > 0 ? sPanScan : ix) + 0x20); } } } vts.skip(8); for (int ix = 0; ix < 16; ix++) { title.getColors()[ix] = (int) vts.readu32(); } vts.skip(2); int programMapOffset = vts.readu16(); int cellPlaybackOffset = vts.readu16(); int[] cells = new int[chapters]; vts.at(offset + programMapOffset); for (int ix = 0; ix < chapters; ix++) { cells[ix] = vts.readu8(); } for (int ix = 0; ix < chapters; ix++) { int current = cells[ix]; int next = (ix + 1 < chapters ? cells[ix + 1] : cellCount); long ms = 0; while (current < next) { vts.at(offset + cellPlaybackOffset + ((current - 1) * 0x18) + 4); ms += vts.readBcdTimeMs(); current++; } if (ms > 0 || next != cellCount) { title.getChapterTimeMs().add(ms); } } }