Package bdsup2sub.supstream.hd

Source Code of bdsup2sub.supstream.hd.SupHD

/*
* 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.hd;

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

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

import static bdsup2sub.utils.TimeUtils.ptsToTimeStr;

/**
* Reading of HD-DVD captions demuxed from EVO transport streams (HD-DVD-SUP).
*/
public class SupHD 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 ArrayList<SubPictureHD> subPictures = new ArrayList<SubPictureHD>();
    /** color palette of the last decoded caption  */
    private Palette palette;
    /** bitmap of the last decoded caption  */
    private Bitmap bitmap;
    /** FileBuffer to read from the file  */
    private FileBuffer buffer;
    /** index of dominant color for the current caption  */
    private int primaryColorIndex;


    public SupHD(String supFile) throws CoreException {
        try {
            buffer = new FileBuffer(supFile);
        } catch (FileBufferException ex) {
            throw new CoreException(ex.getMessage());
        }
        int bufsize = (int)buffer.getSize();

        SubPictureHD pic;
        int index = 0;
        try {
            while (index < bufsize) {
                if (Core.isCanceled()) {
                    throw new CoreException("Canceled by user!");
                }
                Core.setProgress(index);

                if (buffer.getWord(index) != 0x5350) {
                    throw new CoreException("ID 'SP' missing at index " + ToolBox.toHexLeftZeroPadded(index, 8) + "\n");
                }
                int masterIndex = index + 10; //end of header
                pic = new SubPictureHD();
                // hard code size since it's not part of the format???
                pic.setWidth(1920);
                pic.setHeight(1080);
                logger.info("#" + (subPictures.size() + 1) + "\n");
                pic.setStartTime(buffer.getDWordLE(index+=2)); // read PTS
                int packetSize = buffer.getDWord(index+=10);
                // offset to command buffer
                int ofsCmd = buffer.getDWord(index+=4) + masterIndex;
                pic.setImageBufferSize(ofsCmd - (index + 4));
                index  = ofsCmd;
                int dcsq = buffer.getWord(index);
                pic.setStartTime(pic.getStartTime() + (dcsq * 1024));
                logger.info("DCSQ start    ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "  (" + ptsToTimeStr(pic.getStartTime()) + ")\n");
                index += 2; // 2 bytes: dcsq
                int nextIndex = buffer.getDWord(index) + masterIndex; // offset to next dcsq
                index += 5// 4 bytes: offset, 1 byte: start
                int cmd;
                boolean stopDisplay = false;
                boolean stopCommand = false;
                int alphaSum;
                int minAlphaSum = 256 * 256; // 256 fully transparent entries
                while(!stopDisplay) {
                    cmd = buffer.getByte(index++);
                    switch (cmd) {
                        case 0x01:
                            logger.info("DCSQ start    ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "  (" + ptsToTimeStr(pic.getStartTime() + (dcsq * 1024)) + ")\n");
                            logger.warn("DCSQ start ignored due to missing DCSQ stop\n");
                            break;
                        case 0x02:
                            stopDisplay = true;
                            pic.setEndTime(pic.getStartTime() +(dcsq*1024));
                            logger.info("DCSQ stop     ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "  (" + ptsToTimeStr(pic.getEndTime()) + ")\n");
                            break;
                        case 0x83: // palette
                            logger.trace("Palette info  ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "\n");
                            pic.setPaletteOffset(index);
                            index += 0x300;
                            break;
                        case 0x84: // alpha
                            logger.trace("Alpha info    ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "\n");
                            alphaSum = 0;
                            for (int i=index; i < index+0x100; i++) {
                                alphaSum += buffer.getByte(i);
                            }
                            if (alphaSum < minAlphaSum) {
                                pic.setAlphaOffset(index);
                                minAlphaSum = alphaSum;
                            } else {
                                logger.warn("Found faded alpha buffer -> alpha buffer skipped\n");
                            }

                            index += 0x100;
                            break;
                        case 0x85: // area
                            pic.setOfsX((buffer.getByte(index)<<4) | (buffer.getByte(index+1)>>4));
                            pic.setImageWidth((((buffer.getByte(index+1)&0xf)<<8) | (buffer.getByte(index+2))) - pic.getXOffset() + 1);
                            pic.setOfsY((buffer.getByte(index+3)<<4) | (buffer.getByte(index+4)>>4));
                            pic.setImageHeight((((buffer.getByte(index+4)&0xf)<<8) | (buffer.getByte(index+5))) - pic.getYOffset() + 1);
                            logger.trace("Area info     ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "  ("
                                    + pic.getXOffset() + ", " + pic.getYOffset() + ") - (" + (pic.getXOffset() + pic.getImageWidth()) + ", "
                                    + (pic.getYOffset() + pic.getImageHeight()) + ")\n");
                            index += 6;
                            break;
                        case 0x86: // even/odd offsets
                            pic.setImageBufferOffsetEven(buffer.getDWord(index) + masterIndex);
                            pic.setImageBufferOffsetOdd(buffer.getDWord(index+4) + masterIndex);
                            logger.trace("RLE buffers   ofs: " + ToolBox.toHexLeftZeroPadded(index, 8)
                                    + "  (even: " + ToolBox.toHexLeftZeroPadded(pic.getImageBufferOffsetEven(), 8)
                                    + ", odd: " + ToolBox.toHexLeftZeroPadded(pic.getImageBufferOffsetOdd(), 8) + "\n");
                            index += 8;
                            break;
                        case 0xff:
                            if (stopCommand) {
                                logger.warn("DCSQ stop missing.\n");
                                for (++index; index < bufsize; index++)
                                    if (buffer.getByte(index++) != 0xff) {
                                        index--;
                                        break;
                                    }
                                stopDisplay = true;
                            } else {
                                index = nextIndex;
                                // add to display time
                                int d = buffer.getWord(index);
                                dcsq = d;
                                nextIndex = buffer.getDWord(index+2) + masterIndex;
                                stopCommand = (index == nextIndex);
                                logger.trace("DCSQ          ofs: " + ToolBox.toHexLeftZeroPadded(index, 8) + "  (" + (d * 1024 / 90)
                                        + "ms),    next DCSQ at ofs: " + ToolBox.toHexLeftZeroPadded(nextIndex, 8) + "\n");
                                index += 6;
                            }
                            break;
                        default:
                            throw new CoreException("Unexpected command " + cmd + " at index " + ToolBox.toHexLeftZeroPadded(index, 8));
                    }
                }
                index = masterIndex + packetSize;
                subPictures.add(pic);
            }
        } catch (CoreException ex) {
            if (subPictures.size() == 0) {
                throw ex;
            }
            logger.error(ex.getMessage() + "\n");
            logger.trace("Probably not all caption imported due to error.\n");
        } catch (FileBufferException ex) {
            if (subPictures.size() == 0) {
                throw new CoreException (ex.getMessage());
            }
            logger.error(ex.getMessage() + "\n");
            logger.trace("Probably not all caption imported due to error.\n");
        }
    }

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

    /**
     * decode one line from the RLE buffer
     * @param trg target buffer for uncompressed data
     * @param trgOfs offset in target buffer
     * @param width image width of encoded caption
     * @param maxPixels maximum number of pixels in caption
     * @param src source buffer
     */
    private static void decodeLine(byte[] trg, int trgOfs, int width, int maxPixels, BitStream src) {
        int x=0;
        int pixelsLeft;
        int sumPixels = 0;
        boolean lf = false;

        while (src.bitsLeft() > 0 && sumPixels<maxPixels) {
            int rleType = src.readBits(1);
            int colorType = src.readBits(1);
            int color;
            int numPixels;

            if (colorType == 1) {
                color = src.readBits(8);
            } else {
                color = src.readBits(2); // Colors between 0 and 3 are stored in two bits
            }

            if (rleType == 1) {
                int rleSize = src.readBits(1);
                if (rleSize == 1) {
                    numPixels = src.readBits(7) + 9;
                    if (numPixels == 9) {
                        numPixels = width - x;
                    }
                } else {
                    numPixels = src.readBits(3) + 2;
                }
            } else
                numPixels = 1;

            if (x+numPixels == width) {
                src.syncToByte();
                lf = true;
            }
            sumPixels += numPixels;

            // write pixels to target
            if (x+numPixels > width) {
                pixelsLeft = x + numPixels - width;
                numPixels = width - x;
                lf = true;
            } else {
                pixelsLeft = 0;
            }

            for (int i=0; i<numPixels; i++) {
                trg[trgOfs+x+i] = (byte)color;
            }

            if (lf) {
                trgOfs += x + numPixels + width; // skip odd/even line
                x = pixelsLeft;
                lf = false;
            } else {
                x += numPixels;
            }

            // copy remaining pixels to new line
            for (int i=0; i < pixelsLeft; i++) {
                trg[trgOfs+i] = (byte)color;
            }
        }
    }

    /**
     * decode caption from the input stream
     * @param pic SubPicture object containing info about the caption
     * @param transIdx index of the transparent color
     * @return bitmap of the decoded caption
     * @throws CoreException
     */
    private Bitmap decodeImage(SubPictureHD pic, int transIdx) throws CoreException {
        int w = pic.getImageWidth();
        int h = pic.getImageHeight();
        int warnings = 0;

        if (w > pic.getWidth() || h > pic.getHeight()) {
            throw new CoreException("Subpicture too large: " + w + "x" + h + " at offset " + ToolBox.toHexLeftZeroPadded(pic.getImageBufferOffsetEven(), 8));
        }

        Bitmap bm = new Bitmap(w, h, (byte)transIdx);

        int sizeEven = pic.getImageBufferOffsetOdd() - pic.getImageBufferOffsetEven();
        int sizeOdd = pic.getImageBufferSize() + pic.getImageBufferOffsetEven() - pic.getImageBufferOffsetOdd();

        if (sizeEven <= 0 || sizeOdd <= 0) {
            throw new CoreException("Corrupt buffer offset information");
        }

        byte evenBuf[] = new byte[sizeEven];
        byte oddBuf[]  = new byte[sizeOdd];

        try {
            // copy buffers
            try {
                for (int i=0; i < evenBuf.length; i++) {
                    evenBuf[i] = (byte)buffer.getByte(pic.getImageBufferOffsetEven() + i);
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                warnings++;
            }
            try {
                for (int i=0; i < oddBuf.length; i++) {
                    oddBuf[i= (byte)buffer.getByte(pic.getImageBufferOffsetOdd() +i);
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                warnings++;
            }
            // decode even lines
            try {
                BitStream even = new BitStream(evenBuf);
                decodeLine(bm.getInternalBuffer(), 0, w, w*(h/2+(h&1)), even);
            } catch (ArrayIndexOutOfBoundsException ex) {
                warnings++;
            }
            // decode odd lines
            try {
                BitStream odd  = new BitStream(oddBuf);
                decodeLine(bm.getInternalBuffer(), w, w, (h/2)*w, odd);
            } catch (ArrayIndexOutOfBoundsException ex) {
                warnings++;
            }

            if (warnings > 0) {
                logger.warn("problems during RLE decoding of picture at offset " + ToolBox.toHexLeftZeroPadded(pic.getImageBufferOffsetEven(), 8) + "\n");
            }

            return bm;
        } catch (FileBufferException ex) {
            throw new CoreException (ex.getMessage());
        }
    }

    /**
     * decode palette from the input stream
     * @param pic SubPicture object containing info about the caption
     * @return decoded palette
     * @throws CoreException
     */
    private Palette decodePalette(final SubPictureHD pic) throws CoreException {
        int ofs = pic.getPaletteOffset();
        int alphaOfs = pic.getAlphaOffset();

        Palette palette = new Palette(256);
        try {
            for (int i=0; i < palette.getSize(); i++) {
                // each palette entry consists of 3 bytes
                int y = buffer.getByte(ofs++);
                int cr,cb;
                if (configuration.isSwapCrCb()) {
                    cb = buffer.getByte(ofs++);
                    cr = buffer.getByte(ofs++);
                } else {
                    cr = buffer.getByte(ofs++);
                    cb = buffer.getByte(ofs++);
                }
                // each alpha entry consists of 1 byte
                int alpha = 0xff - buffer.getByte(alphaOfs++);
                if (alpha < configuration.getAlphaCrop()) { // to not mess with scaling algorithms, make transparent color black
                    palette.setRGB(i, 0, 0, 0);
                } else {
                    palette.setYCbCr(i, y, cb, cr);
                }
                palette.setAlpha(i, alpha);
            }
            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(SubPictureHD pic) throws CoreException {
        palette = decodePalette(pic);
        bitmap  = decodeImage(pic, palette.getIndexOfMostTransparentPaletteEntry());
        primaryColorIndex = bitmap.getPrimaryColorIndex(palette.getAlpha(), configuration.getAlphaThreshold(), palette.getY());
    }

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

    public Palette getPalette() {
        return palette;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

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

    public BufferedImage getImage(Bitmap bm) {
        return bm.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 0;
    }

    public boolean isForced(int index) {
        return false;
    }

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

Related Classes of bdsup2sub.supstream.hd.SupHD

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.