package mp4.util;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import mp4.util.atom.Atom;
import mp4.util.atom.AtomException;
import mp4.util.atom.CttsAtom;
import mp4.util.atom.DefaultAtomVisitor;
import mp4.util.atom.ElstAtom;
import mp4.util.atom.FtypAtom;
import mp4.util.atom.HdlrAtom;
import mp4.util.atom.LeafAtom;
import mp4.util.atom.MdhdAtom;
import mp4.util.atom.MvhdAtom;
import mp4.util.atom.StcoAtom;
import mp4.util.atom.StscAtom;
import mp4.util.atom.StsdAtom;
import mp4.util.atom.StssAtom;
import mp4.util.atom.StszAtom;
import mp4.util.atom.SttsAtom;
import mp4.util.atom.TkhdAtom;
public class Mp4Dump extends DefaultAtomVisitor {
// the input mp4 file
private DataInputStream mp4file;
// the output stream
private PrintStream out;
// the current indentation level
private int level;
// the last media handler processed, need since the stsd requires context
private HdlrAtom mediaHandler;
private static String outputFile = null;
private static String inputFile = null;
private static int maxEntries = Integer.MAX_VALUE;
/**
* Constructor for the Mpeg-4 file reader. It opens the mp4 file.
* @param inputfn the Mpeg-4 file name
* @param outputfn where the output goes, System.out by default
*/
public Mp4Dump(String inputfn, String outputfn) {
this.level = 0;
this.out = System.out;
this.mediaHandler = null;
try {
mp4file = new DataInputStream(new FileInputStream(inputfn));
if (outputfn != null) {
this.out = new PrintStream(outputfn);
}
} catch (FileNotFoundException e) {
MP4Log.log("File not found " + inputfn);
e.printStackTrace();
System.exit(-1);
}
}
/**
* Method to indent the output based upon the current level
*/
private void indent() {
for (int i = 0; i < level; i++) {
out.print("\t");
}
}
/**
* Print with indentation the leaf atom header data
* @param atom the leaf atom to print and indent
* @throws AtomException
*/
private void printLeafHeader(LeafAtom atom) throws AtomException {
indent();
out.print(atom);
atom.readData(mp4file);
}
/**
* The default action for an atom, which covers both leaf and
* container atoms. For leaf atoms, only the name is printed.
* Otherwise, the visit method for the atom needs to be implemented.
* For a container atom, the method increments the indent level
* and processes the atoms in the container.
* @param atom the atom to process
* @throws AtomException is there is an IOException
*/
@Override
protected void defaultAction(Atom atom) throws AtomException {
indent();
out.println(atom);
if (atom.isContainer()) {
level = level + 1;
long bytesRead = 0;
long bytesToRead = atom.dataSize();
while (bytesRead < bytesToRead) {
bytesRead += printAtom();
}
level = level - 1;
}
else {
try {
// some more ugly code to deal with unsigned vs. signed problems
long dataSize = atom.dataSize();
while (dataSize > Integer.MAX_VALUE) {
mp4file.skipBytes(Integer.MAX_VALUE);
dataSize -= Integer.MAX_VALUE;
}
mp4file.skipBytes((int)dataSize);
} catch (IOException e) {
throw new AtomException("Unable to read mp4 file");
}
}
}
@Override
public void visit(CttsAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" entries " + atom.getNumEntries());
level = level + 1;
for (int i = 0; i < atom.getNumEntries() && i < maxEntries; i++) {
indent();
out.println(atom.getSampleCount(i) + " " + atom.getSampleOffset(i));
}
level = level - 1;
}
@Override
public void visit(ElstAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" entries " + atom.getNumEntries());
level = level + 1;
for (int i = 0; i < atom.getNumEntries() && i < maxEntries; i++) {
indent();
out.println("duration " + atom.getDuration(i) +
" time " + atom.getMediaTime(i) + " rate " + atom.getMediaRate(i));
}
level = level - 1;
}
@Override
public void visit(FtypAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" " + new String(atom.getMajorBrand()));
}
@Override
public void visit(HdlrAtom atom) throws AtomException {
printLeafHeader(atom);
mediaHandler = atom;
out.println(" " + atom.getHandlerType());
}
@Override
public void visit(MvhdAtom atom) throws AtomException {
printLeafHeader(atom);
long ts = atom.getTimeScale();
long duration = atom.getDuration();
printDuration(ts, duration);
out.println();
}
@Override
public void visit(MdhdAtom atom) throws AtomException {
printLeafHeader(atom);
long ts = atom.getTimeScale();
long duration = atom.getDuration();
printDuration(ts, duration);
out.println();
}
@Override
public void visit(StcoAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" entries " + atom.getNumEntries());
level = level + 1;
for (int i = 0; i < atom.getNumEntries() && i < maxEntries; i++) {
indent();
out.println((i+1) + " " + atom.getChunkOffset(i+1));
}
level = level - 1;
}
@Override
public void visit(StscAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" entries " + atom.getNumEntries());
level = level + 1;
for (int i = 0; i < atom.getNumEntries(); i++) {
indent();
out.println(atom.getFirstChunk(i) +
" " + atom.getSamplesPerChunk(i) +
" " + atom.getDescriptionId(i));
}
level = level - 1;
}
@Override
public void visit(StsdAtom atom) throws AtomException {
//printLeafHeader(atom);
out.print(" entries " + atom.getNumEntries());
/*
for (int i = 0 ; i < atom.getNumEntries(); i++) {
if (mediaHandler.isVideo()) {
out.print(" " + atom.getWidth() + "x" + atom.getHeight());
}
else if (mediaHandler.isSound()) {
}
out.println();
}
*/
}
@Override
public void visit(StssAtom atom) throws AtomException {
printLeafHeader(atom);
long numEntries = atom.getNumEntries();
out.println(" entries " + numEntries);
level = level + 1;
for (int i = 0; i < numEntries && i < maxEntries; i++) {
indent();
out.println((i + 1) + " " + atom.getSampleEntry(i));
}
level = level - 1;
}
@Override
public void visit(StszAtom atom) throws AtomException {
printLeafHeader(atom);
out.print(" entries " + atom.getNumEntries());
long size = atom.getSampleSize();
if (size != 0) {
out.println(" sample size " + size);
}
else {
out.println();
level = level + 1;
long totalSize = 0;
for (int i = 0; i < atom.getNumEntries() && i < maxEntries; i++) {
indent();
out.println((i + 1) + " " + atom.getTableSampleSize(i+1));
totalSize += atom.getTableSampleSize(i+1);
}
level = level - 1;
}
}
@Override
public void visit(SttsAtom atom) throws AtomException {
printLeafHeader(atom);
out.println(" entries " + atom.getNumEntries());
level = level + 1;
long time = 0;
for (int i = 0; i < atom.getNumEntries() && i < maxEntries; i++) {
indent();
out.println(atom.getSampleCount(i) + " " + atom.getSampleDuration(i));
time += atom.getSampleCount(i) * atom.getSampleDuration(i);
}
indent();
out.println("stts time = " + time);
level = level - 1;
}
@Override
public void visit(TkhdAtom atom) throws AtomException {
printLeafHeader(atom);
//out.print(" track " + atom.getTrackId() + " " + atom.getDuration());
out.println();
}
/**
* Read an atom from the mpeg4 file and print it.
* @return the number of bytes read
* @throws AtomException
*/
private long printAtom() throws AtomException {
// get the atom size
byte[] word = new byte[Atom.ATOM_WORD];
int num;
try {
num = mp4file.read(word);
} catch (IOException e1) {
throw new AtomException("IOException while reading file");
}
// check for end of file
if (num == -1) {
return -1;
}
if (num != Atom.ATOM_WORD) {
throw new AtomException("Unable to read enough bytes for atom");
}
long size = Atom.byteArrayToUnsignedInt(word, 0);
// get the atom type
try {
num = mp4file.read(word);
} catch (IOException e1) {
throw new AtomException("IOException while reading file");
}
if (num != Atom.ATOM_WORD) {
throw new AtomException("Unable to read enough bytes for atom");
}
try {
Class<?> cls = Class.forName(Atom.typeToClassName(word, null));
Atom atom = (Atom) cls.newInstance();
atom.setSize(size);
atom.accept(this);
} catch (ClassNotFoundException e) {
throw new AtomException("Class not found " + e);
} catch (InstantiationException e) {
throw new AtomException("Unable to instantiate atom");
} catch (IllegalAccessException e) {
throw new AtomException("Unabel to access atom object");
}
return size;
}
public void printDuration(long timeScale, long duration) {
long totalSeconds = duration/timeScale;
out.print(" timescale " + timeScale);
out.print(" duration " + duration);
out.print(" time ");
if (totalSeconds > 3600) {
long hours = totalSeconds / 3600;
out.print(hours + "h");
totalSeconds = totalSeconds - (hours * 3600);
}
if (totalSeconds > 60) {
long minutes = totalSeconds / 60;
out.print(minutes + "m");
totalSeconds = totalSeconds - (minutes * 60);
}
out.print(totalSeconds + "s");
}
/**
* Process the command line arguments.
* @param args the user-specified arguments
*/
private static 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("-top")) {
maxEntries = Integer.valueOf(args[++i]);
}
else {
help();
}
i++;
}
if (inputFile == null) {
help();
}
}
private static void help() {
MP4Log.log("Mp4Dump <args>");
MP4Log.log(" -in inputfile.mp4");
MP4Log.log(" [-out outputfile.txt]\tdefault=System.out");
MP4Log.log(" [-top num]\tdefault=all");
System.exit(-1);
}
/**
* Main for the dump utility
* @param args the user-specifed arguments
*/
public static void main(String[] args) {
processArgs(args);
Mp4Dump reader = new Mp4Dump(inputFile, outputFile);
try {
long size = 0;;
while (size != -1) {
size = reader.printAtom();
}
} catch (AtomException e) {
MP4Log.log("Invalid atom descriptor");
e.printStackTrace();
}
}
}