Package bdsup2sub.supstream.dvd

Source Code of bdsup2sub.supstream.dvd.SubDvdWriter

/*
* 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.Palette;
import bdsup2sub.core.Configuration;
import bdsup2sub.core.Constants;
import bdsup2sub.core.CoreException;
import bdsup2sub.supstream.SubPicture;
import bdsup2sub.utils.ToolBox;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import static bdsup2sub.core.Constants.LANGUAGES;
import static bdsup2sub.utils.TimeUtils.ptsToTimeStrIdx;

public final class SubDvdWriter {

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

    private static final byte[] PACK_HEADER = {
            0x00, 0x00, 0x01, (byte)0xba,              // 0:  0x000001ba - packet ID
            0x44, 0x02, (byte)0xc4, (byte)0x82, 0x04, (byte)0xa9// 4:  system clock reference
            0x01 , (byte)0x89, (byte)0xc3,              // 10: multiplexer rate
            (byte)0xf8,                        // 13: stuffing info
    };

    private static final byte[] HEADER_FIRST = {            // header only in first packet
            0x00, 0x00, 0x01, (byte)0xbd,              // 0: 0x000001bd - sub ID
            0x00, 0x00,                        // 4: packet length
            (byte)0x81, (byte)0x80,                 // 6:  packet type
            0x05,                          // 8:  PTS length
            0x00, 0x0, 0x00, 0x00, 0x00,              // 9:  PTS
            0x20,                          // 14: stream ID
            0x00, 0x00,                        // 15: Subpicture size in bytes
            0x00, 0x00,                        // 17: offset to control header
    };

    private static final byte[] HEADER_NEXT = {            // header in following packets
            0x00, 0x00, 0x01, (byte)0xbd,              // 0: 0x000001bd - sub ID
            0x00, 0x00,                        // 4: packet length
            (byte)0x81, (byte)0x00,                 // 6: packet type
            0x00,                          // 8: PTS length = 0
            0x20                          // 9: Stream ID
    };

    private static final byte[] CONTROL_HEADER = {
            0x00,                          //  dummy byte (for shifting when forced)
            0x00, 0x00,                        //  0: offset to end sequence
            0x01,                          //  2: CMD 1: start displaying
            0x03, 0x32, 0x10,                    //  3: CMD 3: Palette Info
            0x04, (byte)0xff, (byte)0xff,              //  6: CMD 4: Alpha Info
            0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,        //  9: CMD 5: sub position
            0x06, 0x00, 0x00, 0x00, 0x00,              // 16: CMD 6: rle offsets
            (byte)0xff,                        // 21: End of control header
            0x00, 0x00,                        // 22: display duration in 90kHz/1024
            0x00, 0x00,                        // 24: offset to end sequence (again)
            0x02, (byte)0xff,                    // 26: CMD 2: stop displaying
    };

    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;
            CONTROL_HEADER[2] = 0x01; // display
            CONTROL_HEADER[3] = 0x00; // forced
            controlHeaderLen = CONTROL_HEADER.length;
        } else {
            forcedOfs = 1;
            CONTROL_HEADER[2] = 0x00; // part of offset
            CONTROL_HEADER[3] = 0x01; // display
            controlHeaderLen = 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?
        HEADER_FIRST[9] = (byte) (((ptm >> 29) & 0x0E) | 0x21);
        HEADER_FIRST[10] = (byte) (ptm >> 22);
        HEADER_FIRST[11] = (byte) ((ptm >> 14) | 1);
        HEADER_FIRST[12] = (byte) (ptm >> 7);
        HEADER_FIRST[13] = (byte) (ptm * 2 + 1);

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

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

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

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

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

        /* display duration in frames */
        tmp = (int) ((pic.getEndTime() - pic.getStartTime()) / 1024); // 11.378ms resolution????
        CONTROL_HEADER[1 + 22] = (byte) ((tmp >> 8) & 0xff);
        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;
        CONTROL_HEADER[forcedOfs + 0] = (byte) ((tmp >> 8) & 0xff);
        CONTROL_HEADER[forcedOfs + 1] = (byte) (tmp & 0xff);
        CONTROL_HEADER[1 + 24] = (byte) ((tmp >> 8) & 0xff);
        CONTROL_HEADER[1 + 25] = (byte) (tmp & 0xff);

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

        /* offset to control buffer - 2 is the size of the offset */
        tmp = even.length + odd.length + 2;
        HEADER_FIRST[17] = (byte) (tmp >> 8);
        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 = PACK_HEADER.length + 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 - PACK_HEADER.length - HEADER_FIRST.length); // size - 0x7df
            while (remainingRLEsize > (0x800 - PACK_HEADER.length - HEADER_NEXT.length - controlHeaderLen)) {
                remainingRLEsize -= (0x800 - PACK_HEADER.length - HEADER_NEXT.length);
                bufSize += PACK_HEADER.length + HEADER_NEXT.length;
                numAdditionalPackets++;
            }
            // packet length of the 1st packet should be the maximum size
            tmp = 0x800 - PACK_HEADER.length - 6;
        } else {
            tmp = (bufSize - 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 : PACK_HEADER) {
            buf[ofs++] = packHeader;
        }

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

        // set pts length
        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++] = HEADER_FIRST[i];
        }
        for (int i = 0; i < stuffingBytes; i++) {
            buf[ofs++] = (byte) 0xff;
        }
        for (int i = 14; i < HEADER_FIRST.length; i++) {
            buf[ofs++] = HEADER_FIRST[i];
        }

        // write (first part of) RLE buffer
        tmp = sizeRLE;
        if (numAdditionalPackets > 0) {
            tmp = (0x800 - PACK_HEADER.length - stuffingBytes - 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] = 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 = HEADER_NEXT.length + (controlHeaderLen - controlHeaderWritten) + (sizeRLE - ofsRLE) - 6;
            } else {
                tmp = 0x800 - PACK_HEADER.length - 6;
                rleSizeLeft = (0x800 - PACK_HEADER.length - 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
            PACK_HEADER[13] = (byte) (0xf8);
            for (byte packHeader : PACK_HEADER) {
                buf[ofs++] = packHeader;
            }

            // set packet length
            HEADER_NEXT[4] = (byte) (tmp >> 8);
            HEADER_NEXT[5] = (byte) tmp;
            for (byte b : 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] = CONTROL_HEADER[forcedOfs + (controlHeaderWritten++)];
                }
            }
        }

        // write (rest of) control header
        for (int i = controlHeaderWritten; i < controlHeaderLen; i++) {
            buf[ofs++] = 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) {
            }
        }
    }
}
TOP

Related Classes of bdsup2sub.supstream.dvd.SubDvdWriter

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.