try {
// 2 bytes: packet identifier 0x5350
long startOffset = offset;
if (buffer.getWord(offset) != 0x5350) {
throw new CoreException("Missing packet identifier at offset " + ToolBox.toHexLeftZeroPadded(offset, 8));
}
// 8 bytes PTS: system clock reference, but use only the first 4
SubPictureDVD pic = new SubPictureDVD();
pic.setOffset(offset);
pic.setWidth(screenWidth);
pic.setHeight(screenHeight);
int pts = buffer.getDWordLE(offset += 2);
pic.setStartTime(pts);
// 2 bytes: packet length (number of bytes after this entry)
length = buffer.getWord(offset += 8);
// 2 bytes: offset to control buffer
ctrlOfsRel = buffer.getWord(offset += 2);
rleSize = ctrlOfsRel - 2; // calculate size of RLE buffer
ctrlSize = length - ctrlOfsRel - 2; // calculate size of control header
if (ctrlSize < 0) {
throw new CoreException("Invalid control buffer size");
}
ctrlOffset = ctrlOfsRel + offset; // absolute offset of control header
offset += 2;
pic.setRleFragments(new ArrayList<ImageObjectFragment>(1));
rleFrag = new ImageObjectFragment(offset, rleSize);
pic.getRleFragments().add(rleFrag);
pic.setRleSize(rleSize);
pic.setPal(new int[4]);
pic.setAlpha(new int[4]);
int alphaSum = 0;
int[] alphaUpdate = new int[4];
int alphaUpdateSum;
int delay = -1;
boolean colorAlphaUpdate = false;
logger.trace("SP_DCSQT at ofs: " + ToolBox.toHexLeftZeroPadded(ctrlOffset, 8) + "\n");
// copy control header in buffer (to be more compatible with VobSub)
ctrlHeader = new byte[ctrlSize];
for (int i=0; i < ctrlSize; i++) {
ctrlHeader[i] = (byte)buffer.getByte(ctrlOffset + i);
}
try {
// parse control header
int b;
int index = 0;
int endSeqOfs = getWord(ctrlHeader, index) - ctrlOfsRel - 2;
if (endSeqOfs < 0 || endSeqOfs > ctrlSize) {
logger.warn("Invalid end sequence offset -> no end time\n");
endSeqOfs = ctrlSize;
}
index += 2;
parse_ctrl:
while (index < endSeqOfs) {
int cmd = getByte(ctrlHeader, index++);
switch (cmd) {
case 0: // forced (?)
pic.setForced(true);
numForcedFrames++;
break;
case 1: // start display
break;
case 3: // palette info
b = getByte(ctrlHeader, index++);
pic.getPal()[3] = (b >> 4);
pic.getPal()[2] = b & 0x0f;
b = getByte(ctrlHeader, index++);
pic.getPal()[1] = (b >> 4);
pic.getPal()[0] = b & 0x0f;
logger.trace("Palette: " + pic.getPal()[0] + ", " + pic.getPal()[1] + ", " + pic.getPal()[2] + ", " + pic.getPal()[3] + "\n");
break;
case 4: // alpha info
b = getByte(ctrlHeader, index++);
pic.getAlpha()[3] = (b >> 4);
pic.getAlpha()[2] = b & 0x0f;
b = getByte(ctrlHeader, index++);
pic.getAlpha()[1] = (b >> 4);
pic.getAlpha()[0] = b & 0x0f;
for (int i = 0; i < 4; i++) {
alphaSum += pic.getAlpha()[i] & 0xff;
}
logger.trace("Alpha: " + pic.getAlpha()[0] + ", " + pic.getAlpha()[1] + ", " + pic.getAlpha()[2] + ", " + pic.getAlpha()[3] + "\n");
break;
case 5: // coordinates
int xOfs = (getByte(ctrlHeader, index) << 4) | (getByte(ctrlHeader, index+1) >> 4);
pic.setOfsX(xOfs);
pic.setImageWidth((((getByte(ctrlHeader, index + 1) & 0xf) << 8) | (getByte(ctrlHeader, index + 2))) - xOfs + 1);
int yOfs = (getByte(ctrlHeader, index + 3) << 4) | (getByte(ctrlHeader, index + 4) >> 4);
pic.setOfsY(yOfs);
pic.setImageHeight((((getByte(ctrlHeader, index + 4) & 0xf) << 8) | (getByte(ctrlHeader, index + 5))) - yOfs + 1);
logger.trace("Area info:" + " ("
+ pic.getXOffset() + ", " + pic.getYOffset() + ") - (" + (pic.getXOffset() + pic.getImageWidth() - 1) + ", "
+ (pic.getYOffset() + pic.getImageHeight() - 1) + ")\n");
index += 6;
break;
case 6: // offset to RLE buffer
pic.setEvenOffset(getWord(ctrlHeader, index) - 4);
pic.setOddOffset(getWord(ctrlHeader, index + 2) - 4);
index += 4;
logger.trace("RLE ofs: " + ToolBox.toHexLeftZeroPadded(pic.getEvenOffset(), 4) + ", " + ToolBox.toHexLeftZeroPadded(pic.getOddOffset(), 4) + "\n");
break;
case 7: // color/alpha update
colorAlphaUpdate = true;
//int len = ToolBox.getWord(ctrlHeader, index);
// ignore the details for now, but just get alpha and palette info
alphaUpdateSum = 0;
b = getByte(ctrlHeader, index + 10);
alphaUpdate[3] = (b >> 4);
alphaUpdate[2] = b & 0x0f;
b = getByte(ctrlHeader, index + 11);
alphaUpdate[1] = (b >> 4);
alphaUpdate[0] = b & 0x0f;
for (int i = 0; i < 4; i++) {
alphaUpdateSum += alphaUpdate[i] & 0xff;
}
// only use more opaque colors
if (alphaUpdateSum > alphaSum) {
alphaSum = alphaUpdateSum;
System.arraycopy(alphaUpdate, 0, pic.getAlpha(), 0, 4);
// take over frame palette
b = getByte(ctrlHeader, index+8);
pic.getPal()[3] = (b >> 4);
pic.getPal()[2] = b & 0x0f;
b = getByte(ctrlHeader, index+9);
pic.getPal()[1] = (b >> 4);
pic.getPal()[0] = b & 0x0f;
}
// search end sequence
index = endSeqOfs;
delay = getWord(ctrlHeader, index) * 1024;
endSeqOfs = getWord(ctrlHeader, index + 2)-ctrlOfsRel - 2;
if (endSeqOfs < 0 || endSeqOfs > ctrlSize) {
logger.warn("Invalid end sequence offset -> no end time\n");
endSeqOfs = ctrlSize;
}
index += 4;
break;
case 0xff: // end sequence
break parse_ctrl;
default:
logger.warn("Unknown control sequence " + toHexLeftZeroPadded(cmd, 2) + " skipped\n");
break;
}
}
if (endSeqOfs != ctrlSize) {
int ctrlSeqCount = 1;
index = -1;
int nextIndex = endSeqOfs;
while (nextIndex != index) {
index = nextIndex;
delay = getWord(ctrlHeader, index) * 1024;
nextIndex = getWord(ctrlHeader, index + 2) - ctrlOfsRel - 2;
ctrlSeqCount++;
}
if (ctrlSeqCount > 2) {
logger.warn("Control sequence(s) ignored - result may be erratic.");
}
pic.setEndTime(pic.getStartTime() + delay);
} else {
pic.setEndTime(pic.getStartTime());
}
pic.storeOriginal();
if (colorAlphaUpdate) {
logger.warn("Palette update/alpha fading detected - result may be erratic.\n");
}
if (alphaSum == 0) {
if (configuration.getFixZeroAlpha()) {
System.arraycopy(lastAlpha, 0, pic.getAlpha(), 0, 4);
logger.warn("Invisible caption due to zero alpha - used alpha info of last caption.\n");
} else {
logger.warn("Invisible caption due to zero alpha (not fixed due to user setting).\n");
}
}
lastAlpha = pic.getAlpha();
} catch (IndexOutOfBoundsException ex) {
throw new CoreException("Index " + ex.getMessage() + " out of bounds in control header.");
}
subPictures.add(pic);
return startOffset + length + 0x0a;
} catch (FileBufferException ex) {
throw new CoreException(ex.getMessage());
}
}