Package bdsup2sub.supstream.dvd

Source Code of bdsup2sub.supstream.dvd.SubDvd

/*
* 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.*;
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.io.*;
import java.util.ArrayList;
import java.util.List;

import static bdsup2sub.core.Constants.*;
import static bdsup2sub.utils.ByteUtils.getByte;
import static bdsup2sub.utils.ByteUtils.getWord;
import static bdsup2sub.utils.TimeUtils.timeStrToPTS;

/**
* Handling of SUB/IDX (VobSub) streams.
*/
public class SubDvd implements DvdSubtitleStream {

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

    /** ArrayList of captions contained in the current file */
    private final List<SubPictureDVD> subPictures = new ArrayList<SubPictureDVD>();
    /** color palette read from idx file  */
    private Palette srcPalette = new Palette(DEFAULT_DVD_PALETTE);
    /** color palette created for last decoded caption  */
    private Palette palette;
    /** bitmap of the last decoded caption */
    private Bitmap bitmap;
    /** screen width of imported VobSub */
    private int screenWidth = 720;
    /** screen height of imported VobSub  */
    private int screenHeight = 576;
    /** global x offset  */
    private int globalXOffset;
    /** global y offset */
    private int globalYOffset;
    /** global delay  */
    private int globalDelay;
    /** index of language read from IDX */
    private int languageIndex;
    /** stream ID */
    private int streamID;
    /** FileBuffer for reading SUB */
    private final FileBuffer buffer;
    /** index of dominant color for the current caption */
    private int primaryColorIndex;
    /** number of forced captions in the current file  */
    private int forcedFrameCount;
    /** store last alpha values for invisible workaround */
    private static int[] lastAlpha = {0, 0xf, 0xf, 0xf};


    public SubDvd(String subFile, String idxFile) throws CoreException {
        readIdx(idxFile);
        Core.setProgressMax(subPictures.size());
        try {
            buffer = new FileBuffer(subFile);
        } catch (FileBufferException e) {
            throw new CoreException(e.getMessage());
        }
        for (int i=0; i < subPictures.size(); i++) {
            Core.setProgress(i);
            logger.info("# " + (i + 1) + "\n");
            logger.trace("Offset: " + ToolBox.toHexLeftZeroPadded(subPictures.get(i).getOffset(), 8) + "\n");
            long nextOfs;
            if (i < subPictures.size() - 1) {
                nextOfs = subPictures.get(i+1).getOffset();
            } else {
                nextOfs = buffer.getSize();
            }
            readSubFrame(subPictures.get(i), nextOfs, buffer);
        }
        logger.info("\nDetected " + forcedFrameCount + " forced captions.\n");
    }

