org.jcodec.movtool.Util.java Source code

Java tutorial

Introduction

Here is the source code for org.jcodec.movtool.Util.java

Source

package org.jcodec.movtool;

import static org.jcodec.containers.mp4.boxes.Box.findFirst;
import static org.jcodec.containers.mp4.boxes.Box.not;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.apache.commons.lang.ArrayUtils;
import org.jcodec.common.model.Rational;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
import org.jcodec.containers.mp4.boxes.DataRefBox;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.MediaHeaderBox;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.SampleDescriptionBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Cut on ref movies
 * 
 * @author The JCodec project
 * 
 */
public class Util {

    public static class Pair<T> {
        private T a;
        private T b;

        public Pair(T a, T b) {
            this.a = a;
            this.b = b;
        }

        public T getA() {
            return a;
        }

        public T getB() {
            return b;
        }
    }

    public static Pair<List<Edit>> split(List<Edit> edits, Rational trackByMv, long tvMv) {
        long total = 0;
        List<Edit> l = new ArrayList<Edit>();
        List<Edit> r = new ArrayList<Edit>();
        ListIterator<Edit> lit = edits.listIterator();
        while (lit.hasNext()) {
            Edit edit = lit.next();
            if (total + edit.getDuration() > tvMv) {
                int leftDurMV = (int) (tvMv - total);
                int leftDurMedia = trackByMv.multiplyS(leftDurMV);

                Edit left = new Edit(leftDurMV, edit.getMediaTime(), 1.0f);
                Edit right = new Edit(edit.getDuration() - leftDurMV, leftDurMedia + edit.getMediaTime(), 1.0f);

                lit.remove();
                if (left.getDuration() > 0) {
                    lit.add(left);
                    l.add(left);
                }
                if (right.getDuration() > 0) {
                    lit.add(right);
                    r.add(right);
                }
                break;
            } else {
                l.add(edit);
            }
            total += edit.getDuration();
        }
        while (lit.hasNext()) {
            r.add(lit.next());
        }
        return new Pair<List<Edit>>(l, r);
    }

    /**
     * Splits track on the timevalue specified
     * 
     * @param movie
     * @param track
     * @param tvMv
     * @return
     */
    public static Pair<List<Edit>> split(MovieBox movie, TrakBox track, long tvMv) {
        return split(track.getEdits(), new Rational(track.getTimescale(), movie.getTimescale()), tvMv);
    }

    public static void spread(MovieBox movie, TrakBox track, long tvMv, long durationMv) {
        Pair<List<Edit>> split = split(movie, track, tvMv);
        track.getEdits().add(split.getA().size(), new Edit(durationMv, -1, 1.0f));
    }

    public static void shift(MovieBox movie, TrakBox track, long tvMv) {
        track.getEdits().add(0, new Edit(tvMv, -1, 1.0f));
    }

    public static long[] getTimevalues(TrakBox track) {
        TimeToSampleBox stts = findFirst(track, TimeToSampleBox.class, "mdia", "minf", "stbl", "stts");
        int count = 0;
        TimeToSampleEntry[] tts = stts.getEntries();
        for (int i = 0; i < tts.length; i++)
            count += tts[i].getSampleCount();
        long[] tv = new long[count + 1];
        int k = 0;
        for (int i = 0; i < tts.length; i++) {
            for (int j = 0; j < tts[i].getSampleCount(); j++, k++) {
                tv[k + 1] = tv[k] + tts[i].getSampleDuration();
            }
        }
        return tv;
    }

    private static void appendToInternal(MovieBox movie, TrakBox dest, TrakBox src) {
        int off = appendEntries(dest, src);

        appendChunkOffsets(dest, src);
        appendTimeToSamples(dest, src);
        appendSampleToChunk(dest, src, off);
        appendSampleSizes(dest, src);
    }

    private static void updateDuration(TrakBox dest, TrakBox src) {
        MediaHeaderBox mdhd1 = NodeBox.findFirst(dest, MediaHeaderBox.class, "mdia", "mdhd");
        MediaHeaderBox mdhd2 = NodeBox.findFirst(src, MediaHeaderBox.class, "mdia", "mdhd");
        mdhd1.setDuration(mdhd1.getDuration() + mdhd2.getDuration());
    }

