removed qtfaststart beacaue it is not used
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
from .qtfaststart import *
|
|
||||||
from .multi import *
|
from .multi import *
|
||||||
from .interface import *
|
from .interface import *
|
||||||
from .stg import *
|
from .stg import *
|
||||||
|
|||||||
@@ -1,319 +0,0 @@
|
|||||||
"""
|
|
||||||
This file contains a modified version of qtfaststart by qtfaststart
|
|
||||||
https://github.com/danielgtaylor/qtfaststart/tree/master
|
|
||||||
|
|
||||||
All credit goes to the original author.
|
|
||||||
Copyright (C) 2008 - 2013 Daniel G. Taylor <dan@programmer-art.org>
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
||||||
software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies
|
|
||||||
or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
||||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import struct
|
|
||||||
import collections
|
|
||||||
import io
|
|
||||||
|
|
||||||
# define error classes
|
|
||||||
class FastStartException(Exception):
|
|
||||||
"""
|
|
||||||
Raised when something bad happens during processing.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class FastStartSetupError(FastStartException):
|
|
||||||
"""
|
|
||||||
Rasised when asked to process a file that does not need processing
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MalformedFileError(FastStartException):
|
|
||||||
"""
|
|
||||||
Raised when the input file is setup in an unexpected way
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class UnsupportedFormatError(FastStartException):
|
|
||||||
"""
|
|
||||||
Raised when a movie file is recognized as a format not supported.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# define constants
|
|
||||||
CHUNK_SIZE = 8192
|
|
||||||
|
|
||||||
log = logging.getLogger("qtfaststart")
|
|
||||||
|
|
||||||
# Older versions of Python require this to be defined
|
|
||||||
if not hasattr(os, 'SEEK_CUR'):
|
|
||||||
os.SEEK_CUR = 1
|
|
||||||
|
|
||||||
Atom = collections.namedtuple('Atom', 'name position size')
|
|
||||||
|
|
||||||
def read_atom(datastream):
|
|
||||||
"""
|
|
||||||
Read an atom and return a tuple of (size, type) where size is the size
|
|
||||||
in bytes (including the 8 bytes already read) and type is a "fourcc"
|
|
||||||
like "ftyp" or "moov".
|
|
||||||
"""
|
|
||||||
size, type = struct.unpack(">L4s", datastream.read(8))
|
|
||||||
type = type.decode('ascii')
|
|
||||||
return size, type
|
|
||||||
|
|
||||||
|
|
||||||
def _read_atom_ex(datastream):
|
|
||||||
"""
|
|
||||||
Read an Atom from datastream
|
|
||||||
"""
|
|
||||||
pos = datastream.tell()
|
|
||||||
atom_size, atom_type = read_atom(datastream)
|
|
||||||
if atom_size == 1:
|
|
||||||
atom_size, = struct.unpack(">Q", datastream.read(8))
|
|
||||||
return Atom(atom_type, pos, atom_size)
|
|
||||||
|
|
||||||
|
|
||||||
def get_index(datastream):
|
|
||||||
"""
|
|
||||||
Return an index of top level atoms, their absolute byte-position in the
|
|
||||||
file and their size in a list:
|
|
||||||
|
|
||||||
index = [
|
|
||||||
("ftyp", 0, 24),
|
|
||||||
("moov", 25, 2658),
|
|
||||||
("free", 2683, 8),
|
|
||||||
...
|
|
||||||
]
|
|
||||||
|
|
||||||
The tuple elements will be in the order that they appear in the file.
|
|
||||||
"""
|
|
||||||
log.debug("Getting index of top level atoms...")
|
|
||||||
|
|
||||||
index = list(_read_atoms(datastream))
|
|
||||||
_ensure_valid_index(index)
|
|
||||||
|
|
||||||
return index
|
|
||||||
|
|
||||||
|
|
||||||
def _read_atoms(datastream):
|
|
||||||
"""
|
|
||||||
Read atoms until an error occurs
|
|
||||||
"""
|
|
||||||
while datastream:
|
|
||||||
try:
|
|
||||||
atom = _read_atom_ex(datastream)
|
|
||||||
log.debug("%s: %s" % (atom.name, atom.size))
|
|
||||||
except:
|
|
||||||
break
|
|
||||||
|
|
||||||
yield atom
|
|
||||||
|
|
||||||
if atom.size == 0:
|
|
||||||
if atom.name == "mdat":
|
|
||||||
# Some files may end in mdat with no size set, which generally
|
|
||||||
# means to seek to the end of the file. We can just stop indexing
|
|
||||||
# as no more entries will be found!
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Weird, but just continue to try to find more atoms
|
|
||||||
continue
|
|
||||||
|
|
||||||
datastream.seek(atom.position + atom.size)
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_valid_index(index):
|
|
||||||
"""
|
|
||||||
Ensure the minimum viable atoms are present in the index.
|
|
||||||
|
|
||||||
Raise FastStartException if not.
|
|
||||||
"""
|
|
||||||
top_level_atoms = set([item.name for item in index])
|
|
||||||
for key in ["moov", "mdat"]:
|
|
||||||
if key not in top_level_atoms:
|
|
||||||
log.error("%s atom not found, is this a valid MOV/MP4 file?" % key)
|
|
||||||
raise FastStartException()
|
|
||||||
|
|
||||||
|
|
||||||
def find_atoms(size, datastream):
|
|
||||||
"""
|
|
||||||
Compatibilty interface for _find_atoms_ex
|
|
||||||
"""
|
|
||||||
fake_parent = Atom('fake', datastream.tell()-8, size+8)
|
|
||||||
for atom in _find_atoms_ex(fake_parent, datastream):
|
|
||||||
yield atom.name
|
|
||||||
|
|
||||||
|
|
||||||
def _find_atoms_ex(parent_atom, datastream):
|
|
||||||
"""
|
|
||||||
Yield either "stco" or "co64" Atoms from datastream.
|
|
||||||
datastream will be 8 bytes into the stco or co64 atom when the value
|
|
||||||
is yielded.
|
|
||||||
|
|
||||||
It is assumed that datastream will be at the end of the atom after
|
|
||||||
the value has been yielded and processed.
|
|
||||||
|
|
||||||
parent_atom is the parent atom, a 'moov' or other ancestor of CO
|
|
||||||
atoms in the datastream.
|
|
||||||
"""
|
|
||||||
stop = parent_atom.position + parent_atom.size
|
|
||||||
|
|
||||||
while datastream.tell() < stop:
|
|
||||||
try:
|
|
||||||
atom = _read_atom_ex(datastream)
|
|
||||||
except:
|
|
||||||
log.exception("Error reading next atom!")
|
|
||||||
raise FastStartException()
|
|
||||||
|
|
||||||
if atom.name in ["trak", "mdia", "minf", "stbl"]:
|
|
||||||
# Known ancestor atom of stco or co64, search within it!
|
|
||||||
for res in _find_atoms_ex(atom, datastream):
|
|
||||||
yield res
|
|
||||||
elif atom.name in ["stco", "co64"]:
|
|
||||||
yield atom
|
|
||||||
else:
|
|
||||||
# Ignore this atom, seek to the end of it.
|
|
||||||
datastream.seek(atom.position + atom.size)
|
|
||||||
|
|
||||||
|
|
||||||
def process(infilename, limit=float('inf')):
|
|
||||||
"""
|
|
||||||
Convert a Quicktime/MP4 file for streaming by moving the metadata to
|
|
||||||
the front of the file. This method writes a new file.
|
|
||||||
|
|
||||||
If limit is set to something other than zero it will be used as the
|
|
||||||
number of bytes to write of the atoms following the moov atom. This
|
|
||||||
is very useful to create a small sample of a file with full headers,
|
|
||||||
which can then be used in bug reports and such.
|
|
||||||
"""
|
|
||||||
if isinstance(infilename, str):
|
|
||||||
datastream = open(infilename, "rb")
|
|
||||||
elif isinstance(infilename, bytes):
|
|
||||||
datastream = io.BytesIO(infilename)
|
|
||||||
else:
|
|
||||||
raise TypeError("infilename must be a filename, bytes or file-like object")
|
|
||||||
# Get the top level atom index
|
|
||||||
index = get_index(datastream)
|
|
||||||
|
|
||||||
mdat_pos = 999999
|
|
||||||
free_size = 0
|
|
||||||
|
|
||||||
# Make sure moov occurs AFTER mdat, otherwise no need to run!
|
|
||||||
for atom in index:
|
|
||||||
# The atoms are guaranteed to exist from get_index above!
|
|
||||||
if atom.name == "moov":
|
|
||||||
moov_atom = atom
|
|
||||||
moov_pos = atom.position
|
|
||||||
elif atom.name == "mdat":
|
|
||||||
mdat_pos = atom.position
|
|
||||||
elif atom.name == "free" and atom.position < mdat_pos:
|
|
||||||
# This free atom is before the mdat!
|
|
||||||
free_size += atom.size
|
|
||||||
log.info("Removing free atom at %d (%d bytes)" % (atom.position, atom.size))
|
|
||||||
elif atom.name == "\x00\x00\x00\x00" and atom.position < mdat_pos:
|
|
||||||
# This is some strange zero atom with incorrect size
|
|
||||||
free_size += 8
|
|
||||||
log.info("Removing strange zero atom at %s (8 bytes)" % atom.position)
|
|
||||||
|
|
||||||
# Offset to shift positions
|
|
||||||
offset = moov_atom.size - free_size
|
|
||||||
|
|
||||||
if moov_pos < mdat_pos:
|
|
||||||
# moov appears to be in the proper place, don't shift by moov size
|
|
||||||
offset -= moov_atom.size
|
|
||||||
if not free_size:
|
|
||||||
# No free atoms and moov is correct, we are done!
|
|
||||||
log.error("This file appears to already be setup for streaming!")
|
|
||||||
# Stupid hack to retrun the non-processed file:
|
|
||||||
if isinstance(infilename, str):
|
|
||||||
return open(infilename, "rb").read()
|
|
||||||
elif isinstance(infilename, bytes):
|
|
||||||
return io.BytesIO(infilename).read()
|
|
||||||
|
|
||||||
# Read and fix moov
|
|
||||||
moov = _patch_moov(datastream, moov_atom, offset)
|
|
||||||
|
|
||||||
log.info("Writing output...")
|
|
||||||
outfile = b''
|
|
||||||
|
|
||||||
# Write ftype
|
|
||||||
for atom in index:
|
|
||||||
if atom.name == "ftyp":
|
|
||||||
log.debug("Writing ftyp... (%d bytes)" % atom.size)
|
|
||||||
datastream.seek(atom.position)
|
|
||||||
outfile += datastream.read(atom.size)
|
|
||||||
|
|
||||||
# Write moov
|
|
||||||
_bytes = moov.getvalue()
|
|
||||||
log.debug("Writing moov... (%d bytes)" % len(_bytes))
|
|
||||||
outfile += _bytes
|
|
||||||
|
|
||||||
# Write the rest
|
|
||||||
atoms = [item for item in index if item.name not in ["ftyp", "moov", "free"]]
|
|
||||||
for atom in atoms:
|
|
||||||
log.debug("Writing %s... (%d bytes)" % (atom.name, atom.size))
|
|
||||||
datastream.seek(atom.position)
|
|
||||||
|
|
||||||
# for compatability, allow '0' to mean no limit
|
|
||||||
cur_limit = limit or float('inf')
|
|
||||||
cur_limit = min(cur_limit, atom.size)
|
|
||||||
|
|
||||||
for chunk in get_chunks(datastream, CHUNK_SIZE, cur_limit):
|
|
||||||
outfile += chunk
|
|
||||||
|
|
||||||
return outfile
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_moov(datastream, atom, offset):
|
|
||||||
datastream.seek(atom.position)
|
|
||||||
moov = io.BytesIO(datastream.read(atom.size))
|
|
||||||
|
|
||||||
# reload the atom from the fixed stream
|
|
||||||
atom = _read_atom_ex(moov)
|
|
||||||
|
|
||||||
for atom in _find_atoms_ex(atom, moov):
|
|
||||||
# Read either 32-bit or 64-bit offsets
|
|
||||||
ctype, csize = dict(
|
|
||||||
stco=('L', 4),
|
|
||||||
co64=('Q', 8),
|
|
||||||
)[atom.name]
|
|
||||||
|
|
||||||
# Get number of entries
|
|
||||||
version, entry_count = struct.unpack(">2L", moov.read(8))
|
|
||||||
|
|
||||||
log.info("Patching %s with %d entries" % (atom.name, entry_count))
|
|
||||||
|
|
||||||
entries_pos = moov.tell()
|
|
||||||
|
|
||||||
struct_fmt = ">%(entry_count)s%(ctype)s" % vars()
|
|
||||||
|
|
||||||
# Read entries
|
|
||||||
entries = struct.unpack(struct_fmt, moov.read(csize * entry_count))
|
|
||||||
|
|
||||||
# Patch and write entries
|
|
||||||
offset_entries = [entry + offset for entry in entries]
|
|
||||||
moov.seek(entries_pos)
|
|
||||||
moov.write(struct.pack(struct_fmt, *offset_entries))
|
|
||||||
return moov
|
|
||||||
|
|
||||||
def get_chunks(stream, chunk_size, limit):
|
|
||||||
remaining = limit
|
|
||||||
while remaining:
|
|
||||||
chunk = stream.read(min(remaining, chunk_size))
|
|
||||||
if not chunk:
|
|
||||||
return
|
|
||||||
remaining -= len(chunk)
|
|
||||||
yield chunk
|
|
||||||
Reference in New Issue
Block a user