Пример #1
0
  /**
   * 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());
    }
  }
Пример #2
0
 /**
  * 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());
 }
Пример #3
0
  /**
   * Create the binary stream representation of one caption
   *
   * @param pic SubPicture object containing caption info
   * @param bm bitmap
   * @return byte buffer containing the binary stream representation of one caption
   */
  public static byte[] createSubFrame(SubPictureDVD pic, Bitmap bm) {
    /* create RLE buffers */
    byte even[] = SupDvdUtil.encodeLines(bm, true);
    byte odd[] = SupDvdUtil.encodeLines(bm, false);
    int tmp;

    int forcedOfs;
    int controlHeaderLen;
    if (pic.isForced()) {
      forcedOfs = 0;
      SubDvd.CONTROL_HEADER[2] = 0x01; // display
      SubDvd.CONTROL_HEADER[3] = 0x00; // forced
      controlHeaderLen = SubDvd.CONTROL_HEADER.length;
    } else {
      forcedOfs = 1;
      SubDvd.CONTROL_HEADER[2] = 0x00; // part of offset
      SubDvd.CONTROL_HEADER[3] = 0x01; // display
      controlHeaderLen = SubDvd.CONTROL_HEADER.length - 1;
    }

    // fill out all info but the offets (determined later)

    /* header - contains PTM */
    int ptm = (int) pic.getStartTime(); // should be end time, but STC writes start time?
    SubDvd.HEADER_FIRST[9] = (byte) (((ptm >> 29) & 0x0E) | 0x21);
    SubDvd.HEADER_FIRST[10] = (byte) (ptm >> 22);
    SubDvd.HEADER_FIRST[11] = (byte) ((ptm >> 14) | 1);
    SubDvd.HEADER_FIRST[12] = (byte) (ptm >> 7);
    SubDvd.HEADER_FIRST[13] = (byte) (ptm * 2 + 1);

    /* control header */
    /* palette (store reversed) */
    SubDvd.CONTROL_HEADER[1 + 4] =
        (byte) (((pic.getPal()[3] & 0xf) << 4) | (pic.getPal()[2] & 0x0f));
    SubDvd.CONTROL_HEADER[1 + 5] =
        (byte) (((pic.getPal()[1] & 0xf) << 4) | (pic.getPal()[0] & 0x0f));
    /* alpha (store reversed) */
    SubDvd.CONTROL_HEADER[1 + 7] =
        (byte) (((pic.getAlpha()[3] & 0xf) << 4) | (pic.getAlpha()[2] & 0x0f));
    SubDvd.CONTROL_HEADER[1 + 8] =
        (byte) (((pic.getAlpha()[1] & 0xf) << 4) | (pic.getAlpha()[0] & 0x0f));

    /* coordinates of subtitle */
    SubDvd.CONTROL_HEADER[1 + 10] = (byte) ((pic.getXOffset() >> 4) & 0xff);
    tmp = pic.getXOffset() + bm.getWidth() - 1;
    SubDvd.CONTROL_HEADER[1 + 11] = (byte) (((pic.getXOffset() & 0xf) << 4) | ((tmp >> 8) & 0xf));
    SubDvd.CONTROL_HEADER[1 + 12] = (byte) (tmp & 0xff);

    int yOfs = pic.getYOffset() - configuration.getCropOffsetY();
    if (yOfs < 0) {
      yOfs = 0;
    } else {
      int yMax = pic.getHeight() - pic.getImageHeight() - 2 * configuration.getCropOffsetY();
      if (yOfs > yMax) {
        yOfs = yMax;
      }
    }

    SubDvd.CONTROL_HEADER[1 + 13] = (byte) ((yOfs >> 4) & 0xff);
    tmp = yOfs + bm.getHeight() - 1;
    SubDvd.CONTROL_HEADER[1 + 14] = (byte) (((yOfs & 0xf) << 4) | ((tmp >> 8) & 0xf));
    SubDvd.CONTROL_HEADER[1 + 15] = (byte) (tmp & 0xff);

    /* offset to even lines in rle buffer */
    SubDvd.CONTROL_HEADER[1 + 17] =
        0x00; /* 2 bytes subpicture size and 2 bytes control header ofs */
    SubDvd.CONTROL_HEADER[1 + 18] =
        0x04; /* note: SubtitleCreator uses 6 and adds 0x0000 in between */

    /* offset to odd lines in rle buffer */
    tmp = even.length + SubDvd.CONTROL_HEADER[1 + 18];
    SubDvd.CONTROL_HEADER[1 + 19] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 20] = (byte) (tmp & 0xff);

    /* display duration in frames */
    tmp = (int) ((pic.getEndTime() - pic.getStartTime()) / 1024); // 11.378ms resolution????
    SubDvd.CONTROL_HEADER[1 + 22] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 23] = (byte) (tmp & 0xff);

    /* offset to end sequence - 22 is the offset of the end sequence */
    tmp = even.length + odd.length + 22 + (pic.isForced() ? 1 : 0) + 4;
    SubDvd.CONTROL_HEADER[forcedOfs + 0] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[forcedOfs + 1] = (byte) (tmp & 0xff);
    SubDvd.CONTROL_HEADER[1 + 24] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 25] = (byte) (tmp & 0xff);

    // subpicture size
    tmp = even.length + odd.length + 4 + controlHeaderLen;
    SubDvd.HEADER_FIRST[15] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[16] = (byte) tmp;

    /* offset to control buffer - 2 is the size of the offset */
    tmp = even.length + odd.length + 2;
    SubDvd.HEADER_FIRST[17] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[18] = (byte) tmp;

    // in the SUB format only 0x800 bytes can be written per packet. If a packet
    // is larger, it has to be split into fragments <= 0x800 bytes
    // which follow one after the other.

    int sizeRLE = even.length + odd.length;
    int bufSize =
        SubDvd.PACK_HEADER.length + SubDvd.HEADER_FIRST.length + controlHeaderLen + sizeRLE;
    int numAdditionalPackets = 0;
    if (bufSize > 0x800) {
      // determine how many additional headers we will need
      // considering that each additional header also adds to the size
      // due to its own headers
      numAdditionalPackets = 1;
      int remainingRLEsize =
          sizeRLE
              - (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_FIRST.length); // size - 0x7df
      while (remainingRLEsize
          > (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length - controlHeaderLen)) {
        remainingRLEsize -= (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length);
        bufSize += SubDvd.PACK_HEADER.length + SubDvd.HEADER_NEXT.length;
        numAdditionalPackets++;
      }
      // packet length of the 1st packet should be the maximum size
      tmp = 0x800 - SubDvd.PACK_HEADER.length - 6;
    } else {
      tmp = (bufSize - SubDvd.PACK_HEADER.length - 6);
    }

    // allocate and fill buffer
    byte buf[] = new byte[(1 + numAdditionalPackets) * 0x800];

    int stuffingBytes;
    int diff = buf.length - bufSize;
    if (diff > 0 && diff < 6) {
      stuffingBytes = diff;
    } else {
      stuffingBytes = 0;
    }

    int ofs = 0;
    for (byte packHeader : SubDvd.PACK_HEADER) {
      buf[ofs++] = packHeader;
    }

    // set packet length
    tmp += stuffingBytes;
    SubDvd.HEADER_FIRST[4] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[5] = (byte) tmp;

    // set pts length
    SubDvd.HEADER_FIRST[8] = (byte) (5 + stuffingBytes);

    // write header and use pts for stuffing bytes (if needed)
    for (int i = 0; i < 14; i++) {
      buf[ofs++] = SubDvd.HEADER_FIRST[i];
    }
    for (int i = 0; i < stuffingBytes; i++) {
      buf[ofs++] = (byte) 0xff;
    }
    for (int i = 14; i < SubDvd.HEADER_FIRST.length; i++) {
      buf[ofs++] = SubDvd.HEADER_FIRST[i];
    }

    // write (first part of) RLE buffer
    tmp = sizeRLE;
    if (numAdditionalPackets > 0) {
      tmp = (0x800 - SubDvd.PACK_HEADER.length - stuffingBytes - SubDvd.HEADER_FIRST.length);
      if (tmp > sizeRLE) { // can only happen in 1st buffer
        tmp = sizeRLE;
      }
    }
    for (int i = 0; i < tmp; i++) {
      if (i < even.length) {
        buf[ofs++] = even[i];
      } else {
        buf[ofs++] = odd[i - even.length];
      }
    }
    int ofsRLE = tmp;

    // fill gap in first packet with (parts of) control header
    // only if the control header is split over two packets
    int controlHeaderWritten = 0;
    if (numAdditionalPackets == 1 && ofs < 0x800) {
      for (; ofs < 0x800; ofs++) {
        buf[ofs] = SubDvd.CONTROL_HEADER[forcedOfs + (controlHeaderWritten++)];
      }
    }

    // write additional packets
    for (int p = 0; p < numAdditionalPackets; p++) {
      int rleSizeLeft;
      if (p == numAdditionalPackets - 1) {
        // last loop
        rleSizeLeft = sizeRLE - ofsRLE;
        tmp =
            SubDvd.HEADER_NEXT.length
                + (controlHeaderLen - controlHeaderWritten)
                + (sizeRLE - ofsRLE)
                - 6;
      } else {
        tmp = 0x800 - SubDvd.PACK_HEADER.length - 6;
        rleSizeLeft = (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length);
        // now, again, it could happen that the RLE buffer runs out before the last package
        if (rleSizeLeft > (sizeRLE - ofsRLE)) {
          rleSizeLeft = sizeRLE - ofsRLE;
        }
      }
      // copy packet headers
      SubDvd.PACK_HEADER[13] = (byte) (0xf8);
      for (byte packHeader : SubDvd.PACK_HEADER) {
        buf[ofs++] = packHeader;
      }

      // set packet length
      SubDvd.HEADER_NEXT[4] = (byte) (tmp >> 8);
      SubDvd.HEADER_NEXT[5] = (byte) tmp;
      for (byte b : SubDvd.HEADER_NEXT) {
        buf[ofs++] = b;
      }

      // copy RLE buffer
      for (int i = ofsRLE; i < ofsRLE + rleSizeLeft; i++) {
        if (i < even.length) {
          buf[ofs++] = even[i];
        } else {
          buf[ofs++] = odd[i - even.length];
        }
      }
      ofsRLE += rleSizeLeft;
      // fill possible gap in all but last package with (parts of) control header
      // only if the control header is split over two packets
      // this can only happen in the package before the last one though
      if (p != numAdditionalPackets - 1) {
        for (; ofs < (p + 2) * 0x800; ofs++) {
          buf[ofs] = SubDvd.CONTROL_HEADER[forcedOfs + (controlHeaderWritten++)];
        }
      }
    }

    // write (rest of) control header
    for (int i = controlHeaderWritten; i < controlHeaderLen; i++) {
      buf[ofs++] = SubDvd.CONTROL_HEADER[forcedOfs + i];
    }

    // fill rest of last packet with padding bytes
    diff = buf.length - ofs;
    if (diff >= 6) {
      diff -= 6;
      buf[ofs++] = 0x00;
      buf[ofs++] = 0x00;
      buf[ofs++] = 0x01;
      buf[ofs++] = (byte) 0xbe;
      buf[ofs++] = (byte) (diff >> 8);
      buf[ofs++] = (byte) diff;
      for (; ofs < buf.length; ofs++) {
        buf[ofs] = (byte) 0xff;
      }
    } // else should never happen due to stuffing bytes

    return buf;
  }
