예제 #1
0
파일: SupBD.java 프로젝트: bepcyc/BDSup2Sub
/** Reading and writing of Blu-Ray captions demuxed from M2TS transport streams (BD-SUP). */
public class SupBD implements SubtitleStream {

  private static final Configuration configuration = Configuration.getInstance();
  private static final Logger logger = Logger.getInstance();

  private FileBuffer buffer;
  private List<SubPictureBD> subPictures = new ArrayList<SubPictureBD>();
  private int forcedFrameCount;

  /** color palette of the last decoded caption */
  private Palette palette;
  /** bitmap of the last decoded caption */
  private Bitmap bitmap;
  /** index of dominant color for the current caption */
  private int primaryColorIndex;

  public SupBD(String filename) throws CoreException {
    SupBDParser parser = new SupBDParser(filename);
    buffer = parser.getBuffer();
    subPictures = parser.getSubPictures();
    forcedFrameCount = parser.getForcedFrameCount();
  }

  /**
   * Decode caption from the input stream.
   *
   * @param subPictureBD SubPicture object containing info about the caption
   * @param transparentColorIndex index of the transparent color
   * @return bitmap of the decoded caption
   * @throws CoreException
   */
  private Bitmap decodeImage(SubPictureBD subPictureBD, int transparentColorIndex)
      throws CoreException {
    int width = subPictureBD.getImageWidth();
    int height = subPictureBD.getImageHeight();
    // always decode image obj 0, start with first entry in fragment list
    ImageObjectFragment imageObjectFragment =
        subPictureBD.getImageObject().getFragmentList().get(0);
    long startOfs = imageObjectFragment.getImageBufferOfs();

    if (width > subPictureBD.getWidth() || height > subPictureBD.getHeight()) {
      throw new CoreException(
          "Subpicture too large: "
              + width
              + "x"
              + height
              + " at offset "
              + ToolBox.toHexLeftZeroPadded(startOfs, 8));
    }

    Bitmap bm = new Bitmap(width, height, (byte) transparentColorIndex);

    int b;
    int index = 0;
    int ofs = 0;
    int size;
    int xpos = 0;

    try {
      // just for multi-packet support, copy all of the image data in one common buffer
      byte[] buffer = new byte[subPictureBD.getImageObject().getBufferSize()];
      index = 0;

      for (ImageObjectFragment fragment : subPictureBD.getImageObject().getFragmentList()) {
        // copy data of all packet to one common buffer
        imageObjectFragment = fragment;
        for (int i = 0; i < imageObjectFragment.getImagePacketSize(); i++) {
          buffer[index + i] =
              (byte) this.buffer.getByte(imageObjectFragment.getImageBufferOfs() + i);
        }
        index += imageObjectFragment.getImagePacketSize();
      }

      index = 0;

      do {
        b = buffer[index++] & 0xff;
        if (b == 0) {
          b = buffer[index++] & 0xff;
          if (b == 0) {
            // next line
            ofs = (ofs / width) * width;
            if (xpos < width) {
              ofs += width;
            }
            xpos = 0;
          } else {
            if ((b & 0xC0) == 0x40) {
              // 00 4x xx -> xxx zeroes
              size = ((b - 0x40) << 8) + (buffer[index++] & 0xff);
              for (int i = 0; i < size; i++) {
                bm.getInternalBuffer()[ofs++] = 0; /*(byte)b;*/
              }
              xpos += size;
            } else if ((b & 0xC0) == 0x80) {
              // 00 8x yy -> x times value y
              size = (b - 0x80);
              b = buffer[index++] & 0xff;
              for (int i = 0; i < size; i++) {
                bm.getInternalBuffer()[ofs++] = (byte) b;
              }
              xpos += size;
            } else if ((b & 0xC0) != 0) {
              // 00 cx yy zz -> xyy times value z
              size = ((b - 0xC0) << 8) + (buffer[index++] & 0xff);
              b = buffer[index++] & 0xff;
              for (int i = 0; i < size; i++) {
                bm.getInternalBuffer()[ofs++] = (byte) b;
              }
              xpos += size;
            } else {
              // 00 xx -> xx times 0
              for (int i = 0; i < b; i++) {
                bm.getInternalBuffer()[ofs++] = 0;
              }
              xpos += b;
            }
          }
        } else {
          bm.getInternalBuffer()[ofs++] = (byte) b;
          xpos++;
        }
      } while (index < buffer.length);

      return bm;
    } catch (FileBufferException ex) {
      throw new CoreException(ex.getMessage());
    } catch (ArrayIndexOutOfBoundsException ex) {
      logger.warn(
          "Problems during RLE decoding of picture OBJ at offset "
              + ToolBox.toHexLeftZeroPadded(startOfs + index, 8)
              + "\n");
      return bm;
    }
  }

