package org.jcodec.movtool;
import java.lang.IllegalStateException;
import java.lang.System;
import static java.lang.System.arraycopy;
import static org.jcodec.common.io.NIOUtils.readableChannel;
import static org.jcodec.common.io.NIOUtils.writableChannel;
import java.util.Iterator;
import org.jcodec.common.io.SeekableByteChannel;
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.platform.Platform;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Strips movie to editlist
*
* @author The JCodec project
*
*/
public class Strip {
public static void main1(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Syntax: strip <ref movie> <out movie>");
System.exit(-1);
}
SeekableByteChannel input = null;
SeekableByteChannel out = null;
try {
input = readableChannel(new File(args[0]));
File file = new File(args[1]);
Platform.deleteFile(file);
out = writableChannel(file);
MovieBox movie = MP4Util.createRefMovie(input, "file://" + new File(args[0]).getAbsolutePath());
new Strip().strip(movie);
MP4Util.writeMovie(out, movie);
} finally {
if (input != null)
input.close();
if (out != null)
out.close();
}
}
public void strip(MovieBox movie) throws IOException {
TrakBox[] tracks = movie.getTracks();
for (int i = 0; i < tracks.length; i++) {
TrakBox track = tracks[i];
stripTrack(movie, track);
}
}
public void stripTrack(MovieBox movie, TrakBox track) {
ChunkReader chunks = new ChunkReader(track);
List<Edit> edits = track.getEdits();
List<Edit> oldEdits = deepCopy(edits);
List<Chunk> result = new ArrayList<Chunk>();
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 = NodeBox.findFirstPath(track, NodeBox.class, Box.path("mdia.minf.stbl"));
stbl.replace("stts", getTimeToSamples(result));
stbl.replace("stsz", getSampleSizes(result));
stbl.replace("stsc", getSamplesToChunk(result));
stbl.removeChildren("stco", "co64");
stbl.add(getChunkOffsets(result));
NodeBox.findFirstPath(track, MediaHeaderBox.class, Box.path("mdia.mdhd")).setDuration(totalDuration(result));
}
private long totalDuration(List<Chunk> result) {
long duration = 0;
for (Chunk chunk : result) {
duration += chunk.getDuration();
}
return duration;
}
private List<Edit> deepCopy(List<Edit> edits) {
ArrayList<Edit> newList = new ArrayList<Edit>();
for (Edit edit : edits) {
newList.add(Edit.createEdit(edit));
}
return newList;
}
public Box getChunkOffsets(List<Chunk> 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 ? ChunkOffsets64Box.createChunkOffsets64Box(result) : ChunkOffsetsBox.createChunkOffsetsBox(result);
}
public TimeToSampleBox getTimeToSamples(List<Chunk> chunks) {
ArrayList<TimeToSampleEntry> tts = new ArrayList<TimeToSampleEntry>();
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 TimeToSampleBox.createTimeToSampleBox(tts.toArray(new TimeToSampleEntry[0]));
}
public SampleSizesBox getSampleSizes(List<Chunk> 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 SampleSizesBox.createSampleSizesBox(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 SampleSizesBox.createSampleSizesBox2(sizes);
}
public SampleToChunkBox getSamplesToChunk(List<Chunk> chunks) {
ArrayList<SampleToChunkEntry> result = new ArrayList<SampleToChunkEntry>();
Iterator<Chunk> 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 SampleToChunkBox.createSampleToChunkBox(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);
}
}