mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-01 00:43:37 +00:00
0d9fbc1ad7
This version of SickBeard uses both TVDB and TVRage to search and gather it's series data from allowing you to now have access to and download shows that you couldn't before because of being locked into only what TheTVDB had to offer. Also this edition is based off the code we used in our XEM editon so it does come with scene numbering support as well as all the other features our XEM edition has to offer. Please before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present, we HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk! Enjoy!
246 lines
8.9 KiB
Python
246 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
|
|
|