  /**
   * decode palette from the input stream
   *
   * @param pic SubPicture object containing info about the current caption
   * @return
   * @throws CoreException
   */
  private Palette decodePalette(SubPictureBD pic) throws CoreException {
    boolean fadeOut = false;
    int paletteIndex;
    List<PaletteInfo> paletteInfos = pic.getPalettes().get(pic.getImageObject().getPaletteID());
    if (paletteInfos == null) {
      throw new CoreException("Palette ID out of bounds.");
    }

    Palette palette = new Palette(256, Core.usesBT601());
    // by definition, index 0xff is always completely transparent
    // also all entries must be fully transparent after initialization

    try {
      for (PaletteInfo paletteInfo : paletteInfos) {
        int index = paletteInfo.getPaletteOffset();
        for (int i = 0; i < paletteInfo.getPaletteSize(); i++) {
          // each palette entry consists of 5 bytes
          paletteIndex = buffer.getByte(index);
          int y = buffer.getByte(++index);
          int cr, cb;
          if (configuration.isSwapCrCb()) {
            cb = buffer.getByte(++index);
            cr = buffer.getByte(++index);
          } else {
            cr = buffer.getByte(++index);
            cb = buffer.getByte(++index);
          }
          int alpha = buffer.getByte(++index);

          int alphaOld = palette.getAlpha(paletteIndex);
          // avoid fading out
          if (alpha >= alphaOld) {
            if (alpha
                < configuration
                    .getAlphaCrop()) { // to not mess with scaling algorithms, make transparent
                                       // color black
              y = 16;
              cr = 128;
              cb = 128;
            }
            palette.setAlpha(paletteIndex, alpha);
          } else {
            fadeOut = true;
          }

          palette.setYCbCr(paletteIndex, y, cb, cr);
          index++;
        }
      }
      if (fadeOut) {
        logger.warn("fade out detected -> patched palette\n");
      }
      return palette;
    } catch (FileBufferException ex) {
      throw new CoreException(ex.getMessage());
    }
  }

  /**
   * decode given picture
   *
   * @param pic SubPicture object containing info about caption
   * @throws CoreException
   */
  private void decode(SubPictureBD pic) throws CoreException {
    palette = decodePalette(pic);
    bitmap = decodeImage(pic, palette.getIndexOfMostTransparentPaletteEntry());
    primaryColorIndex =
        bitmap.getPrimaryColorIndex(
            palette.getAlpha(), configuration.getAlphaThreshold(), palette.getY());
  }

