001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.documentlibrary.util;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.util.FileUtil;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.io.RandomAccessFile;
024    
025    /**
026     * This class updates an MP4 to be a "fast start" movie. This allows the MP4 to
027     * be more quickly read by a client player without needing to completely
028     * download the file. It is achieved by moving the movie's MOOV atom to the
029     * front of the file. This code is based on the public domain code of <a
030     * href="http://www.wired-space.de/media/JQTFaststart.java">JQTFaststat</a>.
031     *
032     * @author Juan Gonz??lez
033     */
034    public class JQTFastStart {
035    
036            public static void convert(File inputFile, File outputFile)
037                    throws IOException {
038    
039                    _instance.doConvert(inputFile, outputFile);
040            }
041    
042            protected void doConvert(File inputFile, File outputFile)
043                    throws IOException {
044    
045                    validate(inputFile, outputFile);
046    
047                    RandomAccessFile randomAccessInputFile = null;
048                    RandomAccessFile randomAccessOutputFile = null;
049    
050                    try {
051                            randomAccessInputFile = new RandomAccessFile(inputFile, "r");
052    
053                            Atom atom = null;
054                            Atom ftypAtom = null;
055    
056                            boolean ftypFound = false;
057                            boolean mdatFound = false;
058                            boolean isFastStart = false;
059    
060                            while (randomAccessInputFile.getFilePointer() <
061                                                    randomAccessInputFile.length()) {
062    
063                                    atom = new Atom(randomAccessInputFile);
064    
065                                    if (!atom.isTopLevelAtom()) {
066                                            throw new IOException(
067                                                    "Non top level atom was found " + atom.getType());
068                                    }
069    
070                                    if (ftypFound && !mdatFound && atom.isMOOV()) {
071                                            isFastStart = true;
072    
073                                            break;
074                                    }
075    
076                                    if (atom.isFTYP()) {
077                                            ftypAtom = atom;
078    
079                                            ftypAtom.fillBuffer(randomAccessInputFile);
080    
081                                            ftypFound = true;
082                                    }
083                                    else if (atom.isMDAT()) {
084                                            mdatFound = true;
085    
086                                            randomAccessInputFile.skipBytes((int)atom.getSize());
087                                    }
088                                    else {
089                                            randomAccessInputFile.skipBytes((int)atom.getSize());
090                                    }
091                            }
092    
093                            if (isFastStart) {
094                                    if (_log.isInfoEnabled()) {
095                                            _log.info("The movie is already a fast start MP4");
096                                    }
097    
098                                    FileUtil.move(inputFile, outputFile);
099    
100                                    return;
101                            }
102    
103                            if (!atom.isMOOV()) {
104                                    throw new IOException("Last atom was not of type MOOV");
105                            }
106    
107                            randomAccessInputFile.seek(atom.getOffset());
108    
109                            Atom moovAtom = atom;
110    
111                            moovAtom.fillBuffer(randomAccessInputFile);
112    
113                            if (moovAtom.hasCompressedMoovAtom()) {
114                                    throw new IOException("Compressed MOOV atoms are unsupported");
115                            }
116    
117                            moovAtom.patchAtom();
118    
119                            randomAccessInputFile.seek(
120                                    ftypAtom.getOffset() + ftypAtom.getSize());
121    
122                            randomAccessOutputFile = new RandomAccessFile(outputFile, "rw");
123    
124                            randomAccessOutputFile.setLength(0);
125    
126                            randomAccessOutputFile.write(ftypAtom.getBuffer());
127                            randomAccessOutputFile.write(moovAtom.getBuffer());
128    
129                            byte[] buffer = new byte[1024 * 1024];
130    
131                            while ((randomAccessInputFile.getFilePointer() + buffer.length) <
132                                                    moovAtom.getOffset()) {
133    
134                                    int read = randomAccessInputFile.read(buffer);
135    
136                                    randomAccessOutputFile.write(buffer, 0, read);
137                            }
138    
139                            int bufferSize = (int)
140                                    (moovAtom.getOffset() - randomAccessInputFile.getFilePointer());
141    
142                            buffer = new byte[bufferSize];
143    
144                            randomAccessInputFile.readFully(buffer);
145    
146                            randomAccessOutputFile.write(buffer);
147                    }
148                    finally {
149                            if (randomAccessInputFile != null) {
150                                    randomAccessInputFile.close();
151                            }
152    
153                            if (randomAccessOutputFile != null) {
154                                    randomAccessOutputFile.close();
155                            }
156                    }
157            }
158    
159            protected void validate(File inputFile, File outputFile)
160                    throws IOException {
161    
162                    if (!inputFile.exists() || !inputFile.canRead()) {
163                            throw new IOException("Input file cannot be read " + inputFile);
164                    }
165    
166                    if (outputFile.exists()) {
167                            throw new IOException("Output file alread exists " + outputFile);
168                    }
169    
170                    if (inputFile.getAbsolutePath().equals(outputFile.getAbsolutePath())) {
171                            throw new IOException(
172                                    "Input file and output file cannot be the same " + inputFile);
173                    }
174            }
175    
176            private static Log _log = LogFactoryUtil.getLog(JQTFastStart.class);
177    
178            private static JQTFastStart _instance = new JQTFastStart();
179    
180    }