Пример #4
0
public final class SubDvdWriter {

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

  private SubDvdWriter() {}

  /**
   * Create the binary stream representation of one caption
   *
   * @param pic SubPicture object containing caption info
   * @param bm bitmap
   * @return byte buffer containing the binary stream representation of one caption
   */
  public static byte[] createSubFrame(SubPictureDVD pic, Bitmap bm) {
    /* create RLE buffers */
    byte even[] = SupDvdUtil.encodeLines(bm, true);
    byte odd[] = SupDvdUtil.encodeLines(bm, false);
    int tmp;

    int forcedOfs;
    int controlHeaderLen;
    if (pic.isForced()) {
      forcedOfs = 0;
      SubDvd.CONTROL_HEADER[2] = 0x01; // display
      SubDvd.CONTROL_HEADER[3] = 0x00; // forced
      controlHeaderLen = SubDvd.CONTROL_HEADER.length;
    } else {
      forcedOfs = 1;
      SubDvd.CONTROL_HEADER[2] = 0x00; // part of offset
      SubDvd.CONTROL_HEADER[3] = 0x01; // display
      controlHeaderLen = SubDvd.CONTROL_HEADER.length - 1;
    }

    // fill out all info but the offets (determined later)

    /* header - contains PTM */
    int ptm = (int) pic.getStartTime(); // should be end time, but STC writes start time?
    SubDvd.HEADER_FIRST[9] = (byte) (((ptm >> 29) & 0x0E) | 0x21);
    SubDvd.HEADER_FIRST[10] = (byte) (ptm >> 22);
    SubDvd.HEADER_FIRST[11] = (byte) ((ptm >> 14) | 1);
    SubDvd.HEADER_FIRST[12] = (byte) (ptm >> 7);
    SubDvd.HEADER_FIRST[13] = (byte) (ptm * 2 + 1);

    /* control header */
    /* palette (store reversed) */
    SubDvd.CONTROL_HEADER[1 + 4] =
        (byte) (((pic.getPal()[3] & 0xf) << 4) | (pic.getPal()[2] & 0x0f));
    SubDvd.CONTROL_HEADER[1 + 5] =
        (byte) (((pic.getPal()[1] & 0xf) << 4) | (pic.getPal()[0] & 0x0f));
    /* alpha (store reversed) */
    SubDvd.CONTROL_HEADER[1 + 7] =
        (byte) (((pic.getAlpha()[3] & 0xf) << 4) | (pic.getAlpha()[2] & 0x0f));
    SubDvd.CONTROL_HEADER[1 + 8] =
        (byte) (((pic.getAlpha()[1] & 0xf) << 4) | (pic.getAlpha()[0] & 0x0f));

    /* coordinates of subtitle */
    SubDvd.CONTROL_HEADER[1 + 10] = (byte) ((pic.getXOffset() >> 4) & 0xff);
    tmp = pic.getXOffset() + bm.getWidth() - 1;
    SubDvd.CONTROL_HEADER[1 + 11] = (byte) (((pic.getXOffset() & 0xf) << 4) | ((tmp >> 8) & 0xf));
    SubDvd.CONTROL_HEADER[1 + 12] = (byte) (tmp & 0xff);

    int yOfs = pic.getYOffset() - configuration.getCropOffsetY();
    if (yOfs < 0) {
      yOfs = 0;
    } else {
      int yMax = pic.getHeight() - pic.getImageHeight() - 2 * configuration.getCropOffsetY();
      if (yOfs > yMax) {
        yOfs = yMax;
      }
    }

    SubDvd.CONTROL_HEADER[1 + 13] = (byte) ((yOfs >> 4) & 0xff);
    tmp = yOfs + bm.getHeight() - 1;
    SubDvd.CONTROL_HEADER[1 + 14] = (byte) (((yOfs & 0xf) << 4) | ((tmp >> 8) & 0xf));
    SubDvd.CONTROL_HEADER[1 + 15] = (byte) (tmp & 0xff);

    /* offset to even lines in rle buffer */
    SubDvd.CONTROL_HEADER[1 + 17] =
        0x00; /* 2 bytes subpicture size and 2 bytes control header ofs */
    SubDvd.CONTROL_HEADER[1 + 18] =
        0x04; /* note: SubtitleCreator uses 6 and adds 0x0000 in between */

    /* offset to odd lines in rle buffer */
    tmp = even.length + SubDvd.CONTROL_HEADER[1 + 18];
    SubDvd.CONTROL_HEADER[1 + 19] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 20] = (byte) (tmp & 0xff);