    public static void appendTo(MovieBox movie, TrakBox dest, TrakBox src) {
        appendToInternal(movie, dest, src);
        appendEdits(dest, src, dest.getEdits().size());
        updateDuration(dest, src);
    }

    public static void insertTo(MovieBox movie, TrakBox dest, TrakBox src, long tvMv) {
        appendToInternal(movie, dest, src);
        insertEdits(movie, dest, src, tvMv);
        updateDuration(dest, src);
    }

    private static void insertEdits(MovieBox movie, TrakBox dest, TrakBox src, long tvMv) {
        Pair<List<Edit>> split = split(movie, dest, tvMv);
        appendEdits(dest, src, split.getA().size());
    }

    private static void appendEdits(TrakBox dest, TrakBox src, int ind) {
        for (Edit edit : src.getEdits()) {
            edit.shift(dest.getMediaDuration());
        }
        dest.getEdits().addAll(ind, src.getEdits());
        dest.setEdits(dest.getEdits());
    }

    private static void appendSampleSizes(TrakBox trakBox1, TrakBox trakBox2) {
        SampleSizesBox stsz1 = NodeBox.findFirst(trakBox1, SampleSizesBox.class, "mdia", "minf", "stbl", "stsz");
        SampleSizesBox stsz2 = NodeBox.findFirst(trakBox2, SampleSizesBox.class, "mdia", "minf", "stbl", "stsz");
        if (stsz1.getDefaultSize() != stsz2.getDefaultSize())
            throw new IllegalArgumentException("Can't append to track that has different default sample size");
        SampleSizesBox stszr;
        if (stsz1.getDefaultSize() > 0) {
            stszr = new SampleSizesBox(stsz1.getDefaultSize(), stsz1.getCount() + stsz2.getCount());
        } else {
            stszr = new SampleSizesBox(ArrayUtils.addAll(stsz1.getSizes(), stsz2.getSizes()));
        }
        NodeBox.findFirst(trakBox1, NodeBox.class, "mdia", "minf", "stbl").replace("stsz", stszr);
    }

    private static void appendSampleToChunk(TrakBox trakBox1, TrakBox trakBox2, int off) {
        SampleToChunkBox stsc1 = NodeBox.findFirst(trakBox1, SampleToChunkBox.class, "mdia", "minf", "stbl",
                "stsc");
        SampleToChunkBox stsc2 = NodeBox.findFirst(trakBox2, SampleToChunkBox.class, "mdia", "minf", "stbl",
                "stsc");

        SampleToChunkEntry[] orig = stsc2.getSampleToChunk();
        SampleToChunkEntry[] shifted = new SampleToChunkEntry[orig.length];
        for (int i = 0; i < orig.length; i++) {
            shifted[i] = new SampleToChunkEntry(orig[i].getFirst() + stsc1.getSampleToChunk().length,
                    orig[i].getCount(), orig[i].getEntry() + off);
        }
        NodeBox.findFirst(trakBox1, NodeBox.class, "mdia", "minf", "stbl").replace("stsc",
                new SampleToChunkBox((SampleToChunkEntry[]) ArrayUtils.addAll(stsc1.getSampleToChunk(), shifted)));
    }

    private static int appendEntries(TrakBox trakBox1, TrakBox trakBox2) {
        appendDrefs(trakBox1, trakBox2);

        SampleEntry[] ent1 = NodeBox.findAll(trakBox1, SampleEntry.class, "mdia", "minf", "stbl", "stsd", null);
        SampleEntry[] ent2 = NodeBox.findAll(trakBox2, SampleEntry.class, "mdia", "minf", "stbl", "stsd", null);

        SampleDescriptionBox stsd = new SampleDescriptionBox(ent1);
        for (SampleEntry se : ent2) {
            se.setDrefInd((short) (se.getDrefInd() + ent1.length));
            stsd.add(se);
        }

        NodeBox.findFirst(trakBox1, NodeBox.class, "mdia", "minf", "stbl").replace("stsd", stsd);
        return ent1.length;
    }

    private static void appendDrefs(TrakBox trakBox1, TrakBox trakBox2) {
        DataRefBox dref1 = NodeBox.findFirst(trakBox1, DataRefBox.class, "mdia", "minf", "dinf", "dref");
        DataRefBox dref2 = NodeBox.findFirst(trakBox2, DataRefBox.class, "mdia", "minf", "dinf", "dref");
        dref1.getBoxes().addAll(dref2.getBoxes());
    }

    private static void appendTimeToSamples(TrakBox trakBox1, TrakBox trakBox2) {
        TimeToSampleBox stts1 = NodeBox.findFirst(trakBox1, TimeToSampleBox.class, "mdia", "minf", "stbl", "stts");
        TimeToSampleBox stts2 = NodeBox.findFirst(trakBox2, TimeToSampleBox.class, "mdia", "minf", "stbl", "stts");
        TimeToSampleBox sttsNew = new TimeToSampleBox(
                (TimeToSampleEntry[]) ArrayUtils.addAll(stts1.getEntries(), stts2.getEntries()));
        NodeBox.findFirst(trakBox1, NodeBox.class, "mdia", "minf", "stbl").replace("stts", sttsNew);
    }

    private static void appendChunkOffsets(TrakBox trakBox1, TrakBox trakBox2) {
        ChunkOffsetsBox stco1 = NodeBox.findFirst(trakBox1, ChunkOffsetsBox.class, "mdia", "minf", "stbl", "stco");
        ChunkOffsets64Box co641 = NodeBox.findFirst(trakBox1, ChunkOffsets64Box.class, "mdia", "minf", "stbl",
                "co64");
        ChunkOffsetsBox stco2 = NodeBox.findFirst(trakBox2, ChunkOffsetsBox.class, "mdia", "minf", "stbl", "stco");
        ChunkOffsets64Box co642 = NodeBox.findFirst(trakBox2, ChunkOffsets64Box.class, "mdia", "minf", "stbl",
                "co64");

        long[] off1 = stco1 == null ? co641.getChunkOffsets() : stco1.getChunkOffsets();
        long[] off2 = stco2 == null ? co642.getChunkOffsets() : stco2.getChunkOffsets();
        NodeBox stbl1 = NodeBox.findFirst(trakBox1, NodeBox.class, "mdia", "minf", "stbl");
        stbl1.filter(not("stco"));
        stbl1.filter(not("co64"));
        stbl1.add(co641 == null && co642 == null ? new ChunkOffsetsBox(ArrayUtils.addAll(off1, off2))
                : new ChunkOffsets64Box(ArrayUtils.addAll(off1, off2)));
    }

    public static void forceEditList(MovieBox movie, TrakBox trakBox) {
        List<Edit> edits = trakBox.getEdits();
        if (edits == null || edits.size() == 0) {
            MovieHeaderBox mvhd = findFirst(movie, MovieHeaderBox.class, "mvhd");
            edits = new ArrayList<Edit>();
            trakBox.setEdits(edits);
            edits.add(new Edit((int) mvhd.getDuration(), 0, 1.0f));
            trakBox.setEdits(edits);
        }
    }

    public static void forceEditList(MovieBox movie) {
        for (TrakBox trakBox : movie.getTracks()) {
            forceEditList(movie, trakBox);
        }
    }

    public static List<Edit> editsOnEdits(Rational mvByTrack, List<Edit> lower, List<Edit> higher) {
        List<Edit> result = new ArrayList<Edit>();
        List<Edit> next = new ArrayList<Edit>(lower);
        for (Edit edit : higher) {
            long startMv = mvByTrack.multiply(edit.getMediaTime());
            Pair<List<Edit>> split = split(next, mvByTrack.flip(), startMv);
            Pair<List<Edit>> split2 = split(split.getB(), mvByTrack.flip(), startMv + edit.getDuration());
            result.addAll(split2.getA());
            next = split2.getB();
        }
        return result;
    }
}