SickRage/lib/hachoir_parser/video/mov.py

247 lines
8.9 KiB
Python

"""
Apple Quicktime Movie (file extension ".mov") parser.
Documents:
- Parsing and Writing QuickTime Files in Java (by Chris Adamson, 02/19/2003)
http://www.onjava.com/pub/a/onjava/2003/02/19/qt_file_format.html
- QuickTime File Format (official technical reference)
http://developer.apple.com/documentation/QuickTime/QTFF/qtff.pdf
- Apple QuickTime:
http://wiki.multimedia.cx/index.php?title=Apple_QuickTime
- File type (ftyp):
http://www.ftyps.com/
Author: Victor Stinner
Creation: 2 august 2006
"""
from lib.hachoir_parser import Parser
from lib.hachoir_core.field import (ParserError, FieldSet, MissingField,
UInt8, Int16, UInt16, UInt32, TimestampMac32,
String, PascalString8, CString,
RawBytes, PaddingBytes)
from lib.hachoir_core.endian import BIG_ENDIAN
from lib.hachoir_core.text_handler import textHandler, hexadecimal
class QTFloat32(FieldSet):
static_size = 32
def createFields(self):
yield Int16(self, "int_part")
yield UInt16(self, "float_part")
def createValue(self):
return self["int_part"].value + float(self["float_part"].value) / 65535
def createDescription(self):
return str(self.value)
class AtomList(FieldSet):
def createFields(self):
while not self.eof:
yield Atom(self, "atom[]")
class TrackHeader(FieldSet):
def createFields(self):
yield textHandler(UInt8(self, "version"), hexadecimal)
# TODO: sum of :
# TrackEnabled = 1;
# TrackInMovie = 2;
# TrackInPreview = 4;
# TrackInPoster = 8
yield RawBytes(self, "flags", 3)
yield TimestampMac32(self, "creation_date")
yield TimestampMac32(self, "lastmod_date")
yield UInt32(self, "track_id")
yield PaddingBytes(self, "reserved[]", 8)
yield UInt32(self, "duration")
yield PaddingBytes(self, "reserved[]", 8)
yield Int16(self, "video_layer", "Middle is 0, negative in front")
yield PaddingBytes(self, "other", 2)
yield QTFloat32(self, "geom_a", "Width scale")
yield QTFloat32(self, "geom_b", "Width rotate")
yield QTFloat32(self, "geom_u", "Width angle")
yield QTFloat32(self, "geom_c", "Height rotate")
yield QTFloat32(self, "geom_d", "Height scale")
yield QTFloat32(self, "geom_v", "Height angle")
yield QTFloat32(self, "geom_x", "Position X")
yield QTFloat32(self, "geom_y", "Position Y")
yield QTFloat32(self, "geom_w", "Divider scale")
yield QTFloat32(self, "frame_size_width")
yield QTFloat32(self, "frame_size_height")
class HDLR(FieldSet):
def createFields(self):
yield textHandler(UInt8(self, "version"), hexadecimal)
yield RawBytes(self, "flags", 3)
yield String(self, "subtype", 8)
yield String(self, "manufacturer", 4)
yield UInt32(self, "res_flags")
yield UInt32(self, "res_flags_mask")
if self.root.is_mpeg4:
yield CString(self, "name")
else:
yield PascalString8(self, "name")
class MediaHeader(FieldSet):
def createFields(self):
yield textHandler(UInt8(self, "version"), hexadecimal)
yield RawBytes(self, "flags", 3)
yield TimestampMac32(self, "creation_date")
yield TimestampMac32(self, "lastmod_date")
yield UInt32(self, "time_scale")
yield UInt32(self, "duration")
yield UInt16(self, "mac_lang")
yield Int16(self, "quality")
class ELST(FieldSet):
def createFields(self):
yield textHandler(UInt8(self, "version"), hexadecimal)
yield RawBytes(self, "flags", 3)
yield UInt32(self, "nb_edits")
yield UInt32(self, "length")
yield UInt32(self, "start")
yield QTFloat32(self, "playback_speed")
class Load(FieldSet):
def createFields(self):
yield UInt32(self, "start")
yield UInt32(self, "length")
yield UInt32(self, "flags") # PreloadAlways = 1 or TrackEnabledPreload = 2
yield UInt32(self, "hints") # KeepInBuffer = 0x00000004; HighQuality = 0x00000100; SingleFieldVideo = 0x00100000
class MovieHeader(FieldSet):
def createFields(self):
yield textHandler(UInt8(self, "version"), hexadecimal)
yield RawBytes(self, "flags", 3)
yield TimestampMac32(self, "creation_date")
yield TimestampMac32(self, "lastmod_date")
yield UInt32(self, "time_scale")
yield UInt32(self, "duration")
yield QTFloat32(self, "play_speed")
yield UInt16(self, "volume")
yield PaddingBytes(self, "reserved[]", 10)
yield QTFloat32(self, "geom_a", "Width scale")
yield QTFloat32(self, "geom_b", "Width rotate")
yield QTFloat32(self, "geom_u", "Width angle")
yield QTFloat32(self, "geom_c", "Height rotate")
yield QTFloat32(self, "geom_d", "Height scale")
yield QTFloat32(self, "geom_v", "Height angle")
yield QTFloat32(self, "geom_x", "Position X")
yield QTFloat32(self, "geom_y", "Position Y")
yield QTFloat32(self, "geom_w", "Divider scale")
yield UInt32(self, "preview_start")
yield UInt32(self, "preview_length")
yield UInt32(self, "still_poster")
yield UInt32(self, "sel_start")
yield UInt32(self, "sel_length")
yield UInt32(self, "current_time")
yield UInt32(self, "next_track")
class FileType(FieldSet):
def createFields(self):
yield String(self, "brand", 4, "Major brand")
yield UInt32(self, "version", "Version")
while not self.eof:
yield String(self, "compat_brand[]", 4, "Compatible brand")
class Atom(FieldSet):
tag_info = {
# TODO: Use dictionnary of dictionnary, like Matroska parser does
# "elst" is a child of "edts", but not of "moov" for example
"moov": (AtomList, "movie", "Movie"),
"trak": (AtomList, "track", "Track"),
"mdia": (AtomList, "media", "Media"),
"edts": (AtomList, "edts", ""),
"minf": (AtomList, "minf", ""),
"stbl": (AtomList, "stbl", ""),
"dinf": (AtomList, "dinf", ""),
"elst": (ELST, "edts", ""),
"tkhd": (TrackHeader, "track_hdr", "Track header"),
"hdlr": (HDLR, "hdlr", ""),
"mdhd": (MediaHeader, "media_hdr", "Media header"),
"load": (Load, "load", ""),
"mvhd": (MovieHeader, "movie_hdr", "Movie header"),
"ftyp": (FileType, "file_type", "File type"),
}
tag_handler = [ item[0] for item in tag_info ]
tag_desc = [ item[1] for item in tag_info ]
def createFields(self):
yield UInt32(self, "size")
yield String(self, "tag", 4)
size = self["size"].value
if size == 1:
raise ParserError("Extended size is not supported!")
#yield UInt64(self, "size64")
size = self["size64"].value
elif size == 0:
#size = (self.root.size - self.root.current_size - self.current_size) / 8
if self._size is None:
size = (self.parent.size - self.current_size) / 8 - 8
else:
size = (self.size - self.current_size) / 8
else:
size = size - 8
if 0 < size:
tag = self["tag"].value
if tag in self.tag_info:
handler, name, desc = self.tag_info[tag]
yield handler(self, name, desc, size=size*8)
else:
yield RawBytes(self, "data", size)
def createDescription(self):
return "Atom: %s" % self["tag"].value
class MovFile(Parser):
PARSER_TAGS = {
"id": "mov",
"category": "video",
"file_ext": ("mov", "qt", "mp4", "m4v", "m4a", "m4p", "m4b"),
"mime": (u"video/quicktime", u'video/mp4'),
"min_size": 8*8,
"magic": (("moov", 4*8),),
"description": "Apple QuickTime movie"
}
BRANDS = {
# File type brand => MIME type
'mp41': u'video/mp4',
'mp42': u'video/mp4',
}
endian = BIG_ENDIAN
def __init__(self, *args, **kw):
Parser.__init__(self, *args, **kw)
self.is_mpeg4 = False
def validate(self):
# TODO: Write better code, erk!
size = self.stream.readBits(0, 32, self.endian)
if size < 8:
return "Invalid first atom size"
tag = self.stream.readBytes(4*8, 4)
return tag in ("ftyp", "moov", "free")
def createFields(self):
while not self.eof:
yield Atom(self, "atom[]")
def createMimeType(self):
first = self[0]
try:
# Read brands in the file type
if first['tag'].value != "ftyp":
return None
file_type = first["file_type"]
brand = file_type["brand"].value
if brand in self.BRANDS:
return self.BRANDS[brand]
for field in file_type.array("compat_brand"):
brand = field.value
if brand in self.BRANDS:
return self.BRANDS[brand]
except MissingField:
pass
return None