    /* display duration in frames */
    tmp = (int) ((pic.getEndTime() - pic.getStartTime()) / 1024); // 11.378ms resolution????
    SubDvd.CONTROL_HEADER[1 + 22] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 23] = (byte) (tmp & 0xff);

    /* offset to end sequence - 22 is the offset of the end sequence */
    tmp = even.length + odd.length + 22 + (pic.isForced() ? 1 : 0) + 4;
    SubDvd.CONTROL_HEADER[forcedOfs + 0] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[forcedOfs + 1] = (byte) (tmp & 0xff);
    SubDvd.CONTROL_HEADER[1 + 24] = (byte) ((tmp >> 8) & 0xff);
    SubDvd.CONTROL_HEADER[1 + 25] = (byte) (tmp & 0xff);

    // subpicture size
    tmp = even.length + odd.length + 4 + controlHeaderLen;
    SubDvd.HEADER_FIRST[15] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[16] = (byte) tmp;

    /* offset to control buffer - 2 is the size of the offset */
    tmp = even.length + odd.length + 2;
    SubDvd.HEADER_FIRST[17] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[18] = (byte) tmp;

    // in the SUB format only 0x800 bytes can be written per packet. If a packet
    // is larger, it has to be split into fragments <= 0x800 bytes
    // which follow one after the other.

    int sizeRLE = even.length + odd.length;
    int bufSize =
        SubDvd.PACK_HEADER.length + SubDvd.HEADER_FIRST.length + controlHeaderLen + sizeRLE;
    int numAdditionalPackets = 0;
    if (bufSize > 0x800) {
      // determine how many additional headers we will need
      // considering that each additional header also adds to the size
      // due to its own headers
      numAdditionalPackets = 1;
      int remainingRLEsize =
          sizeRLE
              - (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_FIRST.length); // size - 0x7df
      while (remainingRLEsize
          > (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length - controlHeaderLen)) {
        remainingRLEsize -= (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length);
        bufSize += SubDvd.PACK_HEADER.length + SubDvd.HEADER_NEXT.length;
        numAdditionalPackets++;
      }
      // packet length of the 1st packet should be the maximum size
      tmp = 0x800 - SubDvd.PACK_HEADER.length - 6;
    } else {
      tmp = (bufSize - SubDvd.PACK_HEADER.length - 6);
    }

    // allocate and fill buffer
    byte buf[] = new byte[(1 + numAdditionalPackets) * 0x800];

    int stuffingBytes;
    int diff = buf.length - bufSize;
    if (diff > 0 && diff < 6) {
      stuffingBytes = diff;
    } else {
      stuffingBytes = 0;
    }

    int ofs = 0;
    for (byte packHeader : SubDvd.PACK_HEADER) {
      buf[ofs++] = packHeader;
    }

    // set packet length
    tmp += stuffingBytes;
    SubDvd.HEADER_FIRST[4] = (byte) (tmp >> 8);
    SubDvd.HEADER_FIRST[5] = (byte) tmp;

    // set pts length
    SubDvd.HEADER_FIRST[8] = (byte) (5 + stuffingBytes);

    // write header and use pts for stuffing bytes (if needed)
    for (int i = 0; i < 14; i++) {
      buf[ofs++] = SubDvd.HEADER_FIRST[i];
    }
    for (int i = 0; i < stuffingBytes; i++) {
      buf[ofs++] = (byte) 0xff;
    }
    for (int i = 14; i < SubDvd.HEADER_FIRST.length; i++) {
      buf[ofs++] = SubDvd.HEADER_FIRST[i];
    }

    // write (first part of) RLE buffer
    tmp = sizeRLE;
    if (numAdditionalPackets > 0) {
      tmp = (0x800 - SubDvd.PACK_HEADER.length - stuffingBytes - SubDvd.HEADER_FIRST.length);
      if (tmp > sizeRLE) { // can only happen in 1st buffer
        tmp = sizeRLE;
      }
    }
    for (int i = 0; i < tmp; i++) {
      if (i < even.length) {
        buf[ofs++] = even[i];
      } else {
        buf[ofs++] = odd[i - even.length];
      }
    }
    int ofsRLE = tmp;

    // fill gap in first packet with (parts of) control header
    // only if the control header is split over two packets
    int controlHeaderWritten = 0;
    if (numAdditionalPackets == 1 && ofs < 0x800) {
      for (; ofs < 0x800; ofs++) {
        buf[ofs] = SubDvd.CONTROL_HEADER[forcedOfs + (controlHeaderWritten++)];
      }
    }

    // write additional packets
    for (int p = 0; p < numAdditionalPackets; p++) {
      int rleSizeLeft;
      if (p == numAdditionalPackets - 1) {
        // last loop
        rleSizeLeft = sizeRLE - ofsRLE;
        tmp =
            SubDvd.HEADER_NEXT.length
                + (controlHeaderLen - controlHeaderWritten)
                + (sizeRLE - ofsRLE)
                - 6;
      } else {
        tmp = 0x800 - SubDvd.PACK_HEADER.length - 6;
        rleSizeLeft = (0x800 - SubDvd.PACK_HEADER.length - SubDvd.HEADER_NEXT.length);
        // now, again, it could happen that the RLE buffer runs out before the last package
        if (rleSizeLeft > (sizeRLE - ofsRLE)) {
          rleSizeLeft = sizeRLE - ofsRLE;
        }
      }
      // copy packet headers
      SubDvd.PACK_HEADER[13] = (byte) (0xf8);
      for (byte packHeader : SubDvd.PACK_HEADER) {
        buf[ofs++] = packHeader;
      }

      // set packet length
      SubDvd.HEADER_NEXT[4] = (byte) (tmp >> 8);
      SubDvd.HEADER_NEXT[5] = (byte) tmp;
      for (byte b : SubDvd.HEADER_NEXT) {
        buf[ofs++] = b;
      }

      // copy RLE buffer
      for (int i = ofsRLE; i < ofsRLE + rleSizeLeft; i++) {
        if (i < even.length) {
          buf[ofs++] = even[i];
        } else {
          buf[ofs++] = odd[i - even.length];
        }
      }
      ofsRLE += rleSizeLeft;
      // fill possible gap in all but last package with (parts of) control header
      // only if the control header is split over two packets
      // this can only happen in the package before the last one though
      if (p != numAdditionalPackets - 1) {
        for (; ofs < (p + 2) * 0x800; ofs++) {
          buf[ofs] = SubDvd.CONTROL_HEADER[forcedOfs + (controlHeaderWritten++)];
        }
      }
    }

    // write (rest of) control header
    for (int i = controlHeaderWritten; i < controlHeaderLen; i++) {
      buf[ofs++] = SubDvd.CONTROL_HEADER[forcedOfs + i];
    }

    // fill rest of last packet with padding bytes
    diff = buf.length - ofs;
    if (diff >= 6) {
      diff -= 6;
      buf[ofs++] = 0x00;
      buf[ofs++] = 0x00;
      buf[ofs++] = 0x01;
      buf[ofs++] = (byte) 0xbe;
      buf[ofs++] = (byte) (diff >> 8);
      buf[ofs++] = (byte) diff;
      for (; ofs < buf.length; ofs++) {
        buf[ofs] = (byte) 0xff;
      }
    } // else should never happen due to stuffing bytes

    return buf;
  }

  /**
   * Create VobSub IDX file
   *
   * @param fname file name
   * @param pic a SubPicture object used to read screen width and height
   * @param offsets array of offsets (one for each caption)
   * @param timestamps array of PTS time stamps (one for each caption)
   * @param palette 16 color main Palette
   * @throws bdsup2sub.core.CoreException
   */
  public static void writeIdx(
      String fname, SubPicture pic, int[] offsets, int[] timestamps, Palette palette)
      throws CoreException {
    BufferedWriter out = null;
    try {
      out = new BufferedWriter(new FileWriter(fname));

      out.write("# VobSub index file, v7 (do not modify this line!)");
      out.newLine();
      out.write("# Created by " + Constants.APP_NAME + " " + Constants.APP_VERSION);
      out.newLine();
      out.newLine();
      out.write("# Frame size");
      out.newLine();
      out.write(
          "size: " + pic.getWidth() + "x" + (pic.getHeight() - 2 * configuration.getCropOffsetY()));
      out.newLine();
      out.newLine();
      out.write("# Origin - upper-left corner");
      out.newLine();
      out.write("org: 0, 0");
      out.newLine();
      out.newLine();
      out.write("# Scaling");
      out.newLine();
      out.write("scale: 100%, 100%");
      out.newLine();
      out.newLine();
      out.write("# Alpha blending");
      out.newLine();
      out.write("alpha: 100%");
      out.newLine();
      out.newLine();
      out.write("# Smoothing");
      out.newLine();
      out.write("smooth: OFF");
      out.newLine();
      out.newLine();
      out.write("# Fade in/out in milliseconds");
      out.newLine();
      out.write("fadein/out: 0, 0");
      out.newLine();
      out.newLine();
      out.write("# Force subtitle placement relative to (org.x, org.y)");
      out.newLine();
      out.write("align: OFF at LEFT TOP");
      out.newLine();
      out.newLine();
      out.write("# For correcting non-progressive desync. (in millisecs or hh:mm:ss:ms)");
      out.newLine();
      out.write("time offset: 0");
      out.newLine();
      out.newLine();
      out.write("# ON: displays only forced subtitles, OFF: shows everything");
      out.newLine();
      out.write("forced subs: OFF");
      out.newLine();
      out.newLine();
      out.write("# The palette of the generated file");
      out.newLine();
      out.write("palette: ");
      // Palette pal = Core.getCurrentDVDPalette();
      for (int i = 0; i < palette.getSize(); i++) {
        int rbg[] = palette.getRGB(i);
        int val = (rbg[0] << 16) | (rbg[1] << 8) | rbg[2];
        out.write(ToolBox.toHexLeftZeroPadded(val, 6).substring(2));
        if (i != palette.getSize() - 1) {
          out.write(", ");
        }
      }
      out.newLine();
      out.newLine();
      out.write("# Custom colors (transp idxs and the four colors)");
      out.newLine();
      out.write("custom colors: OFF, tridx: 1000, colors: 000000, 444444, 888888, cccccc");
      out.newLine();
      out.newLine();
      out.write("# Language index in use");
      out.newLine();
      out.write("langidx: 0");
      out.newLine();
      out.newLine();
      out.write("# " + LANGUAGES[configuration.getLanguageIdx()][0]);
      out.newLine();
      out.write("id: " + LANGUAGES[configuration.getLanguageIdx()][1] + ", index: 0");
      out.newLine();
      out.write(
          "# Decomment next line to activate alternative name in DirectVobSub / Windows Media Player 6.x");
      out.newLine();
      out.write("# alt: " + LANGUAGES[configuration.getLanguageIdx()][0]);
      out.newLine();
      out.write("# Vob/Cell ID: 1, 1 (PTS: 0)");
      out.newLine();
      for (int i = 0; i < timestamps.length; i++) {
        out.write("timestamp: " + ptsToTimeStrIdx(timestamps[i]));
        out.write(", filepos: " + ToolBox.toHexLeftZeroPadded(offsets[i], 9).substring(2));
        out.newLine();
      }
    } catch (IOException ex) {
      throw new CoreException(ex.getMessage());
    } finally {
      try {
        if (out != null) {
          out.close();
        }
      } catch (IOException ex) {
      }
    }
  }
}
Пример #5
0
  /**
   * Create VobSub IDX file
   *
   * @param fname file name
   * @param pic a SubPicture object used to read screen width and height
   * @param offsets array of offsets (one for each caption)
   * @param timestamps array of PTS time stamps (one for each caption)
   * @param palette 16 color main Palette
   * @throws bdsup2sub.core.CoreException
   */
  public static void writeIdx(
      String fname, SubPicture pic, int[] offsets, int[] timestamps, Palette palette)
      throws CoreException {
    BufferedWriter out = null;
    try {
      out = new BufferedWriter(new FileWriter(fname));

      out.write("# VobSub index file, v7 (do not modify this line!)");
      out.newLine();
      out.write("# Created by " + Constants.APP_NAME + " " + Constants.APP_VERSION);
      out.newLine();
      out.newLine();
      out.write("# Frame size");
      out.newLine();
      out.write(
          "size: " + pic.getWidth() + "x" + (pic.getHeight() - 2 * configuration.getCropOffsetY()));
      out.newLine();
      out.newLine();
      out.write("# Origin - upper-left corner");
      out.newLine();
      out.write("org: 0, 0");
      out.newLine();
      out.newLine();
      out.write("# Scaling");
      out.newLine();
      out.write("scale: 100%, 100%");
      out.newLine();
      out.newLine();
      out.write("# Alpha blending");
      out.newLine();
      out.write("alpha: 100%");
      out.newLine();
      out.newLine();
      out.write("# Smoothing");
      out.newLine();
      out.write("smooth: OFF");
      out.newLine();
      out.newLine();
      out.write("# Fade in/out in milliseconds");
      out.newLine();
      out.write("fadein/out: 0, 0");
      out.newLine();
      out.newLine();
      out.write("# Force subtitle placement relative to (org.x, org.y)");
      out.newLine();
      out.write("align: OFF at LEFT TOP");
      out.newLine();
      out.newLine();
      out.write("# For correcting non-progressive desync. (in millisecs or hh:mm:ss:ms)");
      out.newLine();
      out.write("time offset: 0");
      out.newLine();
      out.newLine();
      out.write("# ON: displays only forced subtitles, OFF: shows everything");
      out.newLine();
      out.write("forced subs: OFF");
      out.newLine();
      out.newLine();
      out.write("# The palette of the generated file");
      out.newLine();
      out.write("palette: ");
      // Palette pal = Core.getCurrentDVDPalette();
      for (int i = 0; i < palette.getSize(); i++) {
        int rbg[] = palette.getRGB(i);
        int val = (rbg[0] << 16) | (rbg[1] << 8) | rbg[2];
        out.write(ToolBox.toHexLeftZeroPadded(val, 6).substring(2));
        if (i != palette.getSize() - 1) {
          out.write(", ");
        }
      }
      out.newLine();
      out.newLine();
      out.write("# Custom colors (transp idxs and the four colors)");
      out.newLine();
      out.write("custom colors: OFF, tridx: 1000, colors: 000000, 444444, 888888, cccccc");
      out.newLine();
      out.newLine();
      out.write("# Language index in use");
      out.newLine();
      out.write("langidx: 0");
      out.newLine();
      out.newLine();
      out.write("# " + LANGUAGES[configuration.getLanguageIdx()][0]);
      out.newLine();
      out.write("id: " + LANGUAGES[configuration.getLanguageIdx()][1] + ", index: 0");
      out.newLine();
      out.write(
          "# Decomment next line to activate alternative name in DirectVobSub / Windows Media Player 6.x");
      out.newLine();
      out.write("# alt: " + LANGUAGES[configuration.getLanguageIdx()][0]);
      out.newLine();
      out.write("# Vob/Cell ID: 1, 1 (PTS: 0)");
      out.newLine();
      for (int i = 0; i < timestamps.length; i++) {
        out.write("timestamp: " + ptsToTimeStrIdx(timestamps[i]));
        out.write(", filepos: " + ToolBox.toHexLeftZeroPadded(offsets[i], 9).substring(2));
        out.newLine();
      }
    } catch (IOException ex) {
      throw new CoreException(ex.getMessage());
    } finally {
      try {
        if (out != null) {
          out.close();
        }
      } catch (IOException ex) {
      }
    }
  }
Пример #6
0
/** 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());
  }
}
Пример #7
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);
      }
    }
  }
}
Пример #8
0
  /**
   * 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) {
      }
    }
  }
Пример #9
0
  /* (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!");
    }
  }