Initial Commit
[Projects.git] / pkgbuilds / pytivo / pkg / usr / share / pyTivo / plugins / video / qtfaststart.py
blob72015897e43c24d0fe110a42e6562607f065de80
1 import logging
3 logger = logging.getLogger('pyTivo.video.qt-faststart')
4 """
5 Quicktime/MP4 Fast Start
6 ------------------------
7 Enable streaming and pseudo-streaming of Quicktime and MP4 files by
8 moving metadata and offset information to the front of the file.
10 This program is based on qt-faststart.c from the ffmpeg project, which is
11 released into the public domain, as well as ISO 14496-12:2005 (the official
12 spec for MP4), which can be obtained from the ISO or found online.
14 The goals of this project are to run anywhere without compilation (in
15 particular, many Windows and Mac OS X users have trouble getting
16 qt-faststart.c compiled), to run about as fast as the C version, to be more
17 user friendly, and to use less actual lines of code doing so.
19 Features
20 --------
22 * Works everywhere Python can be installed
23 * Handles both 32-bit (stco) and 64-bit (co64) atoms
24 * Handles any file where the mdat atom is before the moov atom
25 * Preserves the order of other atoms
26 * Can replace the original file (if given no output file)
28 License
29 -------
30 Copyright (C) 2008 Daniel G. Taylor <dan@programmer-art.org>
32 This program is free software: you can redistribute it and/or modify
33 it under the terms of the GNU General Public License as published by
34 the Free Software Foundation, either version 3 of the License, or
35 (at your option) any later version.
37 This program is distributed in the hope that it will be useful,
38 but WITHOUT ANY WARRANTY; without even the implied warranty of
39 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 GNU General Public License for more details.
42 You should have received a copy of the GNU General Public License
43 along with this program. If not, see <http://www.gnu.org/licenses/>.
44 """
46 import os
47 import struct
49 from optparse import OptionParser
50 from StringIO import StringIO
52 VERSION = "1.0"
53 CHUNK_SIZE = 512 * 1024
55 count = 0
57 def read_atom(datastream):
58 """
59 Read an atom and return a tuple of (size, type) where size is the size
60 in bytes (including the 8 bytes already read) and type is a "fourcc"
61 like "ftyp" or "moov".
62 """
63 values = struct.unpack(">L4c", datastream.read(8))
64 return values[0], "".join(values[1:])
66 def get_index(datastream):
67 """
68 Return an index of top level atoms, their absolute byte-position in the
69 file and their size in a dict:
71 index = {
72 "ftyp": [0, 24],
73 "moov": [25, 2658],
74 "free": [2683, 8],
75 ...
78 The keys are not guaranteed to be in order, but can be put in order
79 with a simple sort, e.g.
81 >>> keys = index.keys()
82 >>> keys.sort(lambda x, y: cmp(index[x][0], index[y][0]))
83 """
84 index = {}
86 # Read atoms until we catch an error
87 while(datastream):
88 try:
89 atom_size, atom_type = read_atom(datastream)
90 except:
91 break
92 index[atom_type] = [datastream.tell() - 8, atom_size]
93 datastream.seek(atom_size - 8, os.SEEK_CUR)
95 # Make sure the atoms we need exist
96 for key in ["ftyp", "moov", "mdat"]:
97 if not index.has_key(key):
98 logger.debug( "%s atom not found, is this a valid MOV/MP4 file?" % key)
99 return []
101 return index
103 def find_atoms(size, datastream):
105 This function is a generator that will yield either "stco" or "co64"
106 when either atom is found. datastream can be assumed to be 8 bytes
107 into the stco or co64 atom when the value is yielded.
109 It is assumed that datastream will be at the end of the atom after
110 the value has been yielded and processed.
112 size is the number of bytes to the end of the atom in the datastream.
114 stop = datastream.tell() + size
116 while datastream.tell() < stop:
117 atom_size, atom_type = read_atom(datastream)
118 if atom_type in ["trak", "mdia", "minf", "stbl"]:
119 # Known ancestor atom of stco or co64, search within it!
120 for atype in find_atoms(atom_size - 8, datastream):
121 yield atype
122 elif atom_type in ["stco", "co64"]:
123 yield atom_type
124 else:
125 # Ignore this atom, seek to the end of it.
126 datastream.seek(atom_size - 8, os.SEEK_CUR)
128 def output(outfile, offset, data):
129 global count
130 length = len(data)
131 if count + length > offset:
132 if offset > count:
133 data = data[offset - count:]
134 outfile.write(data)
135 count += length
137 def fast_start(datastream, outfile, offset=0):
139 Convert a Quicktime/MP4 file for streaming by moving the metadata to
140 the front of the file. This method writes a new file.
143 global count
144 count = 0
146 # Get the top level atom index
147 index = get_index(datastream)
148 # Make sure moov occurs AFTER mdat, otherwise no need to run!
149 if len(index) == 0 or index["moov"][0] < index["mdat"][0]:
150 logger.debug('mp4 already streamable -- copying')
151 datastream.seek(offset)
152 while True:
153 block = datastream.read(CHUNK_SIZE)
154 if not block:
155 break
156 outfile.write(block)
157 return
159 # Read and fix moov
160 datastream.seek(index["moov"][0])
161 moov_size = index["moov"][1]
162 moov = StringIO(datastream.read(moov_size))
164 moov.seek(8)
165 for atom_type in find_atoms(moov_size - 8, moov):
166 # Read either 32-bit or 64-bit offsets
167 ctype, csize = atom_type == "stco" and ("L", 4) or ("Q", 8)
169 # Get number of entries
170 version, entry_count = struct.unpack(">2L", moov.read(8))
172 logger.debug("Patching %s with %d entries" % (atom_type, entry_count))
174 # Read entries
175 entries = struct.unpack(">" + ctype * entry_count,
176 moov.read(csize * entry_count))
178 # Patch and write entries
179 moov.seek(-csize * entry_count, os.SEEK_CUR)
180 moov.write(struct.pack(">" + ctype * entry_count,
181 *[entry + moov_size for entry in entries]))
183 # Write ftype
184 datastream.seek(index["ftyp"][0])
185 output(outfile, offset, datastream.read(index["ftyp"][1]))
187 # Write moov
188 moov.seek(0)
189 output(outfile, offset, moov.read())
191 # Write the rest
192 atoms = [atom for atom in index.keys() if atom not in ["ftyp", "moov"]]
193 atoms.sort(lambda x, y: cmp(index[x][0], index[y][0]))
194 for atom in atoms:
195 start, size = index[atom]
196 datastream.seek(start)
198 # Write in chunks to not use too much memory
199 for x in range(size / CHUNK_SIZE):
200 output(outfile, offset, datastream.read(CHUNK_SIZE))
202 if size % CHUNK_SIZE:
203 output(outfile, offset, datastream.read(size % CHUNK_SIZE))