/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portlet.documentlibrary.util; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.util.FileUtil; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; /** * This class updates an MP4 to be a "fast start" movie. This allows the MP4 to * be more quickly read by a client player without needing to completely * download the file. It is achieved by moving the movie's MOOV atom to the * front of the file. This code is based on the public domain code of <a * href="http://www.wired-space.de/media/JQTFaststart.java">JQTFaststat</a>. * * @author Juan González */ public class JQTFastStart { public static void convert(File inputFile, File outputFile) throws IOException { _instance.doConvert(inputFile, outputFile); } protected void doConvert(File inputFile, File outputFile) throws IOException { validate(inputFile, outputFile); RandomAccessFile randomAccessInputFile = null; RandomAccessFile randomAccessOutputFile = null; try { randomAccessInputFile = new RandomAccessFile(inputFile, "r"); Atom atom = null; Atom ftypAtom = null; boolean fastStart = false; boolean ftypFound = false; boolean mdatFound = false; while (randomAccessInputFile.getFilePointer() < randomAccessInputFile.length()) { atom = new Atom(randomAccessInputFile); if (!atom.isTopLevelAtom()) { throw new IOException( "Non top level atom was found " + atom.getType()); } if (ftypFound && !mdatFound && atom.isMOOV()) { fastStart = true; break; } if (atom.isFTYP()) { ftypAtom = atom; ftypAtom.fillBuffer(randomAccessInputFile); ftypFound = true; } else if (atom.isMDAT()) { mdatFound = true; randomAccessInputFile.skipBytes((int)atom.getSize()); } else { randomAccessInputFile.skipBytes((int)atom.getSize()); } } if (fastStart) { if (_log.isInfoEnabled()) { _log.info("The movie is already a fast start MP4"); } FileUtil.move(inputFile, outputFile); return; } if (!atom.isMOOV()) { throw new IOException("Last atom was not of type MOOV"); } randomAccessInputFile.seek(atom.getOffset()); Atom moovAtom = atom; moovAtom.fillBuffer(randomAccessInputFile); if (moovAtom.hasCompressedMoovAtom()) { throw new IOException("Compressed MOOV atoms are unsupported"); } moovAtom.patchAtom(); randomAccessInputFile.seek( ftypAtom.getOffset() + ftypAtom.getSize()); randomAccessOutputFile = new RandomAccessFile(outputFile, "rw"); randomAccessOutputFile.setLength(0); randomAccessOutputFile.write(ftypAtom.getBuffer()); randomAccessOutputFile.write(moovAtom.getBuffer()); byte[] buffer = new byte[1024 * 1024]; while ((randomAccessInputFile.getFilePointer() + buffer.length) < moovAtom.getOffset()) { int read = randomAccessInputFile.read(buffer); randomAccessOutputFile.write(buffer, 0, read); } int bufferSize = (int) (moovAtom.getOffset() - randomAccessInputFile.getFilePointer()); buffer = new byte[bufferSize]; randomAccessInputFile.readFully(buffer); randomAccessOutputFile.write(buffer); } finally { if (randomAccessInputFile != null) { randomAccessInputFile.close(); } if (randomAccessOutputFile != null) { randomAccessOutputFile.close(); } } } protected void validate(File inputFile, File outputFile) throws IOException { if (!inputFile.exists() || !inputFile.canRead()) { throw new IOException("Input file cannot be read " + inputFile); } if (outputFile.exists()) { throw new IOException("Output file alread exists " + outputFile); } if (inputFile.getAbsolutePath().equals(outputFile.getAbsolutePath())) { throw new IOException( "Input file and output file cannot be the same " + inputFile); } } private static final Log _log = LogFactoryUtil.getLog(JQTFastStart.class); private static final JQTFastStart _instance = new JQTFastStart(); }