Package bdsup2sub.supstream.dvd

Source Code of bdsup2sub.supstream.dvd.SupDvd

/*
* Copyright 2014 Volker Oth (0xdeadbeef) / Miklos Juhasz (mjuhasz)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bdsup2sub.supstream.dvd;

import bdsup2sub.bitmap.Bitmap;
import bdsup2sub.bitmap.BitmapBounds;
import bdsup2sub.bitmap.Palette;
import bdsup2sub.core.Configuration;
import bdsup2sub.core.Core;
import bdsup2sub.core.CoreException;
import bdsup2sub.core.Logger;
import bdsup2sub.supstream.ImageObjectFragment;
import bdsup2sub.supstream.SubPicture;
import bdsup2sub.tools.FileBuffer;
import bdsup2sub.tools.FileBufferException;
import bdsup2sub.utils.ToolBox;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import static bdsup2sub.utils.ByteUtils.*;
import static bdsup2sub.utils.ToolBox.toHexLeftZeroPadded;

/**
* Handling of DVD SUP/IFO streams.
*/
public class SupDvd implements DvdSubtitleStream {

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

    private List<SubPictureDVD> subPictures = new ArrayList<SubPictureDVD>();
    private int screenWidth = 720;
    private int screenHeight = 576;
    private int languageIdx;

    private Palette srcPalette;
    /** color palette created for last decoded caption  */
    private Palette palette;
    /** bitmap of the last decoded caption  */
    private Bitmap bitmap;
    /** FileBuffer for reading SUP */
    private FileBuffer fileBuffer;
    /** index of dominant color for the current caption  */
    private int primaryColorIndex;
    /** number of forced captions in the current file  */
    private int numForcedFrames;

    /** store last alpha values for invisible workaround */
    private static int[] lastAlpha = { 0, 0xf, 0xf, 0xf };


    public SupDvd(String supFile, String ifoFile) throws CoreException {
        IfoParser ifoParser = new IfoParser(ifoFile);
        this.screenHeight = ifoParser.getScreenHeight();
        this.screenWidth = ifoParser.getScreenWidth();
        this.languageIdx = ifoParser.getLanguageIdx();
        this.srcPalette = ifoParser.getSrcPalette();

        readSupFile(supFile);
    }

    private void readSupFile(String supFile) throws CoreException {
        try {
            long offset = 0;
            fileBuffer = new FileBuffer(supFile);
            long size = fileBuffer.getSize();
            Core.setProgressMax((int) size);
            int i = 0;
            do {
                logger.info("# " + (++i) + "\n");
                Core.setProgress(offset);
                logger.trace("Offset: " + ToolBox.toHexLeftZeroPadded(offset, 8) + "\n");
                offset = readSupFrame(offset, fileBuffer);
            } while (offset < size);
        } catch (FileBufferException e) {
            throw new CoreException(e.getMessage());
        }
        logger.info("\nDetected " + numForcedFrames + " forced captions.\n");
    }

    private long readSupFrame(long offset, FileBuffer buffer) throws CoreException  {
        long ctrlOffset;
        int  ctrlOfsRel;
        int  rleSize;
        int  ctrlSize;
        ImageObjectFragment rleFrag;
        int  length;
        byte[] ctrlHeader;

        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());
        }
    }

    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");
        }
    }

    private void decode(SubPictureDVD pic) throws CoreException {
        palette = SupDvdUtil.decodePalette(pic, srcPalette);
        bitmap  = SupDvdUtil.decodeImage(pic, fileBuffer, palette.getIndexOfMostTransparentPaletteEntry());

        // 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) {
            int width = bounds.xMax - bounds.xMin + 1;
            int height = bounds.yMax - bounds.yMin + 1;
            if (width < 2) {
                width = 2;
            }
            if (height < 2) {
                height = 2;
            }
            bitmap = bitmap.crop(bounds.xMin, bounds.yMin, width, height);
            // update picture
            pic.setImageWidth(width);
            pic.setImageHeight(height);
            pic.setOfsX(pic.getOriginalX() + bounds.xMin);
            pic.setOfsY(pic.getOriginalY() + bounds.yMin);
        }
        primaryColorIndex = bitmap.getPrimaryColorIndex(palette.getAlpha(), configuration.getAlphaThreshold(), palette.getY());
    }

    public int[] getFramePalette(int index) {
        return subPictures.get(index).getPal();
    }

    public int[] getOriginalFramePalette(int index) {
        return subPictures.get(index).getOriginalPal();
    }

    public int[] getFrameAlpha(int index) {
        return subPictures.get(index).getAlpha();
    }

    public int[] getOriginalFrameAlpha(int index) {
        return subPictures.get(index).getOriginalAlpha();
    }

    public BufferedImage getImage(Bitmap bm) {
        return bm.getImage(palette.getColorModel());
    }

    public Palette getPalette() {
        return palette;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public BufferedImage getImage() {
        return bitmap.getImage(palette.getColorModel());
    }

    public int getPrimaryColorIndex() {
        return primaryColorIndex;
    }

    public SubPicture getSubPicture(int index) {
        return subPictures.get(index);
    }

    public int getFrameCount() {
        return subPictures.size();
    }

    public int getForcedFrameCount() {
        return numForcedFrames;
    }

    public boolean isForced(int index) {
        return subPictures.get(index).isForced();
    }

    public void close() {
        if (fileBuffer != null) {
            fileBuffer.close();
        }
    }

    public long getEndTime(int index) {
        return subPictures.get(index).getEndTime();
    }

    public long getStartTime(int index) {
        return subPictures.get(index).getStartTime();
    }

    public long getStartOffset(int index) {
        return subPictures.get(index).getOffset();
    }

    public int getLanguageIndex() {
        return languageIdx;
    }

    public Palette getSrcPalette() {
        return srcPalette;
    }

    public void setSrcPalette(Palette pal) {
        srcPalette = pal;
    }
}
TOP

Related Classes of bdsup2sub.supstream.dvd.SupDvd

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.