package mp4.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import mp4.util.atom.Atom;
import mp4.util.atom.AtomException;
import mp4.util.atom.MdatAtom;
import mp4.util.atom.MoovAtom;
import mp4.util.atom.MvhdAtom;
import mp4.util.atom.TrakAtom;
/**
* This class is used to split an mpeg4 file.
*
* The mpeg4 file format is the
* <a href="http://developer.apple.com/DOCUMENTATION/QuickTime/QTFF/qtff.pdf">Quicktime format</a>
*
* The mpeg4 format is also ISO standard 14496-12. That is, Part 12 of
* the ISO mpeg4 specificatio.
*
* Splitting the mpeg4 file requires rewritting the stbl atom container
* with new data and cutting off the mdat section.
*/
public class Mp4Split extends Mp4Parser {
public String inputFile;
public String outputFile;
public boolean writeMdat = true;
public static float time;
protected MoovAtom cutMoov;
protected MdatAtom cutMdat;
Mp4InterleaveWriter iwriter = null;
protected boolean force32bit = false;
protected boolean force32bit_times = false;
public Mp4Split(DataInputStream mp4file) {
super(mp4file);
}
public Mp4Split() {
}
/**
* Constructor for the Mpeg-4 file splitter. It opens the
* @param fn
* @throws IOException
*/
/*
public Mp4Split(String fn) {
try {
mp4file = new DataInputStream(new FileInputStream(fn));
MP4Log.log("DBG: file size " + new File(fn).length());
} catch (FileNotFoundException e) {
MP4Log.log("File not found " + fn);
System.exit(-1);
}
}
*/
public long lenHeaders() throws IOException {
return
ftyp.size()
+ cutMoov.size()
+ cutMdat.getHeaderSize()
;
}
public long lenSplitMp4() throws IOException {
return
ftyp.size()
+ cutMoov.size()
+ cutMdat.size()
;
}
public void writeMdatHeader(DataOutputStream dos, long len) throws IOException {
cutMdat.writeHeader(dos, len);
}
public void writeMdatBody(DataOutputStream dos) throws IOException {
cutMdat.writeData(dos);
}
public void writeProgressiveMp4(DataOutputStream dos, long mDatLen) throws IOException {
ftyp.writeData(dos);
cutMoov.writeData(dos);
long headerLen = ftyp.size() + cutMoov.size() + cutMdat.getHeaderSize();
cutMdat.writeHeader(dos, mDatLen+headerLen);
System.out.println("Header written len: " + headerLen + " Body:" + mDatLen);
}
public void writeSplitMp4(DataOutputStream dos) throws IOException {
ftyp.writeData(dos);
if (iwriter != null) {
iwriter.write(dos, writeMdat);
} else {
cutMoov.writeData(dos);
if (this.writeMdat) {
cutMdat.writeData(dos);
}
}
}
public float getTimeFromByteOffset(long offset) {
return 0.0f;
}
public void calcSplitMp4() throws IOException {
calcSplitMp4(false);
}
/**
*
* @param reinterleave
* @return file offset at which to start reading the media
* @throws IOException
*/
public long calcSplitMp4(boolean reinterleave) throws IOException {
long mdatOffset = 0;
try {
mdatOffset = parseMp4();
MP4Log.log("DBG: old mdat offset: " + mdatOffset);
// Remove all but one video, one audio track
// this makes some things a little easier
boolean hasVideo = false;
boolean hasAudio = false;
Iterator<TrakAtom> it = moov.getTracks();
while (it.hasNext()) {
TrakAtom trak = it.next();
if (!trak.isEnabled()) {
it.remove();
continue;
}
if (trak.getMdia().getHdlr().isVideo()) {
if (hasVideo) {
it.remove();
} else {
hasVideo = true;
}
} else if (trak.getMdia().getHdlr().isSound()) {
if (hasAudio) {
it.remove();
} else {
hasAudio = true;
}
} else {
it.remove();
}
}
//float common = moov.findAdjustedTime(time);
cutMoov = moov.cut(time);
// Remove the IODS atom
// because it may point to tracks we have already removed
cutMoov.setIods(null);
cutMoov.recomputeSize();
long startMdataOffset = cutMoov.firstDataByteOffset();
long mdatSkip = cutMoov.firstDataByteOffset()
- moov.firstDataByteOffset();
cutMdat = mdat.cut(mdatSkip);
int mdatHeaderSize = cutMdat.getHeaderSize();
// update stco segment by mdatSkip + difference in moov size
long newMoovSz = ftyp.size() + cutMoov.size();
long updateAmount = mdatSkip + (mdatOffset - newMoovSz);
if (mdat.isLargeAtom()) {
updateAmount += Atom.LARGE_SIZE_SIZE;
mdatHeaderSize+= Atom.LARGE_SIZE_SIZE;
}
MP4Log.log("DBG: updateAmount " + updateAmount);
cutMoov.fixupOffsets(-updateAmount);
MP4Log.log("DBG: movie skip " + mdatSkip);
MP4Log.log("DBG: Cut Movie time "
+ cutMoov.getMvhd().getDurationNormalized() + " sec ");
if (reinterleave) {
iwriter = new Mp4InterleaveWriter(cutMoov,
cutMdat,
ftyp.size() + cutMoov.size() + Atom.ATOM_HEADER_SIZE);
iwriter.calcInterleave();
} else {
// In any case, mdat is going to be at the end, and 0 is a legal size in MP4s
// meaning everything to the end of the file is part of the atom. To simplify 64-bit handling, let's just do it.
//cutMdat.setSize(0);
}
return startMdataOffset + mdatHeaderSize;
// return updateAmount + mdatHeaderSize;
} catch (AtomException e) {
MP4Log.log("Error parseing Mp4 file " + e);
throw new IOException(e.getMessage());
}
}
public long progSplitMp4(boolean reinterleave) throws IOException {
long mdatOffset = 0;
try {
mdatOffset = parseMp4();
MP4Log.log("DBG: old mdat offset: " + mdatOffset);
// Remove all but one video, one audio track
// this makes some things a little easier
boolean hasVideo = false;
boolean hasAudio = false;
Iterator<TrakAtom> it = moov.getTracks();
while (it.hasNext()) {
TrakAtom trak = it.next();
if (!trak.isEnabled()) {
it.remove();
continue;
}
if (trak.getMdia().getHdlr().isVideo()) {
if (hasVideo) {
it.remove();
} else {
hasVideo = true;
}
} else if (trak.getMdia().getHdlr().isSound()) {
if (hasAudio) {
it.remove();
} else {
hasAudio = true;
}
} else {
it.remove();
}
}
//float common = moov.findAdjustedTime(time);
cutMoov = moov.cut(time);
// Remove the IODS atom
// because it may point to tracks we have already removed
cutMoov.setIods(null);
cutMoov.recomputeSize();
// long startMdataOffset = cutMoov.firstDataByteOffset();
long mdatSkip = cutMoov.firstDataByteOffset()
- moov.firstDataByteOffset();
cutMdat = mdat.cut(mdatSkip);
int mdatHeaderSize = cutMdat.getHeaderSize();
// update stco segment by mdatSkip + difference in moov size
long newMoovSz = ftyp.size() + cutMoov.size();
long updateAmount = mdatSkip + (mdatOffset - newMoovSz);
if (mdat.isLargeAtom()) {
updateAmount += Atom.LARGE_SIZE_SIZE;
mdatHeaderSize+= Atom.LARGE_SIZE_SIZE;
}
MP4Log.log("DBG: updateAmount " + updateAmount);
cutMoov.fixupOffsets(-updateAmount);
MP4Log.log("DBG: movie skip " + mdatSkip);
MP4Log.log("DBG: Cut Movie time "
+ cutMoov.getMvhd().getDurationNormalized() + " sec ");
if (reinterleave) {
iwriter = new Mp4InterleaveWriter(cutMoov,
cutMdat,
ftyp.size() + cutMoov.size() + Atom.ATOM_HEADER_SIZE);
iwriter.calcInterleave();
} else {
// In any case, mdat is going to be at the end, and 0 is a legal size in MP4s
// meaning everything to the end of the file is part of the atom. To simplify 64-bit handling, let's just do it.
//cutMdat.setSize(0);
}
return mdatSkip + mdatOffset + mdatHeaderSize;
// return startMdataOffset + mdatHeaderSize;
// return updateAmount + mdatHeaderSize;
} catch (AtomException e) {
MP4Log.log("Error parseing Mp4 file " + e);
throw new IOException(e.getMessage());
}
}
/*
public void splitMp4() {
try {
FtypAtom ftyp = (FtypAtom) parseAtom();
MoovAtom moov = (MoovAtom) parseAtom();
MdatAtom mdat = (MdatAtom) parseAtom();
MP4Log.log("DBG: moov size " + moov.dataSize());
MP4Log.log("DBG: mdat size " + mdat.dataSize());
MoovAtom cutMoov = moov.cut(time);
MP4Log.log("DBG: moov chunk " + moov.firstDataByteOffset());
MP4Log.log("DBG: cut moov chunk " + cutMoov.firstDataByteOffset());
long mdatSkip = cutMoov.firstDataByteOffset() - moov.firstDataByteOffset();
MdatAtom cutMdat = mdat.cut(mdatSkip);
// update stco segment by mdatSkip + difference in moov size
long updateAmount = mdatSkip + (moov.size() - cutMoov.size());
MP4Log.log("DBG: updateAmount " + updateAmount);
cutMoov.fixupOffsets(-updateAmount);
MP4Log.log("DBG: movie skip " + mdatSkip);
MP4Log.log("DBG: Cut Movie time " + cutMoov.getMvhd().getDurationNormalized() + " sec ");
DataOutputStream dos = new DataOutputStream(new FileOutputStream(outputFile));
ftyp.writeData(dos);
cutMoov.writeData(dos);
if (Mp4Split.mdat) {
cutMdat.writeData(dos);
}
} catch (AtomException e) {
MP4Log.log("Error parseing Mp4 file " + e);
} catch (FileNotFoundException e) {
MP4Log.log("Error creating file output stream");
} catch (IOException e) {
MP4Log.log("Error writing output ");
e.printStackTrace();
}
}
*/
/**
* Process the command line arguments.
* @param args the user-specified arguments
*/
protected void processArgs(String[] args) {
int i = 0;
while (i < args.length) {
String arg = args[i];
if (arg.equals("-in")) {
inputFile = args[++i];
} else if (arg.equals("-out")) {
outputFile = args[++i];
} else if (arg.equals("-time")) {
time = Float.valueOf(args[++i]);
} else if (arg.equals("-no_mdat")) {
writeMdat = false;
} else {
help();
}
i++;
}
if (inputFile == null) {
help();
}
}
private static void help() {
MP4Log.log("Mp4Split <args>");
MP4Log.log(" -in <inputfile.mp4>");
MP4Log.log(" -out <outputfile.mp4>");
MP4Log.log(" -time <seconds>");
MP4Log.log(" [-no_mdat]");
System.exit(-1);
}
public void runCmdLine(String[] args) {
processArgs(args);
try {
mp4file = new DataInputStream(new FileInputStream(new File(inputFile)));
calcSplitMp4();
writeSplitMp4(new DataOutputStream(new FileOutputStream(new File(outputFile))));
} catch (FileNotFoundException e) {
MP4Log.log("Err: FileNoutFound: " + e.getMessage());
} catch (IOException e) {
MP4Log.log("Err: IOException: " + e.getMessage());
}
MP4Log.log("Complete.");
}
/**
* @param args
*/
public static void main(String[] args) {
Mp4Split splitter = new Mp4Split();
splitter.runCmdLine(args);
}
}