    private void readIdx(String idxFile) throws CoreException {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(idxFile));
            String s;
            int v;
            int langIdx = 0;
            boolean ignore = false;
            while ((s = in.readLine()) != null) {
                s = s.trim();
                if (s.length() < 1 || s.charAt(0) == '#') {
                    continue;
                }
                int pos = s.indexOf(':');
                if (pos == -1 || s.length()-pos <= 1) {
                    logger.error("Illegal key: " + s + "\n");
                    continue;
                }
                String key = s.substring(0, pos).trim();
                String val = s.substring(pos+1).trim();

                // size (e.g. "size: 720x576")
                if (key.equalsIgnoreCase("size")) {
                    pos = val.indexOf('x');
                    if (pos == -1 || val.length()-pos <= 1) {
                        throw new CoreException("Illegal size: " + val);
                    }
                    v = ToolBox.getInt(val.substring(0,pos));
                    if (v < 2) {
                        throw new CoreException("Illegal screen width: " + v);
                    }
                    screenWidth = v;
                    v = ToolBox.getInt(val.substring(pos+1));
                    if (v < 2) {
                        throw new CoreException("Illegal screen height: " + v);
                    }
                    screenHeight = v;
                    continue;
                }

                // origin (e.g. "org: 0, 0")
                if (key.equalsIgnoreCase("org")) {
                    pos = val.indexOf(',');
                    if (pos == -1 || val.length()-pos <= 1) {
                        throw new CoreException("Illegal origin: " + val);
                    }
                    v = ToolBox.getInt(val.substring(0,pos));
                    if (v < 0) {
                        throw new CoreException("Illegal x origin: " + v);
                    }
                    globalXOffset = v;
                    v = ToolBox.getInt(val.substring(pos+1));
                    if (v < 0) {
                        throw new CoreException("Illegal y origin: " + v);
                    }
                    globalYOffset = v;
                    continue;
                }

                // scale (e.g. "scale: 100%, 100%")
                if (key.equalsIgnoreCase("scale")) {
                    // ignored for the moment
                    continue;
                }

                // alpha (e.g. "alpha: 100%")
                if (key.equalsIgnoreCase("alpha")) {
                    // ignored for the moment
                    continue;
                }

                // smoothing (e.g. "smooth: OFF")
                if (key.equalsIgnoreCase("smooth")) {
                    // ignored for the moment
                    continue;
                }

                // fading (e.g. "fadein/out: 0, 0");
                if (key.equalsIgnoreCase("fadein/out")) {
                    // ignored for the moment
                    continue;
                }

                // alignment (e.g. "align: OFF at LEFT TOP")
                if (key.equalsIgnoreCase("align")) {
                    // ignored for the moment
                    continue;
                }

                // time offset (e.g. "time offset: 0")
                if (key.equalsIgnoreCase("time offset")) {
                    v = ToolBox.getInt(val);
                    if (v < 0) {
                        v = (int)timeStrToPTS(val);
                    }
                    if (v < 0) {
                        throw new CoreException("Illegal time offset: " + v);
                    }
                    globalDelay = v * 90; // ms -> 90kHz
                    continue;
                }

                // forced subs (e.g. "forced subs: OFF")
                if (key.equalsIgnoreCase("align")) {
                    // ignored for the moment
                    continue;
                }

                // palette
                if (key.equalsIgnoreCase("palette")) {
                    String vals[] = val.split(",");
                    if (vals == null || vals.length < 1 || vals.length > 16) {
                        throw new CoreException("Illegal palette definition: " + val);
                    }
                    for (int i=0; i<vals.length; i++) {
                        int color = -1;
                        try {
                            color = Integer.parseInt(vals[i].trim(), 16);
                        } catch (NumberFormatException ex) {
                        }
                        if (color == -1) {
                            throw new CoreException("Illegal palette entry: " + vals[i]);
                        }
                        srcPalette.setARGB(i, color);
                    }
                    continue;
                }

                // custom colors (e.g. "custom colors: OFF, tridx: 1000, colors: 000000, 444444, 888888, cccccc")
                if (key.equalsIgnoreCase("custom colors")) {
                    // ignored for the moment
                    continue;
                }

                // language index (e.g. "langidx: 0")
                if (key.equalsIgnoreCase("langidx")) {
                    v = ToolBox.getInt(val);
                    if (v < 0) {
                        throw new CoreException("Illegal language idx: " + v);
                    }
                    langIdx = v; // ms -> 90kHz
                    // ignored for the moment
                    continue;
                }

                // language id (e.g. "id: de, index: 0")
                if (key.equalsIgnoreCase("id")) {
                    String id;
                    pos = val.indexOf(',');
                    if (pos > 0 ) {
                        id = val.substring(0, pos).trim();
                    } else {
                        id = val;
                    }
                    if (id.length() != 2) {
                        logger.warn("Illegal language id: " + id + "\n");
                        continue;
                    }
                    boolean found = false;
                    for (int i=0; i < LANGUAGES.length; i++) {
                        if (id.equalsIgnoreCase(LANGUAGES[i][1])) {
                            languageIndex = i;
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        logger.warn("Illegal language id: " + id + "\n");
                    }

                    pos = val.indexOf(':');
                    if (pos == -1 || s.length() - pos <= 1) {
                        logger.error("Missing index key: " + val + "\n");
                        continue;
                    }
                    key = val.substring(0, pos).trim();
                    val = val.substring(pos+1).trim();
                    if (key.equalsIgnoreCase("index"))  {
                        logger.error("Missing index key: " + s + "\n");
                        continue;
                    }
                    v = ToolBox.getInt(val);
                    if (v < 0) {
                        throw new CoreException("Illegal language index: " + v);
                    }

                    if (v != langIdx) {
                        ignore = true;
                        logger.warn("Language id " + id + "(index:" + v + ") inactive -> ignored\n");
                    } else {
                        streamID = v;
                        ignore = false;
                    }
                    continue;
                }

                if (!ignore) {
                    // timestamp: 00:00:14:160, filepos: 000000000
                    if (key.equalsIgnoreCase("timestamp")) {
                        String vs;
                        pos = val.indexOf(',');
                        if (pos == -1 || val.length()-pos <= 1) {
                            throw new CoreException("Illegal timestamp entry: " + val);
                        }
                        vs = val.substring(0,pos);
                        long t = timeStrToPTS(vs);
                        if (t < 0) {
                            throw new CoreException("Illegal timestamp: " + vs);
                        }
                        vs = val.substring(pos+1).toLowerCase();
                        pos = vs.indexOf("filepos:");
                        if (pos == -1 || vs.length()-pos <= 1) {
                            throw new CoreException("Missing filepos: " + val);
                        }
                        long l = Long.parseLong(vs.substring(pos+8).trim(), 16);
                        if (l == -1) {
                            throw new CoreException("Illegal filepos: " + vs.substring(pos+8));
                        }
                        SubPictureDVD pic = new SubPictureDVD();
                        pic.setOffset(l);
                        pic.setWidth(screenWidth);
                        pic.setHeight(screenHeight);
                        pic.setStartTime(t + globalDelay);
                        subPictures.add(pic);
                    }
                }
            }
        } catch (IOException ex) {
            throw new CoreException(ex.getMessage());
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
            }
        }
    }

    /**
     * Read one frame from SUB file
     * @param pic SubPicture object for this frame
     * @param endOfs end offset
     * @param buffer File Buffer to read from
     * @throws CoreException
     */
    private void readSubFrame(SubPictureDVD pic, long endOfs, FileBuffer buffer) throws CoreException  {
        long ofs = pic.getOffset();
        long ctrlOfs = -1;
        long nextOfs;
        int  ctrlOfsRel = 0;
        int rleSize = 0;
        int rleBufferFound = 0;
        int ctrlSize = -1;
        int ctrlHeaderCopied = 0;
        byte ctrlHeader[] = null;
        ImageObjectFragment rleFrag;
        int length;
        int packHeaderSize;
        boolean firstPackFound = false;

        try {
            do {
                // 4 bytes:  packet identifier 0x000001ba
                long startOfs = ofs;
                if (buffer.getDWord(ofs) != 0x000001ba) {
                    throw new CoreException("Missing packet identifier at ofs " + ToolBox.toHexLeftZeroPadded(ofs,8));
                }
                // 6 bytes:  system clock reference
                // 3 bytes:  multiplexer rate
                // 1 byte:   stuffing info
                int stuffOfs = buffer.getByte(ofs+=13) & 7;
                // 4 bytes:  sub packet ID 0x000001bd
                if (buffer.getDWord(ofs += (1+stuffOfs)) != 0x000001bd) {
                    throw new CoreException("Missing packet identifier at ofs " + ToolBox.toHexLeftZeroPadded(ofs,8));
                }
                // 2 bytes:  packet length (number of bytes after this entry)
                length = buffer.getWord(ofs+=4);
                nextOfs = ofs+2+length;
                // 2 bytes:  packet type
                ofs += 2;
                packHeaderSize = (int)(ofs-startOfs);
                boolean firstPack = ((buffer.getByte(++ofs) & 0x80) == 0x80);
                // 1 byte    pts length
                int ptsLength = buffer.getByte(ofs+=1);
                ofs += 1 + ptsLength; // skip PTS and stream ID
                int packetStreamID = buffer.getByte(ofs++) - 0x20;
                if (packetStreamID != streamID) {
                    // packet doesn't belong to stream -> skip
                    if (nextOfs % 0x800 != 0) {
                        ofs = (nextOfs/0x800 + 1)*0x800;
                        logger.warn("Offset to next fragment is invalid. Fixed to:" + ToolBox.toHexLeftZeroPadded(ofs, 8) + "\n");
                    } else {
                        ofs = nextOfs;
                    }
                    ctrlOfs += 0x800;
                    continue;
                }
                int headerSize = (int)(ofs-startOfs); // only valid for additional packets
                if (firstPack && ptsLength >= 5) {
                    int size = buffer.getWord(ofs);
                    ofs += 2;
                    ctrlOfsRel = buffer.getWord(ofs);
                    rleSize = ctrlOfsRel-2;             // calculate size of RLE buffer
                    ctrlSize = size-ctrlOfsRel-2;       // calculate size of control header
                    if (ctrlSize < 0) {
                        throw new CoreException("Invalid control buffer size");
                    }
                    ctrlHeader = new byte[ctrlSize];
                    ctrlOfs = ctrlOfsRel + ofs; // might have to be corrected for multiple packets
                    ofs += 2;
                    headerSize = (int)(ofs-startOfs);
                    pic.setRleFragments(new ArrayList<ImageObjectFragment>());
                    firstPackFound = true;
                } else {
                    if (firstPackFound) {
                        ctrlOfs += headerSize; // fix absolute offset by adding header bytes
                    } else {
                        logger.warn("Invalid fragment skipped at ofs " + ToolBox.toHexLeftZeroPadded(startOfs, 8) + "\n");
                    }
                }

                // check if control header is (partly) in this packet
                int diff = (int)(nextOfs - ctrlOfs - ctrlHeaderCopied);
                if (diff<0) {
                    diff = 0;
                }
                int copied = ctrlHeaderCopied;
                try {
                    for (int i=0; (i < diff) && (ctrlHeaderCopied<ctrlSize); i++) {
                        ctrlHeader[ctrlHeaderCopied] = (byte)buffer.getByte(ctrlOfs + i + copied);
                        ctrlHeaderCopied++;
                    }
                } catch (ArrayIndexOutOfBoundsException ex) {
                    throw new CoreException("Inconsistent control buffer access (" + ex.getMessage() + ")");
                }
                rleFrag = new ImageObjectFragment(ofs, length - headerSize - diff + packHeaderSize);
                pic.getRleFragments().add(rleFrag);

                rleBufferFound += rleFrag.getImagePacketSize();

                if (ctrlHeaderCopied != ctrlSize && (nextOfs % 0x800 != 0)) {
                    ofs = (nextOfs/0x800 + 1) * 0x800;
                    logger.warn("Offset to next fragment is invalid. Fixed to:" + ToolBox.toHexLeftZeroPadded(ofs, 8) + "\n");
                    rleBufferFound += ofs-nextOfs;
                } else {
                    ofs = nextOfs;
                }
            } while (ofs < endOfs && ctrlHeaderCopied < ctrlSize);

            if (ctrlHeaderCopied != ctrlSize) {
                logger.warn("Control buffer size inconsistent.\n");
                // fill rest of buffer with break command to avoid wrong detection of forced caption (0x00)
                for (int i=ctrlHeaderCopied; i<ctrlSize; i++) {
                    ctrlHeader[i] = (byte)0xff;
                }
            }

            if (rleBufferFound != rleSize) {
                logger.warn("RLE buffer size inconsistent.\n");
            }

            pic.setRleSize(rleBufferFound);
        } catch (FileBufferException ex) {
            throw new CoreException(ex.getMessage());
        }

        pic.setPal(new int[4]);
        pic.setAlpha(new int[4]);
        int alphaSum = 0;
        int alphaUpdate[] = new int[4];
        int alphaUpdateSum;
        int delay = -1;
        boolean ColAlphaUpdate = false;

        logger.trace("SP_DCSQT at ofs: " + ToolBox.toHexLeftZeroPadded(ctrlOfs, 8) + "\n");

        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);
                        forcedFrameCount++;
                        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(globalXOffset +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(globalYOffset +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
                        ColAlphaUpdate = 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 " + ToolBox.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());
            }

            if (ColAlphaUpdate) {
                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();
            pic.storeOriginal();
        } catch (IndexOutOfBoundsException ex) {
            throw new CoreException("Index "+ex.getMessage() + " out of bounds in control header.");
        }
    }

    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 picthrows CoreException {
        palette = SupDvdUtil.decodePalette(pic, srcPalette);
        bitmap  = SupDvdUtil.decodeImage(pic, buffer, 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 w = bounds.xMax - bounds.xMin + 1;
            int 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
            pic.setImageWidth(w);
            pic.setImageHeight(h);
            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 forcedFrameCount;
    }

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

    public void close() {
        if (buffer!=null)
            buffer.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 languageIndex;
    }

    public Palette getSrcPalette() {
        return srcPalette;
    }

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

Related Classes of bdsup2sub.supstream.dvd.SubDvd

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.