  /* (non-Javadoc)
   * @see SubtitleStream#decode(int)
   */
  public void decode(int index) throws CoreException {
    if (index < subPictures.size()) {
      decode(subPictures.get(index));
    } else {
      throw new CoreException("Index " + index + " out of bounds\n");
    }
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getPalette()
   */
  public Palette getPalette() {
    return palette;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getBitmap()
   */
  public Bitmap getBitmap() {
    return bitmap;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getImage()
   */
  public BufferedImage getImage() {
    return bitmap.getImage(palette.getColorModel());
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getImage(Bitmap)
   */
  public BufferedImage getImage(Bitmap bm) {
    return bm.getImage(palette.getColorModel());
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getPrimaryColorIndex()
   */
  public int getPrimaryColorIndex() {
    return primaryColorIndex;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getSubPicture(int)
   */
  public SubPicture getSubPicture(int index) {
    return subPictures.get(index);
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getNumFrames()
   */
  public int getFrameCount() {
    return subPictures.size();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getForcedFrameCount()
   */
  public int getForcedFrameCount() {
    return forcedFrameCount;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#close()
   */
  public void close() {
    if (buffer != null) {
      buffer.close();
    }
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getEndTime(int)
   */
  public long getEndTime(int index) {
    return subPictures.get(index).getEndTime();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getStartTime(int)
   */
  public long getStartTime(int index) {
    return subPictures.get(index).getStartTime();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#isForced(int)
   */
  public boolean isForced(int index) {
    return subPictures.get(index).isForced();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getStartOffset(int)
   */
  public long getStartOffset(int index) {
    SubPictureBD pic = subPictures.get(index);
    return pic.getImageObject().getFragmentList().get(0).getImageBufferOfs();
  }

  /**
   * Get frame rate for given caption
   *
   * @param index index of caption
   * @return frame rate
   */
  public double getFps(int index) {
    return Framerate.valueForId(subPictures.get(index).getType());
  }
}
예제 #2
0
/** Reading and writing of Blu-Ray captions in Xml/Png format. */
public class SupXml implements SubtitleStream {

  private static final Configuration configuration = Configuration.getInstance();
  private static final Logger logger = Logger.getInstance();

  /** ArrayList of captions contained in the current file */
  private List<SubPictureXml> subPictures = new ArrayList<SubPictureXml>();
  /** color palette of the last decoded caption */
  private Palette palette;
  /** bitmap of the last decoded caption */
  private Bitmap bitmap;
  /** index of dominant color for the current caption */
  private int primaryColorIndex;
  /** number of forced captions in the current file */
  private int numForcedFrames;

  /** path of the input stream */
  private String pathName;
  /** file name of XML file used as title */
  private String title;
  /** language id read from the xml */
  private String language = "eng";
  /** resolution read from the xml */
  private Resolution resolution = Resolution.HD_1080;
  /** frame rate read from the stream */
  private double fps = Framerate.FPS_23_976.getValue();
  /** converted xml frame rate read from the stream */
  private double fpsXml = XmlFps(fps);

  /**
   * Constructor (for reading)
   *
   * @param filename file name of Xml file to read
   * @throws CoreException
   */
  public SupXml(String filename) throws CoreException {
    this.pathName = FilenameUtils.addSeparator(FilenameUtils.getParent(filename));
    this.title = FilenameUtils.removeExtension(FilenameUtils.getName(filename));

    SAXParserFactory factory = SAXParserFactory.newInstance();
    SAXParser saxParser;
    try {
      saxParser = factory.newSAXParser();
      DefaultHandler handler = new XmlHandler();
      saxParser.parse(new File(filename), handler);
    } catch (ParserConfigurationException e) {
      throw new CoreException(e.getMessage());
    } catch (SAXException e) {
      throw new CoreException(e.getMessage());
    } catch (IOException e) {
      throw new CoreException(e.getMessage());
    }

    logger.trace("\nDetected " + numForcedFrames + " forced captions.\n");
  }

  /**
   * Return an integer frame rate in BDN XML style
   *
   * @param fps source frame rate
   * @return next integer frame rate (yet returned as double)
   */
  private static double XmlFps(double fps) {
    if (fps == Framerate.FPS_23_975.getValue()) {
      return Framerate.FPS_24.getValue();
    } else if (fps == Framerate.FPS_23_976.getValue()) {
      return Framerate.FPS_24.getValue();
    } else if (fps == Framerate.NTSC.getValue()) {
      return 30.0;
    } else if (fps == Framerate.NTSC_I.getValue()) {
      return 60.0;
    } else {
      return fps;
    }
  }

  /* (non-Javadoc)
   * @see deadbeef.SupTools.SubtitleStream#close()
   */
  @Override
  public void close() {}

  /* (non-Javadoc)
   * @see deadbeef.SupTools.SubtitleStream#decode(int)
   */
  @Override
  public void decode(int index) throws CoreException {
    try {
      File f = new File(subPictures.get(index).getFileName());
      if (!f.exists()) {
        throw new CoreException("file " + subPictures.get(index).getFileName() + " not found.");
      }
      BufferedImage img = ImageIO.read(f);
      int w = img.getWidth();
      int h = img.getHeight();

      this.palette = null;

      // first try to read image and palette directly from imported image
      if (img.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
        IndexColorModel icm = (IndexColorModel) img.getColorModel();
        if (icm.getMapSize() < 255 || (icm.hasAlpha() && icm.getAlpha(255) == 0)) {
          // create palette
          palette = new Palette(256);
          for (int i = 0; i < icm.getMapSize(); i++) {
            int alpha = (icm.getRGB(i) >> 24) & 0xff;
            if (alpha >= configuration.getAlphaCrop()) {
              palette.setARGB(i, icm.getRGB(i));
            } else {
              palette.setARGB(i, 0);
            }
          }
          // copy pixels
          WritableRaster raster = img.getRaster();
          bitmap =
              new Bitmap(
                  img.getWidth(),
                  img.getHeight(),
                  (byte[]) raster.getDataElements(0, 0, img.getWidth(), img.getHeight(), null));
        }
      }

      // if this failed, assume RGB image and quantize palette
      if (palette == null) {
        // grab int array (ARGB)
        int[] pixels = new int[w * h];
        img.getRGB(0, 0, w, h, pixels, 0, w);
        // quantize image
        QuantizeFilter qf = new QuantizeFilter();
        bitmap = new Bitmap(img.getWidth(), img.getHeight());
        int ct[] = qf.quantize(pixels, bitmap.getInternalBuffer(), w, h, 255, false, false);
        int size = ct.length;
        if (size > 255) {
          logger.warn("Quantizer failed.\n");
          size = 255;
        }
        // create palette
        palette = new Palette(256);
        for (int i = 0; i < size; i++) {
          int alpha = (ct[i] >> 24) & 0xff;
          if (alpha >= configuration.getAlphaCrop()) {
            palette.setARGB(i, ct[i]);
          } else {
            palette.setARGB(i, 0);
          }
        }
      }
      primaryColorIndex =
          bitmap.getPrimaryColorIndex(
              palette.getAlpha(), configuration.getAlphaThreshold(), palette.getY());
      // crop
      BitmapBounds bounds =
          bitmap.getCroppingBounds(palette.getAlpha(), configuration.getAlphaCrop());
      if (bounds.yMin > 0
          || bounds.xMin > 0
          || bounds.xMax < bitmap.getWidth() - 1
          || bounds.yMax < bitmap.getHeight() - 1) {
        w = bounds.xMax - bounds.xMin + 1;
        h = bounds.yMax - bounds.yMin + 1;
        if (w < 2) {
          w = 2;
        }
        if (h < 2) {
          h = 2;
        }
        bitmap = bitmap.crop(bounds.xMin, bounds.yMin, w, h);
        // update picture
        SubPictureXml pic = subPictures.get(index);
        pic.setImageWidth(w);
        pic.setImageHeight(h);
        pic.setOfsX(pic.getOriginalXOffset() + bounds.xMin);
        pic.setOfsY(pic.getOriginalYOffset() + bounds.yMin);
      }
    } catch (IOException e) {
      throw new CoreException(e.getMessage());
    } catch (OutOfMemoryError e) {
      JOptionPane.showMessageDialog(
          null,
          "Out of heap! Use -Xmx256m to increase heap!",
          "Error!",
          JOptionPane.WARNING_MESSAGE);
      throw new CoreException("Out of heap! Use -Xmx256m to increase heap!");
    }
  }

  /**
   * Create Xml file
   *
   * @param fname file name
   * @param pics Map of SubPictures and their original indexes which were used to generate the png
   *     file names
   * @throws CoreException
   */
  public static void writeXml(String fname, SortedMap<Integer, SubPicture> pics)
      throws CoreException {
    double fps = configuration.getFpsTrg();
    double fpsXml = XmlFps(fps);
    BufferedWriter out = null;
    String name = FilenameUtils.removeExtension(FilenameUtils.getName(fname));
    try {
      out = new BufferedWriter(new FileWriter(fname));
      out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
      out.newLine();
      out.write(
          "<BDN Version=\"0.93\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"BD-03-006-0093b BDN File Format.xsd\">");
      out.newLine();
      out.write("  <Description>");
      out.newLine();
      out.write("    <Name Title=\"" + name + "\" Content=\"\"/>");
      out.newLine();
      out.write("    <Language Code=\"" + LANGUAGES[configuration.getLanguageIdx()][2] + "\"/>");
      out.newLine();
      String res = configuration.getOutputResolution().getResolutionNameForXml();
      out.write(
          "    <Format VideoFormat=\""
              + res
              + "\" FrameRate=\""
              + ToolBox.formatDouble(fps)
              + "\" DropFrame=\"False\"/>");
      out.newLine();
      long t = pics.get(pics.firstKey()).getStartTime();
      if (fps != fpsXml) {
        t = (t * 2000 + 1001) / 2002;
      }
      String ts = ptsToTimeStrXml(t, fpsXml);
      t = pics.get(pics.lastKey()).getEndTime();
      if (fps != fpsXml) {
        t = (t * 2000 + 1001) / 2002;
      }
      String te = ptsToTimeStrXml(t, fpsXml);
      out.write(
          "    <Events Type=\"Graphic\" FirstEventInTC=\""
              + ts
              + "\" LastEventOutTC=\""
              + te
              + "\" NumberofEvents=\""
              + pics.size()
              + "\"/>");
      out.newLine();
      out.write("  </Description>");
      out.newLine();
      out.write("  <Events>");
      out.newLine();

      for (int idx : pics.keySet()) {
        SubPicture p = pics.get(idx);
        t = p.getStartTime();
        if (fps != fpsXml) {
          t = (t * 2000 + 1001) / 2002;
        }
        ts = ptsToTimeStrXml(t, fpsXml);
        t = p.getEndTime();
        if (fps != fpsXml) {
          t = (t * 2000 + 1001) / 2002;
        }
        te = ptsToTimeStrXml(t, fpsXml);
        String forced = p.isForced() ? "True" : "False";
        out.write("    <Event InTC=\"" + ts + "\" OutTC=\"" + te + "\" Forced=\"" + forced + "\">");
        out.newLine();

        String pname = getPNGname(name, idx + 1);
        out.write(
            "      <Graphic Width=\""
                + p.getImageWidth()
                + "\" Height=\""
                + p.getImageHeight()
                + "\" X=\""
                + p.getXOffset()
                + "\" Y=\""
                + p.getYOffset()
                + "\">"
                + pname
                + "</Graphic>");
        out.newLine();
        out.write("    </Event>");
        out.newLine();
      }
      out.write("  </Events>");
      out.newLine();
      out.write("</BDN>");
      out.newLine();
    } catch (IOException ex) {
      throw new CoreException(ex.getMessage());
    } finally {
      try {
        if (out != null) {
          out.close();
        }
      } catch (IOException ex) {
      }
    }
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getBitmap()
   */
  public Bitmap getBitmap() {
    return bitmap;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getImage()
   */
  public BufferedImage getImage() {
    return bitmap.getImage(palette.getColorModel());
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getImage(Bitmap)
   */
  public BufferedImage getImage(final Bitmap bm) {
    return bm.getImage(palette.getColorModel());
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getForcedFrameCount()
   */
  public int getForcedFrameCount() {
    return numForcedFrames;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getNumFrames()
   */
  public int getFrameCount() {
    return subPictures.size();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getPalette()
   */
  public Palette getPalette() {
    return palette;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getPrimaryColorIndex()
   */
  public int getPrimaryColorIndex() {
    return primaryColorIndex;
  }

  /* (non-Javadoc)
   * @see deadbeef.SupTools.SubtitleStream#getStartOffset(int)
   */
  public long getStartOffset(int index) {
    // dummy
    return 0;
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getSubPicture(int)
   */
  public SubPicture getSubPicture(int index) {
    return subPictures.get(index);
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getEndTime(int)
   */
  public long getEndTime(int index) {
    return subPictures.get(index).getEndTime();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#getStartTime(int)
   */
  public long getStartTime(final int index) {
    return subPictures.get(index).getStartTime();
  }

  /* (non-Javadoc)
   * @see SubtitleStream#isForced(int)
   */
  public boolean isForced(int index) {
    return subPictures.get(index).isForced();
  }

  /**
   * Create PNG name from (xml) file name and index
   *
   * @param fn file name
   * @param idx index
   * @return PNG name
   */
  public static String getPNGname(String fn, int idx) {
    return fn + "_" + ToolBox.leftZeroPad(idx, 4) + ".png";
  }

  /**
   * get language read from Xml
   *
   * @return language as String
   */
  public String getLanguage() {
    return language;
  }

  /**
   * get fps read from Xml
   *
   * @return frame rate as double
   */
  public double getFps() {
    return fps;
  }

  enum XmlState {
    BDN,
    DESCRIPT,
    NAME,
    LANGUAGE,
    FORMAT,
    EVENTS,
    EVENT,
    GRAPHIC,
    UNKNOWN
  }

  private static final String xmlStates[] = {
    "bdn", "description", "name", "language", "format", "events", "event", "graphic"
  };

  class XmlHandler extends DefaultHandler {

    XmlState state;
    StringBuffer txt;
    boolean valid;
    SubPictureXml pic;

    private XmlState findState(final String s) {
      for (XmlState x : XmlState.values()) {
        if (s.toLowerCase().equals(xmlStates[x.ordinal()])) {
          return x;
        }
      }
      return XmlState.UNKNOWN;
    }

    @Override
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
      state = findState(qName);
      String at;

      if (state != XmlState.BDN && !valid) {
        logger.error("BDN tag missing");
      }

      txt = null;

      switch (state) {
        case UNKNOWN:
          logger.error("Unknown tag " + qName + "\n");
          break;
        case BDN:
          if (valid) {
            logger.error("BDN must be used only once");
          } else {
            valid = true;
          }
          break;
        case NAME:
          at = atts.getValue("Title");
          if (at != null) {
            title = at;
            logger.trace("Title: " + title + "\n");
          }
          break;
        case LANGUAGE:
          at = atts.getValue("Code");
          if (at != null) {
            language = at;
            logger.trace("Language: " + language + "\n");
          }
          break;
        case FORMAT:
          at = atts.getValue("FrameRate");
          if (at != null) {
            fps = SubtitleUtils.getFps(at);
            fpsXml = XmlFps(fps);
            logger.trace("fps: " + ToolBox.formatDouble(fps) + "\n");
          }
          at = atts.getValue("VideoFormat");
          if (at != null) {
            String res = at;
            for (Resolution r : Resolution.values()) {
              if (res.length() == 4
                  && res.charAt(0) != '7') { // hack to rename 480p/576p to 480i/576i
                res = res.replace('p', 'i');
              }
              if (r.getResolutionNameForXml().equalsIgnoreCase(res)) {
                resolution = r;
                logger.trace("Language: " + r.getResolutionNameForXml() + "\n");
                break;
              }
            }
          }
          break;
        case EVENTS:
          at = atts.getValue("NumberofEvents");
          if (at != null) {
            int n = ToolBox.getInt(at);
            if (n > 0) {
              /* number of subtitles read from the xml */
              Core.setProgressMax(n);
            }
          }
          break;
        case EVENT:
          pic = new SubPictureXml();
          subPictures.add(pic);
          int num = subPictures.size();
          logger.info("#" + num + "\n");
          Core.setProgress(num);
          at = atts.getValue("InTC");
          if (at != null) {
            pic.setStartTime(timeStrXmlToPTS(at, fpsXml));
            if (pic.getStartTime() == -1) {
              pic.setStartTime(0);
              logger.warn("Invalid start time " + at + "\n");
            }
          }
          at = atts.getValue("OutTC");
          if (at != null) {
            pic.setEndTime(timeStrXmlToPTS(at, fpsXml));
            if (pic.getEndTime() == -1) {
              pic.setEndTime(0);
              logger.warn("Invalid end time " + at + "\n");
            }
          }
          if (fps != fpsXml) {
            pic.setStartTime((pic.getStartTime() * 1001 + 500) / 1000);
            pic.setEndTime((pic.getEndTime() * 1001 + 500) / 1000);
          }
          at = atts.getValue("Forced");
          pic.setForced(at != null && at.equalsIgnoreCase("true"));
          if (pic.isForced()) {
            numForcedFrames++;
          }
          int dim[] = resolution.getDimensions();
          pic.setWidth(dim[0]);
          pic.setHeight(dim[1]);
          break;
        case GRAPHIC:
          pic.setImageWidth(ToolBox.getInt(atts.getValue("Width")));
          pic.setImageHeight(ToolBox.getInt(atts.getValue("Height")));
          pic.setOfsX(ToolBox.getInt(atts.getValue("X")));
          pic.setOfsY(ToolBox.getInt(atts.getValue("Y")));
          pic.storeOriginalOffsets();
          txt = new StringBuffer();
          break;
      }
    }

    @Override
    public void endElement(String namespaceURI, String localName, String qName) {
      XmlState endState = findState(qName);
      if (state == XmlState.GRAPHIC && endState == XmlState.GRAPHIC) {
        pic.setFileName(pathName + txt.toString().trim());
      }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
      if (txt != null) {
        txt.append(ch, start, length);
      }
    }
  }
}