All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jcodec.movtool.Flattern Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
package org.jcodec.movtool;

import static org.jcodec.common.NIOUtils.readableFileChannel;
import static org.jcodec.common.NIOUtils.writableFileChannel;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.containers.mp4.Chunk;
import org.jcodec.containers.mp4.ChunkReader;
import org.jcodec.containers.mp4.ChunkWriter;
import org.jcodec.containers.mp4.MP4Util;
import org.jcodec.containers.mp4.boxes.AliasBox;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
import org.jcodec.containers.mp4.boxes.DataRefBox;
import org.jcodec.containers.mp4.boxes.FileTypeBox;
import org.jcodec.containers.mp4.boxes.Header;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.UrlBox;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Self contained movie creator
 * 
 * @author The JCodec project
 * 
 */
public class Flattern {

    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println("Syntax: self  ");
            System.exit(-1);
        }
        File outFile = new File(args[1]);
        outFile.delete();
        SeekableByteChannel input = null;
        try {
            input = readableFileChannel(new File(args[0]));
            MovieBox movie = MP4Util.parseMovie(input);
            new Flattern().flattern(movie, outFile);
        } finally {
            if (input != null)
                input.close();
        }
    }

    public List listeners = new ArrayList();

    public interface ProgressListener {
        public void trigger(int progress);
    }

    public void addProgressListener(ProgressListener listener) {
        this.listeners.add(listener);
    }

    public void flattern(MovieBox movie, SeekableByteChannel out) throws IOException {
        if (!movie.isPureRefMovie(movie))
            throw new IllegalArgumentException("movie should be reference");
        ByteBuffer buf = ByteBuffer.allocate(16 * 1024 * 1024);
        FileTypeBox ftyp = new FileTypeBox("qt  ", 0x20050300, Arrays.asList(new String[] { "qt  " }));
        ftyp.write(buf);
        long movieOff = buf.position();
        movie.write(buf);

        int extraSpace = calcSpaceReq(movie);
        new Header("free", 8 + extraSpace).write(buf);
        NIOUtils.skip(buf, extraSpace);

        long mdatOff = buf.position();
        new Header("mdat", 0x100000001L).write(buf);
        buf.flip();
        out.write(buf);

        SeekableByteChannel[][] inputs = getInputs(movie);

        TrakBox[] tracks = movie.getTracks();
        ChunkReader[] readers = new ChunkReader[tracks.length];
        ChunkWriter[] writers = new ChunkWriter[tracks.length];
        Chunk[] head = new Chunk[tracks.length];
        int totalChunks = 0, writtenChunks = 0, lastProgress = 0;
        long[] off = new long[tracks.length];
        for (int i = 0; i < tracks.length; i++) {
            readers[i] = new ChunkReader(tracks[i]);
            totalChunks += readers[i].size();

            writers[i] = new ChunkWriter(tracks[i], inputs[i], out);
            head[i] = readers[i].next();
            if (tracks[i].isVideo())
                off[i] = 2 * movie.getTimescale();
        }

        while (true) {
            int min = -1;
            for (int i = 0; i < readers.length; i++) {
                if (head[i] == null)
                    continue;

                if (min == -1)
                    min = i;
                else {
                    long iTv = movie.rescale(head[i].getStartTv(), tracks[i].getTimescale()) + off[i];
                    long minTv = movie.rescale(head[min].getStartTv(), tracks[min].getTimescale()) + off[min];
                    if (iTv < minTv)
                        min = i;
                }
            }
            if (min == -1)
                break;
            writers[min].write(head[min]);
            head[min] = readers[min].next();
            writtenChunks++;

            lastProgress = calcProgress(totalChunks, writtenChunks, lastProgress);
        }
        long mdatSize = out.position() - mdatOff;

        for (int i = 0; i < tracks.length; i++) {
            writers[i].apply();
        }
        out.position(movieOff);
        MP4Util.writeMovie(out, movie);

        long extra = mdatOff - out.position();
        if (extra < 0)
            throw new RuntimeException("Not enough space to write the header");
        out.write((ByteBuffer) ByteBuffer.allocate(8).putInt((int) extra).put(new byte[] { 'f', 'r', 'e', 'e' }).flip());

        out.position(mdatOff + 8);
        out.write(ByteBuffer.allocate(8).putLong(mdatSize));
    }

    private int calcProgress(int totalChunks, int writtenChunks, int lastProgress) {
        int curProgress = 100 * writtenChunks / totalChunks;
        if (lastProgress < curProgress) {
            lastProgress = curProgress;
            for (ProgressListener pl : this.listeners)
                pl.trigger(lastProgress);
        }
        return lastProgress;
    }

    protected SeekableByteChannel[][] getInputs(MovieBox movie) throws IOException {
        TrakBox[] tracks = movie.getTracks();
        SeekableByteChannel[][] result = new SeekableByteChannel[tracks.length][];
        for (int i = 0; i < tracks.length; i++) {
            DataRefBox drefs = NodeBox.findFirst(tracks[i], DataRefBox.class, "mdia", "minf", "dinf", "dref");
            if (drefs == null) {
                throw new RuntimeException("No data references");
            }
            List entries = drefs.getBoxes();
            SeekableByteChannel[] e = new SeekableByteChannel[entries.size()];
            SeekableByteChannel[] inputs = new SeekableByteChannel[entries.size()];
            for (int j = 0; j < e.length; j++) {
                inputs[j] = resolveDataRef(entries.get(j));
            }
            result[i] = inputs;
        }
        return result;
    }

    private int calcSpaceReq(MovieBox movie) {
        int sum = 0;
        for (TrakBox trakBox : movie.getTracks()) {
            ChunkOffsetsBox stco = Box.findFirst(trakBox, ChunkOffsetsBox.class, "mdia", "minf", "stbl", "stco");
            if (stco != null)
                sum += stco.getChunkOffsets().length * 4;
        }
        return sum;
    }

    public SeekableByteChannel resolveDataRef(Box box) throws IOException {
        if (box instanceof UrlBox) {
            String url = ((UrlBox) box).getUrl();
            if (!url.startsWith("file://"))
                throw new RuntimeException("Only file:// urls are supported in data reference");
            return readableFileChannel(new File(url.substring(7)));
        } else if (box instanceof AliasBox) {
            String uxPath = ((AliasBox) box).getUnixPath();
            if (uxPath == null)
                throw new RuntimeException("Could not resolve alias");
            return readableFileChannel(new File(uxPath));
        } else {
            throw new RuntimeException(box.getHeader().getFourcc() + " dataref type is not supported");
        }
    }

    public void flattern(MovieBox movie, File video) throws IOException {
        video.delete();
        SeekableByteChannel out = null;
        try {
            out = writableFileChannel(video);
            flattern(movie, out);
        } finally {
            if (out != null)
                out.close();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy