org.jcodec.movtool.Flattern Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcodec Show documentation
Show all versions of jcodec Show documentation
Pure Java implementation of video/audio codecs and formats
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();
}
}
}