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.containers.mp4.boxes.Box.not;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.List;

import org.jcodec.containers.mp4.Chunk;
import org.jcodec.containers.mp4.ChunkReader;
import org.jcodec.containers.mp4.MP4Util;
import org.jcodec.containers.mp4.boxes.AliasBox;
import org.jcodec.containers.mp4.boxes.AliasBox.ExtraField;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
import org.jcodec.containers.mp4.boxes.DataInfoBox;
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.MediaInfoBox;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.UrlBox;
import org.jcodec.containers.mp4.io.FileInput;
import org.jcodec.containers.mp4.io.RandomAccessInput;
import org.jcodec.containers.mp4.io.WindowInput;

/**
 * This class is part of JCodec ( www.jcodec.org )
 * This software is distributed under FreeBSD License
 * 
 * Self contained movie creator
 * 
 * @author Stanislav Vitvitskiy
 * 
 */
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();
        FileInput input = null;
        try {
            input = new FileInput(new File(args[0]));
            MovieBox movie = MP4Util.parseMovie(input);
            new Flattern().flattern(movie, outFile);
        } finally {
            if (input != null)
                input.close();
        }
    }

    public void flattern(MovieBox movie, RandomAccessFile out) throws IOException {
        if (!movie.isPureRefMovie(movie))
            throw new IllegalArgumentException("movie should be reference");
        FileTypeBox ftyp = new FileTypeBox("qt  ", 0x20050300, Arrays.asList(new String[] { "qt  " }));
        ftyp.write(out);
        long movieOff = out.getFilePointer();
        movie.write(out);

        new Header("free", 8).write(out);
        
        long mdatOff = out.getFilePointer();
        new Header("mdat", 0x100000001L).write(out);

        TrakBox[] tracks = movie.getTracks();
        ChunkReader[] readers = new ChunkReader[tracks.length];
        ChunkWriter[] writers = new ChunkWriter[tracks.length];
        Chunk[] head = new Chunk[tracks.length];
        long[] off = new long[tracks.length];
        for (int i = 0; i < tracks.length; i++) {
            readers[i] = new ChunkReader(tracks[i]);
            writers[i] = new ChunkWriter(tracks[i], getInputs(tracks[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();
        }
        long mdatSize = out.getFilePointer() - mdatOff;

        for (int i = 0; i < tracks.length; i++) {
            writers[i].apply();
        }
        out.seek(movieOff);
        movie.write(out);

        long extra = mdatOff - out.getFilePointer();
        if(extra < 0)
            throw new RuntimeException("Not enough space to write the header");
        new Header("free", extra).write(out);
        
        out.seek(mdatOff + 8);
        out.writeLong(mdatSize);
    }

    private class ChunkWriter {
        private long[] offsets;
        private SampleEntry[] entries;
        private RandomAccessInput[] inputs;
        private int curChunk;
        private RandomAccessFile out;
        byte[] buf = new byte[8092];
        private TrakBox trak;

        public ChunkWriter(TrakBox trak, RandomAccessInput[] inputs, RandomAccessFile out) {
            entries = NodeBox.findAll(trak, SampleEntry.class, "mdia", "minf", "stbl", "stsd", null);
            ChunkOffsetsBox stco = NodeBox.findFirst(trak, ChunkOffsetsBox.class, "mdia", "minf", "stbl", "stco");
            ChunkOffsets64Box co64 = NodeBox.findFirst(trak, ChunkOffsets64Box.class, "mdia", "minf", "stbl", "co64");
            int size;
            if (stco != null)
                size = stco.getChunkOffsets().length;
            else
                size = co64.getChunkOffsets().length;
            this.inputs = inputs;

            offsets = new long[size];
            this.out = out;
            this.trak = trak;
        }

        public void apply() {
            NodeBox stbl = NodeBox.findFirst(trak, NodeBox.class, "mdia", "minf", "stbl");
            stbl.filter(not("stco"));
            stbl.filter(not("co64"));

            boolean big = false;
            for (long offset : offsets) {
                if (offset > 0xffffffffL) {
                    big = true;
                    break;
                }
            }
            stbl.add(big ? new ChunkOffsets64Box(offsets) : new ChunkOffsetsBox(offsets));
            cleanDrefs(trak);
        }

        private void cleanDrefs(TrakBox trak) {
            MediaInfoBox minf = trak.getMdia().getMinf();
            DataInfoBox dinf = trak.getMdia().getMinf().getDinf();
            if (dinf == null) {
                dinf = new DataInfoBox();
                minf.add(dinf);
            }

            DataRefBox dref = dinf.getDref();
            if (dref == null) {
                dref = new DataRefBox();
                dinf.add(dref);
            }

            dref.getBoxes().clear();
            AliasBox alis = new AliasBox();
            alis.setFlags(1); // self ref
            dref.add(alis);

            for (SampleEntry entry : NodeBox.findAll(trak, SampleEntry.class, "mdia", "minf", "stbl", "stsd", null)) {
                entry.setDrefInd((short) 1);
            }
        }

        private RandomAccessInput getInput(Chunk chunk) {
            SampleEntry se = entries[chunk.getEntry() - 1];
            return inputs[se.getDrefInd() - 1];
        }

        public void write(Chunk chunk) throws IOException {
            RandomAccessInput input = getInput(chunk);
            input.seek(chunk.getOffset());
            long filePointer = out.getFilePointer();
            WindowInput inp = new WindowInput(input, chunk.getSize());
            int read;
            while ((read = inp.read(buf)) != -1)
                out.write(buf, 0, read);
            offsets[curChunk++] = filePointer;
        }
    }

    private RandomAccessInput[] getInputs(TrakBox trak) throws IOException {
        DataRefBox drefs = NodeBox.findFirst(trak, DataRefBox.class, "mdia", "minf", "dinf", "dref");
        if (drefs == null) {
            throw new RuntimeException("No data references");
        }
        List entries = drefs.getBoxes();
        RandomAccessInput[] inputs = new RandomAccessInput[entries.size()];
        int i = 0;
        for (Box box : entries) {
            if (box instanceof UrlBox) {
                String url = ((UrlBox) box).getUrl();
                if (!url.startsWith("file://"))
                    throw new RuntimeException("Only file:// urls are supported in data reference");
                inputs[i] = new FileInput(new File(url.substring(7)));
            } else if (box instanceof AliasBox) {
                AliasBox alias = ((AliasBox) box);
                inputs[i] = new FileInput(resolveAlias(alias));
            } else {
                throw new RuntimeException(box.getHeader().getFourcc() + " dataref type is not supported");
            }
            ++i;
        }
        return inputs;
    }

    private File resolveAlias(AliasBox alias) {
        ExtraField extraField = alias.getExtra(AliasBox.UNIXAbsolutePath);
        if (extraField == null) {
            throw new RuntimeException("Could not resolve alias");
        }
        return new File("/" + extraField.toString());
    }

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy