org.jcodec.movtool.Strip 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 java.lang.System.arraycopy;
import static org.jcodec.containers.mp4.boxes.Box.findFirst;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Iterator;
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.Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
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.NodeBox;
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;
import org.jcodec.containers.mp4.io.FileInput;
/**
* This class is part of JCodec ( www.jcodec.org )
* This software is distributed under FreeBSD License
*
* Strips movie to editlist
*
* @author Stanislav Vitvitskiy
*
*/
public class Strip {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Syntax: strip ");
System.exit(-1);
}
FileInput input = null;
RandomAccessFile out = null;
try {
input = new FileInput(new File(args[0]));
File file = new File(args[1]);
file.delete();
out = new RandomAccessFile(file, "rw");
MovieBox movie = MP4Util.createRefMovie(input, "file://" + new File(args[0]).getAbsolutePath());
new Strip().strip(movie);
movie.write(out);
} finally {
if (input != null)
input.close();
if (out != null)
out.close();
}
}
public void strip(MovieBox movie) throws IOException {
for (TrakBox track : movie.getTracks()) {
stripTrack(movie, track);
}
}
public void stripTrack(MovieBox movie, TrakBox track) {
ChunkReader chunks = new ChunkReader(track);
List edits = track.getEdits();
List oldEdits = deepCopy(edits);
List result = new ArrayList();
Chunk chunk;
while ((chunk = chunks.next()) != null) {
boolean intersects = false;
for (Edit edit : oldEdits) {
if (edit.getMediaTime() == -1)
continue; // track offset, not real edit
long editS = edit.getMediaTime();
long editE = edit.getMediaTime() + track.rescale(edit.getDuration(), movie.getTimescale());
long chunkS = chunk.getStartTv();
long chunkE = chunk.getStartTv() + chunk.getDuration();
intersects = intersects(editS, editE, chunkS, chunkE);
if (intersects)
break;
}
if (!intersects) {
for (int i = 0; i < oldEdits.size(); i++) {
if (oldEdits.get(i).getMediaTime() >= chunk.getStartTv() + chunk.getDuration())
edits.get(i).shift(-chunk.getDuration());
}
} else
result.add(chunk);
}
NodeBox stbl = findFirst(track, NodeBox.class, "mdia", "minf", "stbl");
stbl.replace("stts", getTimeToSamples(result));
stbl.replace("stsz", getSampleSizes(result));
stbl.replace("stsc", getSamplesToChunk(result));
stbl.filter(Box.not("stco"));
stbl.filter(Box.not("co64"));
stbl.add(getChunkOffsets(result));
findFirst(track, MediaHeaderBox.class, "mdia", "mdhd").setDuration(totalDuration(result));
}
private long totalDuration(List result) {
long duration = 0;
for (Chunk chunk : result) {
duration += chunk.getDuration();
}
return duration;
}
private List deepCopy(List edits) {
ArrayList newList = new ArrayList();
for (Edit edit : edits) {
newList.add(new Edit(edit));
}
return newList;
}
public Box getChunkOffsets(List chunks) {
long[] result = new long[chunks.size()];
boolean longBox = false;
int i = 0;
for (Chunk chunk : chunks) {
if (chunk.getOffset() >= 0x100000000L)
longBox = true;
result[i++] = chunk.getOffset();
}
return longBox ? new ChunkOffsets64Box(result) : new ChunkOffsetsBox(result);
}
public TimeToSampleBox getTimeToSamples(List chunks) {
ArrayList tts = new ArrayList();
int curTts = -1, cnt = 0;
for (Chunk chunk : chunks) {
if (chunk.getSampleDur() > 0) {
if (curTts == -1 || curTts != chunk.getSampleDur()) {
if (curTts != -1)
tts.add(new TimeToSampleEntry(cnt, curTts));
cnt = 0;
curTts = chunk.getSampleDur();
}
cnt += chunk.getSampleCount();
} else {
for (int dur : chunk.getSampleDurs()) {
if (curTts == -1 || curTts != dur) {
if (curTts != -1)
tts.add(new TimeToSampleEntry(cnt, curTts));
cnt = 0;
curTts = dur;
}
++cnt;
}
}
}
if (cnt > 0)
tts.add(new TimeToSampleEntry(cnt, curTts));
return new TimeToSampleBox(tts.toArray(new TimeToSampleEntry[0]));
}
public SampleSizesBox getSampleSizes(List chunks) {
int nSamples = 0, prevSize = chunks.get(0).getSampleSize();
for (Chunk chunk : chunks) {
nSamples += chunk.getSampleCount();
if (prevSize == 0 && chunk.getSampleSize() != 0)
throw new RuntimeException("Mixed sample sizes not supported");
}
if (prevSize > 0)
return new SampleSizesBox(prevSize, nSamples);
int[] sizes = new int[nSamples];
int startSample = 0;
for (Chunk chunk : chunks) {
arraycopy(chunk.getSampleSizes(), 0, sizes, startSample, chunk.getSampleCount());
startSample += chunk.getSampleCount();
}
return new SampleSizesBox(sizes);
}
public SampleToChunkBox getSamplesToChunk(List chunks) {
ArrayList result = new ArrayList();
Iterator it = chunks.iterator();
Chunk chunk = it.next();
int curSz = chunk.getSampleCount();
int curEntry = chunk.getEntry();
int first = 1, cnt = 1;
while (it.hasNext()) {
chunk = it.next();
int newSz = chunk.getSampleCount();
int newEntry = chunk.getEntry();
if (curSz != newSz || curEntry != newEntry) {
result.add(new SampleToChunkEntry(first, curSz, curEntry));
curSz = newSz;
curEntry = newEntry;
first += cnt;
cnt = 0;
}
++cnt;
}
if (cnt > 0)
result.add(new SampleToChunkEntry(first, curSz, curEntry));
return new SampleToChunkBox(result.toArray(new SampleToChunkEntry[0]));
}
private boolean intersects(long a, long b, long c, long d) {
return (a >= c && a < d) || (b >= c && b < d) || (c >= a && c < b) || (d >= a && d < b);
}
}