diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/filetype/types')
10 files changed, 2078 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/filetype/types/__init__.py b/.venv/lib/python3.12/site-packages/filetype/types/__init__.py new file mode 100644 index 00000000..7d4daed0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/__init__.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from . import archive +from . import audio +from . import application +from . import document +from . import font +from . import image +from . import video +from .base import Type # noqa + +# Supported image types +IMAGE = ( + image.Dwg(), + image.Xcf(), + image.Jpeg(), + image.Jpx(), + image.Apng(), + image.Png(), + image.Gif(), + image.Webp(), + image.Tiff(), + image.Cr2(), + image.Bmp(), + image.Jxr(), + image.Psd(), + image.Ico(), + image.Heic(), + image.Dcm(), + image.Avif(), +) + +# Supported video types +VIDEO = ( + video.M3gp(), + video.Mp4(), + video.M4v(), + video.Mkv(), + video.Mov(), + video.Avi(), + video.Wmv(), + video.Mpeg(), + video.Webm(), + video.Flv(), +) + +# Supported audio types +AUDIO = ( + audio.Aac(), + audio.Midi(), + audio.Mp3(), + audio.M4a(), + audio.Ogg(), + audio.Flac(), + audio.Wav(), + audio.Amr(), + audio.Aiff(), +) + +# Supported font types +FONT = (font.Woff(), font.Woff2(), font.Ttf(), font.Otf()) + +# Supported archive container types +ARCHIVE = ( + archive.Br(), + archive.Rpm(), + archive.Dcm(), + archive.Epub(), + archive.Zip(), + archive.Tar(), + archive.Rar(), + archive.Gz(), + archive.Bz2(), + archive.SevenZ(), + archive.Pdf(), + archive.Exe(), + archive.Swf(), + archive.Rtf(), + archive.Nes(), + archive.Crx(), + archive.Cab(), + archive.Eot(), + archive.Ps(), + archive.Xz(), + archive.Sqlite(), + archive.Deb(), + archive.Ar(), + archive.Z(), + archive.Lzop(), + archive.Lz(), + archive.Elf(), + archive.Lz4(), + archive.Zstd(), +) + +# Supported archive container types +APPLICATION = ( + application.Wasm(), +) + +# Supported document types +DOCUMENT = ( + document.Doc(), + document.Docx(), + document.Odt(), + document.Xls(), + document.Xlsx(), + document.Ods(), + document.Ppt(), + document.Pptx(), + document.Odp(), +) + + +# Expose supported type matchers +TYPES = list(IMAGE + AUDIO + VIDEO + FONT + DOCUMENT + ARCHIVE + APPLICATION) diff --git a/.venv/lib/python3.12/site-packages/filetype/types/application.py b/.venv/lib/python3.12/site-packages/filetype/types/application.py new file mode 100644 index 00000000..6f02370c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/application.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type + + +class Wasm(Type): + """Implements the Wasm image type matcher.""" + + MIME = 'application/wasm' + EXTENSION = 'wasm' + + def __init__(self): + super(Wasm, self).__init__( + mime=Wasm.MIME, + extension=Wasm.EXTENSION + ) + + def match(self, buf): + return buf[:8] == bytearray([0x00, 0x61, 0x73, 0x6d, + 0x01, 0x00, 0x00, 0x00]) diff --git a/.venv/lib/python3.12/site-packages/filetype/types/archive.py b/.venv/lib/python3.12/site-packages/filetype/types/archive.py new file mode 100644 index 00000000..b942ef70 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/archive.py @@ -0,0 +1,687 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +import struct + +from .base import Type + + +class Epub(Type): + """ + Implements the EPUB archive type matcher. + """ + MIME = 'application/epub+zip' + EXTENSION = 'epub' + + def __init__(self): + super(Epub, self).__init__( + mime=Epub.MIME, + extension=Epub.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 57 and + buf[0] == 0x50 and buf[1] == 0x4B and + buf[2] == 0x3 and buf[3] == 0x4 and + buf[30] == 0x6D and buf[31] == 0x69 and + buf[32] == 0x6D and buf[33] == 0x65 and + buf[34] == 0x74 and buf[35] == 0x79 and + buf[36] == 0x70 and buf[37] == 0x65 and + buf[38] == 0x61 and buf[39] == 0x70 and + buf[40] == 0x70 and buf[41] == 0x6C and + buf[42] == 0x69 and buf[43] == 0x63 and + buf[44] == 0x61 and buf[45] == 0x74 and + buf[46] == 0x69 and buf[47] == 0x6F and + buf[48] == 0x6E and buf[49] == 0x2F and + buf[50] == 0x65 and buf[51] == 0x70 and + buf[52] == 0x75 and buf[53] == 0x62 and + buf[54] == 0x2B and buf[55] == 0x7A and + buf[56] == 0x69 and buf[57] == 0x70) + + +class Zip(Type): + """ + Implements the Zip archive type matcher. + """ + MIME = 'application/zip' + EXTENSION = 'zip' + + def __init__(self): + super(Zip, self).__init__( + mime=Zip.MIME, + extension=Zip.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x50 and buf[1] == 0x4B and + (buf[2] == 0x3 or buf[2] == 0x5 or + buf[2] == 0x7) and + (buf[3] == 0x4 or buf[3] == 0x6 or + buf[3] == 0x8)) + + +class Tar(Type): + """ + Implements the Tar archive type matcher. + """ + MIME = 'application/x-tar' + EXTENSION = 'tar' + + def __init__(self): + super(Tar, self).__init__( + mime=Tar.MIME, + extension=Tar.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 261 and + buf[257] == 0x75 and + buf[258] == 0x73 and + buf[259] == 0x74 and + buf[260] == 0x61 and + buf[261] == 0x72) + + +class Rar(Type): + """ + Implements the RAR archive type matcher. + """ + MIME = 'application/x-rar-compressed' + EXTENSION = 'rar' + + def __init__(self): + super(Rar, self).__init__( + mime=Rar.MIME, + extension=Rar.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 6 and + buf[0] == 0x52 and + buf[1] == 0x61 and + buf[2] == 0x72 and + buf[3] == 0x21 and + buf[4] == 0x1A and + buf[5] == 0x7 and + (buf[6] == 0x0 or + buf[6] == 0x1)) + + +class Gz(Type): + """ + Implements the GZ archive type matcher. + """ + MIME = 'application/gzip' + EXTENSION = 'gz' + + def __init__(self): + super(Gz, self).__init__( + mime=Gz.MIME, + extension=Gz.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 2 and + buf[0] == 0x1F and + buf[1] == 0x8B and + buf[2] == 0x8) + + +class Bz2(Type): + """ + Implements the BZ2 archive type matcher. + """ + MIME = 'application/x-bzip2' + EXTENSION = 'bz2' + + def __init__(self): + super(Bz2, self).__init__( + mime=Bz2.MIME, + extension=Bz2.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 2 and + buf[0] == 0x42 and + buf[1] == 0x5A and + buf[2] == 0x68) + + +class SevenZ(Type): + """ + Implements the SevenZ (7z) archive type matcher. + """ + MIME = 'application/x-7z-compressed' + EXTENSION = '7z' + + def __init__(self): + super(SevenZ, self).__init__( + mime=SevenZ.MIME, + extension=SevenZ.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 5 and + buf[0] == 0x37 and + buf[1] == 0x7A and + buf[2] == 0xBC and + buf[3] == 0xAF and + buf[4] == 0x27 and + buf[5] == 0x1C) + + +class Pdf(Type): + """ + Implements the PDF archive type matcher. + """ + MIME = 'application/pdf' + EXTENSION = 'pdf' + + def __init__(self): + super(Pdf, self).__init__( + mime=Pdf.MIME, + extension=Pdf.EXTENSION + ) + + def match(self, buf): + # Detect BOM and skip first 3 bytes + if (len(buf) > 3 and + buf[0] == 0xEF and + buf[1] == 0xBB and + buf[2] == 0xBF): # noqa E129 + buf = buf[3:] + + return (len(buf) > 3 and + buf[0] == 0x25 and + buf[1] == 0x50 and + buf[2] == 0x44 and + buf[3] == 0x46) + + +class Exe(Type): + """ + Implements the EXE archive type matcher. + """ + MIME = 'application/x-msdownload' + EXTENSION = 'exe' + + def __init__(self): + super(Exe, self).__init__( + mime=Exe.MIME, + extension=Exe.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 1 and + buf[0] == 0x4D and + buf[1] == 0x5A) + + +class Swf(Type): + """ + Implements the SWF archive type matcher. + """ + MIME = 'application/x-shockwave-flash' + EXTENSION = 'swf' + + def __init__(self): + super(Swf, self).__init__( + mime=Swf.MIME, + extension=Swf.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 2 and + (buf[0] == 0x43 or + buf[0] == 0x46) and + buf[1] == 0x57 and + buf[2] == 0x53) + + +class Rtf(Type): + """ + Implements the RTF archive type matcher. + """ + MIME = 'application/rtf' + EXTENSION = 'rtf' + + def __init__(self): + super(Rtf, self).__init__( + mime=Rtf.MIME, + extension=Rtf.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 4 and + buf[0] == 0x7B and + buf[1] == 0x5C and + buf[2] == 0x72 and + buf[3] == 0x74 and + buf[4] == 0x66) + + +class Nes(Type): + """ + Implements the NES archive type matcher. + """ + MIME = 'application/x-nintendo-nes-rom' + EXTENSION = 'nes' + + def __init__(self): + super(Nes, self).__init__( + mime=Nes.MIME, + extension=Nes.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x4E and + buf[1] == 0x45 and + buf[2] == 0x53 and + buf[3] == 0x1A) + + +class Crx(Type): + """ + Implements the CRX archive type matcher. + """ + MIME = 'application/x-google-chrome-extension' + EXTENSION = 'crx' + + def __init__(self): + super(Crx, self).__init__( + mime=Crx.MIME, + extension=Crx.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x43 and + buf[1] == 0x72 and + buf[2] == 0x32 and + buf[3] == 0x34) + + +class Cab(Type): + """ + Implements the CAB archive type matcher. + """ + MIME = 'application/vnd.ms-cab-compressed' + EXTENSION = 'cab' + + def __init__(self): + super(Cab, self).__init__( + mime=Cab.MIME, + extension=Cab.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + ((buf[0] == 0x4D and + buf[1] == 0x53 and + buf[2] == 0x43 and + buf[3] == 0x46) or + (buf[0] == 0x49 and + buf[1] == 0x53 and + buf[2] == 0x63 and + buf[3] == 0x28))) + + +class Eot(Type): + """ + Implements the EOT archive type matcher. + """ + MIME = 'application/octet-stream' + EXTENSION = 'eot' + + def __init__(self): + super(Eot, self).__init__( + mime=Eot.MIME, + extension=Eot.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 35 and + buf[34] == 0x4C and + buf[35] == 0x50 and + ((buf[8] == 0x02 and + buf[9] == 0x00 and + buf[10] == 0x01) or + (buf[8] == 0x01 and + buf[9] == 0x00 and + buf[10] == 0x00) or + (buf[8] == 0x02 and + buf[9] == 0x00 and + buf[10] == 0x02))) + + +class Ps(Type): + """ + Implements the PS archive type matcher. + """ + MIME = 'application/postscript' + EXTENSION = 'ps' + + def __init__(self): + super(Ps, self).__init__( + mime=Ps.MIME, + extension=Ps.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 1 and + buf[0] == 0x25 and + buf[1] == 0x21) + + +class Xz(Type): + """ + Implements the XS archive type matcher. + """ + MIME = 'application/x-xz' + EXTENSION = 'xz' + + def __init__(self): + super(Xz, self).__init__( + mime=Xz.MIME, + extension=Xz.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 5 and + buf[0] == 0xFD and + buf[1] == 0x37 and + buf[2] == 0x7A and + buf[3] == 0x58 and + buf[4] == 0x5A and + buf[5] == 0x00) + + +class Sqlite(Type): + """ + Implements the Sqlite DB archive type matcher. + """ + MIME = 'application/x-sqlite3' + EXTENSION = 'sqlite' + + def __init__(self): + super(Sqlite, self).__init__( + mime=Sqlite.MIME, + extension=Sqlite.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x53 and + buf[1] == 0x51 and + buf[2] == 0x4C and + buf[3] == 0x69) + + +class Deb(Type): + """ + Implements the DEB archive type matcher. + """ + MIME = 'application/x-deb' + EXTENSION = 'deb' + + def __init__(self): + super(Deb, self).__init__( + mime=Deb.MIME, + extension=Deb.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 20 and + buf[0] == 0x21 and + buf[1] == 0x3C and + buf[2] == 0x61 and + buf[3] == 0x72 and + buf[4] == 0x63 and + buf[5] == 0x68 and + buf[6] == 0x3E and + buf[7] == 0x0A and + buf[8] == 0x64 and + buf[9] == 0x65 and + buf[10] == 0x62 and + buf[11] == 0x69 and + buf[12] == 0x61 and + buf[13] == 0x6E and + buf[14] == 0x2D and + buf[15] == 0x62 and + buf[16] == 0x69 and + buf[17] == 0x6E and + buf[18] == 0x61 and + buf[19] == 0x72 and + buf[20] == 0x79) + + +class Ar(Type): + """ + Implements the AR archive type matcher. + """ + MIME = 'application/x-unix-archive' + EXTENSION = 'ar' + + def __init__(self): + super(Ar, self).__init__( + mime=Ar.MIME, + extension=Ar.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 6 and + buf[0] == 0x21 and + buf[1] == 0x3C and + buf[2] == 0x61 and + buf[3] == 0x72 and + buf[4] == 0x63 and + buf[5] == 0x68 and + buf[6] == 0x3E) + + +class Z(Type): + """ + Implements the Z archive type matcher. + """ + MIME = 'application/x-compress' + EXTENSION = 'Z' + + def __init__(self): + super(Z, self).__init__( + mime=Z.MIME, + extension=Z.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 1 and + ((buf[0] == 0x1F and + buf[1] == 0xA0) or + (buf[0] == 0x1F and + buf[1] == 0x9D))) + + +class Lzop(Type): + """ + Implements the Lzop archive type matcher. + """ + MIME = 'application/x-lzop' + EXTENSION = 'lzo' + + def __init__(self): + super(Lzop, self).__init__( + mime=Lzop.MIME, + extension=Lzop.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 7 and + buf[0] == 0x89 and + buf[1] == 0x4C and + buf[2] == 0x5A and + buf[3] == 0x4F and + buf[4] == 0x00 and + buf[5] == 0x0D and + buf[6] == 0x0A and + buf[7] == 0x1A) + + +class Lz(Type): + """ + Implements the Lz archive type matcher. + """ + MIME = 'application/x-lzip' + EXTENSION = 'lz' + + def __init__(self): + super(Lz, self).__init__( + mime=Lz.MIME, + extension=Lz.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x4C and + buf[1] == 0x5A and + buf[2] == 0x49 and + buf[3] == 0x50) + + +class Elf(Type): + """ + Implements the Elf archive type matcher + """ + MIME = 'application/x-executable' + EXTENSION = 'elf' + + def __init__(self): + super(Elf, self).__init__( + mime=Elf.MIME, + extension=Elf.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 52 and + buf[0] == 0x7F and + buf[1] == 0x45 and + buf[2] == 0x4C and + buf[3] == 0x46) + + +class Lz4(Type): + """ + Implements the Lz4 archive type matcher. + """ + MIME = 'application/x-lz4' + EXTENSION = 'lz4' + + def __init__(self): + super(Lz4, self).__init__( + mime=Lz4.MIME, + extension=Lz4.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x04 and + buf[1] == 0x22 and + buf[2] == 0x4D and + buf[3] == 0x18) + + +class Br(Type): + """Implements the Br image type matcher.""" + + MIME = 'application/x-brotli' + EXTENSION = 'br' + + def __init__(self): + super(Br, self).__init__( + mime=Br.MIME, + extension=Br.EXTENSION + ) + + def match(self, buf): + return buf[:4] == bytearray([0xce, 0xb2, 0xcf, 0x81]) + + +class Dcm(Type): + """Implements the Dcm image type matcher.""" + + MIME = 'application/dicom' + EXTENSION = 'dcm' + + def __init__(self): + super(Dcm, self).__init__( + mime=Dcm.MIME, + extension=Dcm.EXTENSION + ) + + def match(self, buf): + return buf[128:131] == bytearray([0x44, 0x49, 0x43, 0x4d]) + + +class Rpm(Type): + """Implements the Rpm image type matcher.""" + + MIME = 'application/x-rpm' + EXTENSION = 'rpm' + + def __init__(self): + super(Rpm, self).__init__( + mime=Rpm.MIME, + extension=Rpm.EXTENSION + ) + + def match(self, buf): + return buf[:4] == bytearray([0xed, 0xab, 0xee, 0xdb]) + + +class Zstd(Type): + """ + Implements the Zstd archive type matcher. + https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md + """ + MIME = 'application/zstd' + EXTENSION = 'zst' + MAGIC_SKIPPABLE_START = 0x184D2A50 + MAGIC_SKIPPABLE_MASK = 0xFFFFFFF0 + + def __init__(self): + super(Zstd, self).__init__( + mime=Zstd.MIME, + extension=Zstd.EXTENSION + ) + + @staticmethod + def _to_little_endian_int(buf): + # return int.from_bytes(buf, byteorder='little') + return struct.unpack('<L', buf)[0] + + def match(self, buf): + # Zstandard compressed data is made of one or more frames. + # There are two frame formats defined by Zstandard: + # Zstandard frames and Skippable frames. + # See more details from + # https://tools.ietf.org/id/draft-kucherawy-dispatch-zstd-00.html#rfc.section.2 + is_zstd = ( + len(buf) > 3 and + buf[0] in (0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28) and + buf[1] == 0xb5 and + buf[2] == 0x2f and + buf[3] == 0xfd) + if is_zstd: + return True + # skippable frames + if len(buf) < 8: + return False + magic = self._to_little_endian_int(buf[:4]) & Zstd.MAGIC_SKIPPABLE_MASK + if magic == Zstd.MAGIC_SKIPPABLE_START: + user_data_len = self._to_little_endian_int(buf[4:8]) + if len(buf) < 8 + user_data_len: + return False + next_frame = buf[8 + user_data_len:] + return self.match(next_frame) + return False diff --git a/.venv/lib/python3.12/site-packages/filetype/types/audio.py b/.venv/lib/python3.12/site-packages/filetype/types/audio.py new file mode 100644 index 00000000..3d1f20c3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/audio.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type + + +class Midi(Type): + """ + Implements the Midi audio type matcher. + """ + MIME = 'audio/midi' + EXTENSION = 'midi' + + def __init__(self): + super(Midi, self).__init__( + mime=Midi.MIME, + extension=Midi.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x4D and + buf[1] == 0x54 and + buf[2] == 0x68 and + buf[3] == 0x64) + + +class Mp3(Type): + """ + Implements the MP3 audio type matcher. + """ + MIME = 'audio/mpeg' + EXTENSION = 'mp3' + + def __init__(self): + super(Mp3, self).__init__( + mime=Mp3.MIME, + extension=Mp3.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 2 and + ((buf[0] == 0x49 and + buf[1] == 0x44 and + buf[2] == 0x33) or + (buf[0] == 0xFF and + buf[1] == 0xF2) or + (buf[0] == 0xFF and + buf[1] == 0xF3) or + (buf[0] == 0xFF and + buf[1] == 0xFB))) + + +class M4a(Type): + """ + Implements the M4A audio type matcher. + """ + MIME = 'audio/mp4' + EXTENSION = 'm4a' + + def __init__(self): + super(M4a, self).__init__( + mime=M4a.MIME, + extension=M4a.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 10 and + ((buf[4] == 0x66 and + buf[5] == 0x74 and + buf[6] == 0x79 and + buf[7] == 0x70 and + buf[8] == 0x4D and + buf[9] == 0x34 and + buf[10] == 0x41) or + (buf[0] == 0x4D and + buf[1] == 0x34 and + buf[2] == 0x41 and + buf[3] == 0x20))) + + +class Ogg(Type): + """ + Implements the OGG audio type matcher. + """ + MIME = 'audio/ogg' + EXTENSION = 'ogg' + + def __init__(self): + super(Ogg, self).__init__( + mime=Ogg.MIME, + extension=Ogg.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x4F and + buf[1] == 0x67 and + buf[2] == 0x67 and + buf[3] == 0x53) + + +class Flac(Type): + """ + Implements the FLAC audio type matcher. + """ + MIME = 'audio/x-flac' + EXTENSION = 'flac' + + def __init__(self): + super(Flac, self).__init__( + mime=Flac.MIME, + extension=Flac.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x66 and + buf[1] == 0x4C and + buf[2] == 0x61 and + buf[3] == 0x43) + + +class Wav(Type): + """ + Implements the WAV audio type matcher. + """ + MIME = 'audio/x-wav' + EXTENSION = 'wav' + + def __init__(self): + super(Wav, self).__init__( + mime=Wav.MIME, + extension=Wav.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 11 and + buf[0] == 0x52 and + buf[1] == 0x49 and + buf[2] == 0x46 and + buf[3] == 0x46 and + buf[8] == 0x57 and + buf[9] == 0x41 and + buf[10] == 0x56 and + buf[11] == 0x45) + + +class Amr(Type): + """ + Implements the AMR audio type matcher. + """ + MIME = 'audio/amr' + EXTENSION = 'amr' + + def __init__(self): + super(Amr, self).__init__( + mime=Amr.MIME, + extension=Amr.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 11 and + buf[0] == 0x23 and + buf[1] == 0x21 and + buf[2] == 0x41 and + buf[3] == 0x4D and + buf[4] == 0x52 and + buf[5] == 0x0A) + + +class Aac(Type): + """Implements the Aac audio type matcher.""" + + MIME = 'audio/aac' + EXTENSION = 'aac' + + def __init__(self): + super(Aac, self).__init__( + mime=Aac.MIME, + extension=Aac.EXTENSION + ) + + def match(self, buf): + return (buf[:2] == bytearray([0xff, 0xf1]) or + buf[:2] == bytearray([0xff, 0xf9])) + + +class Aiff(Type): + """ + Implements the AIFF audio type matcher. + """ + MIME = 'audio/x-aiff' + EXTENSION = 'aiff' + + def __init__(self): + super(Aiff, self).__init__( + mime=Aiff.MIME, + extension=Aiff.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 11 and + buf[0] == 0x46 and + buf[1] == 0x4F and + buf[2] == 0x52 and + buf[3] == 0x4D and + buf[8] == 0x41 and + buf[9] == 0x49 and + buf[10] == 0x46 and + buf[11] == 0x46) diff --git a/.venv/lib/python3.12/site-packages/filetype/types/base.py b/.venv/lib/python3.12/site-packages/filetype/types/base.py new file mode 100644 index 00000000..7c0c0d26 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/base.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + + +class Type(object): + """ + Represents the file type object inherited by + specific file type matchers. + Provides convenient accessor and helper methods. + """ + def __init__(self, mime, extension): + self.__mime = mime + self.__extension = extension + + @property + def mime(self): + return self.__mime + + @property + def extension(self): + return self.__extension + + def is_extension(self, extension): + return self.__extension is extension + + def is_mime(self, mime): + return self.__mime is mime + + def match(self, buf): + raise NotImplementedError diff --git a/.venv/lib/python3.12/site-packages/filetype/types/document.py b/.venv/lib/python3.12/site-packages/filetype/types/document.py new file mode 100644 index 00000000..9f57e98c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/document.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type + + +class ZippedDocumentBase(Type): + def match(self, buf): + # start by checking for ZIP local file header signature + idx = self.search_signature(buf, 0, 6000) + if idx != 0: + return + + return self.match_document(buf) + + def match_document(self, buf): + raise NotImplementedError + + def compare_bytes(self, buf, subslice, start_offset): + sl = len(subslice) + + if start_offset + sl > len(buf): + return False + + return buf[start_offset:start_offset + sl] == subslice + + def search_signature(self, buf, start, rangeNum): + signature = b"PK\x03\x04" + length = len(buf) + + end = start + rangeNum + end = length if end > length else end + + if start >= end: + return -1 + + try: + return buf.index(signature, start, end) + except ValueError: + return -1 + + +class OpenDocument(ZippedDocumentBase): + def match_document(self, buf): + # Check if first file in archive is the identifying file + if not self.compare_bytes(buf, b"mimetype", 0x1E): + return + + # Check content of mimetype file if it matches current mime + return self.compare_bytes(buf, bytes(self.mime, "ASCII"), 0x26) + + +class OfficeOpenXml(ZippedDocumentBase): + def match_document(self, buf): + # Check if first file in archive is the identifying file + ft = self.match_filename(buf, 0x1E) + if ft: + return ft + + # Otherwise check that the fist file is one of these + if ( + not self.compare_bytes(buf, b"[Content_Types].xml", 0x1E) + and not self.compare_bytes(buf, b"_rels/.rels", 0x1E) + and not self.compare_bytes(buf, b"docProps", 0x1E) + ): + return + + # Loop through next 3 files and check if they match + # NOTE: OpenOffice/Libreoffice orders ZIP entry differently, so check the 4th file + # https://github.com/h2non/filetype/blob/d730d98ad5c990883148485b6fd5adbdd378364a/matchers/document.go#L134 + idx = 0 + for i in range(4): + # Search for next file header + idx = self.search_signature(buf, idx + 4, 6000) + if idx == -1: + return + + # Filename is at file header + 30 + ft = self.match_filename(buf, idx + 30) + if ft: + return ft + + def match_filename(self, buf, offset): + if self.compare_bytes(buf, b"word/", offset): + return ( + self.mime + == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + if self.compare_bytes(buf, b"ppt/", offset): + return ( + self.mime + == "application/vnd.openxmlformats-officedocument.presentationml.presentation" + ) + if self.compare_bytes(buf, b"xl/", offset): + return ( + self.mime + == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) + + +class Doc(Type): + """ + Implements the Microsoft Word (Office 97-2003) document type matcher. + """ + + MIME = "application/msword" + EXTENSION = "doc" + + def __init__(self): + super(Doc, self).__init__(mime=Doc.MIME, extension=Doc.EXTENSION) + + def match(self, buf): + if len(buf) > 515 and buf[0:8] == b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1": + if buf[512:516] == b"\xEC\xA5\xC1\x00": + return True + if ( + len(buf) > 2142 + and b"\x00\x0A\x00\x00\x00MSWordDoc\x00\x10\x00\x00\x00Word.Document.8\x00\xF49\xB2q" + in buf[2075:2142] + ): + return True + + return False + + +class Docx(OfficeOpenXml): + """ + Implements the Microsoft Word OOXML (Office 2007+) document type matcher. + """ + + MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + EXTENSION = "docx" + + def __init__(self): + super(Docx, self).__init__(mime=Docx.MIME, extension=Docx.EXTENSION) + + +class Odt(OpenDocument): + """ + Implements the OpenDocument Text document type matcher. + """ + + MIME = "application/vnd.oasis.opendocument.text" + EXTENSION = "odt" + + def __init__(self): + super(Odt, self).__init__(mime=Odt.MIME, extension=Odt.EXTENSION) + + +class Xls(Type): + """ + Implements the Microsoft Excel (Office 97-2003) document type matcher. + """ + + MIME = "application/vnd.ms-excel" + EXTENSION = "xls" + + def __init__(self): + super(Xls, self).__init__(mime=Xls.MIME, extension=Xls.EXTENSION) + + def match(self, buf): + if len(buf) > 520 and buf[0:8] == b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1": + if buf[512:516] == b"\xFD\xFF\xFF\xFF" and ( + buf[518] == 0x00 or buf[518] == 0x02 + ): + return True + if buf[512:520] == b"\x09\x08\x10\x00\x00\x06\x05\x00": + return True + if ( + len(buf) > 2095 + and b"\xE2\x00\x00\x00\x5C\x00\x70\x00\x04\x00\x00Calc" + in buf[1568:2095] + ): + return True + + return False + + +class Xlsx(OfficeOpenXml): + """ + Implements the Microsoft Excel OOXML (Office 2007+) document type matcher. + """ + + MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + EXTENSION = "xlsx" + + def __init__(self): + super(Xlsx, self).__init__(mime=Xlsx.MIME, extension=Xlsx.EXTENSION) + + +class Ods(OpenDocument): + """ + Implements the OpenDocument Spreadsheet document type matcher. + """ + + MIME = "application/vnd.oasis.opendocument.spreadsheet" + EXTENSION = "ods" + + def __init__(self): + super(Ods, self).__init__(mime=Ods.MIME, extension=Ods.EXTENSION) + + +class Ppt(Type): + """ + Implements the Microsoft PowerPoint (Office 97-2003) document type matcher. + """ + + MIME = "application/vnd.ms-powerpoint" + EXTENSION = "ppt" + + def __init__(self): + super(Ppt, self).__init__(mime=Ppt.MIME, extension=Ppt.EXTENSION) + + def match(self, buf): + if len(buf) > 524 and buf[0:8] == b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1": + if buf[512:516] == b"\xA0\x46\x1D\xF0": + return True + if buf[512:516] == b"\x00\x6E\x1E\xF0": + return True + if buf[512:516] == b"\x0F\x00\xE8\x03": + return True + if buf[512:516] == b"\xFD\xFF\xFF\xFF" and buf[522:524] == b"\x00\x00": + return True + if ( + len(buf) > 2096 + and buf[2072:2096] + == b"\x00\xB9\x29\xE8\x11\x00\x00\x00MS PowerPoint 97" + ): + return True + + return False + + +class Pptx(OfficeOpenXml): + """ + Implements the Microsoft PowerPoint OOXML (Office 2007+) document type matcher. + """ + + MIME = "application/vnd.openxmlformats-officedocument.presentationml.presentation" + EXTENSION = "pptx" + + def __init__(self): + super(Pptx, self).__init__(mime=Pptx.MIME, extension=Pptx.EXTENSION) + + +class Odp(OpenDocument): + """ + Implements the OpenDocument Presentation document type matcher. + """ + + MIME = "application/vnd.oasis.opendocument.presentation" + EXTENSION = "odp" + + def __init__(self): + super(Odp, self).__init__(mime=Odp.MIME, extension=Odp.EXTENSION) diff --git a/.venv/lib/python3.12/site-packages/filetype/types/font.py b/.venv/lib/python3.12/site-packages/filetype/types/font.py new file mode 100644 index 00000000..461f5c44 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/font.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type + + +class Woff(Type): + """ + Implements the WOFF font type matcher. + """ + MIME = 'application/font-woff' + EXTENSION = 'woff' + + def __init__(self): + super(Woff, self).__init__( + mime=Woff.MIME, + extension=Woff.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 7 and + buf[0] == 0x77 and + buf[1] == 0x4F and + buf[2] == 0x46 and + buf[3] == 0x46 and + ((buf[4] == 0x00 and + buf[5] == 0x01 and + buf[6] == 0x00 and + buf[7] == 0x00) or + (buf[4] == 0x4F and + buf[5] == 0x54 and + buf[6] == 0x54 and + buf[7] == 0x4F) or + (buf[4] == 0x74 and + buf[5] == 0x72 and + buf[6] == 0x75 and + buf[7] == 0x65))) + + +class Woff2(Type): + """ + Implements the WOFF2 font type matcher. + """ + MIME = 'application/font-woff' + EXTENSION = 'woff2' + + def __init__(self): + super(Woff2, self).__init__( + mime=Woff2.MIME, + extension=Woff2.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 7 and + buf[0] == 0x77 and + buf[1] == 0x4F and + buf[2] == 0x46 and + buf[3] == 0x32 and + ((buf[4] == 0x00 and + buf[5] == 0x01 and + buf[6] == 0x00 and + buf[7] == 0x00) or + (buf[4] == 0x4F and + buf[5] == 0x54 and + buf[6] == 0x54 and + buf[7] == 0x4F) or + (buf[4] == 0x74 and + buf[5] == 0x72 and + buf[6] == 0x75 and + buf[7] == 0x65))) + + +class Ttf(Type): + """ + Implements the TTF font type matcher. + """ + MIME = 'application/font-sfnt' + EXTENSION = 'ttf' + + def __init__(self): + super(Ttf, self).__init__( + mime=Ttf.MIME, + extension=Ttf.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 4 and + buf[0] == 0x00 and + buf[1] == 0x01 and + buf[2] == 0x00 and + buf[3] == 0x00 and + buf[4] == 0x00) + + +class Otf(Type): + """ + Implements the OTF font type matcher. + """ + MIME = 'application/font-sfnt' + EXTENSION = 'otf' + + def __init__(self): + super(Otf, self).__init__( + mime=Otf.MIME, + extension=Otf.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 4 and + buf[0] == 0x4F and + buf[1] == 0x54 and + buf[2] == 0x54 and + buf[3] == 0x4F and + buf[4] == 0x00) diff --git a/.venv/lib/python3.12/site-packages/filetype/types/image.py b/.venv/lib/python3.12/site-packages/filetype/types/image.py new file mode 100644 index 00000000..2d4d269e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/image.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type +from .isobmff import IsoBmff + + +class Jpeg(Type): + """ + Implements the JPEG image type matcher. + """ + MIME = 'image/jpeg' + EXTENSION = 'jpg' + + def __init__(self): + super(Jpeg, self).__init__( + mime=Jpeg.MIME, + extension=Jpeg.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 2 and + buf[0] == 0xFF and + buf[1] == 0xD8 and + buf[2] == 0xFF) + + +class Jpx(Type): + """ + Implements the JPEG2000 image type matcher. + """ + + MIME = "image/jpx" + EXTENSION = "jpx" + + def __init__(self): + super(Jpx, self).__init__(mime=Jpx.MIME, extension=Jpx.EXTENSION) + + def match(self, buf): + return ( + len(buf) > 50 + and buf[0] == 0x00 + and buf[1] == 0x00 + and buf[2] == 0x00 + and buf[3] == 0x0C + and buf[16:24] == b"ftypjp2 " + ) + + +class Apng(Type): + """ + Implements the APNG image type matcher. + """ + MIME = 'image/apng' + EXTENSION = 'apng' + + def __init__(self): + super(Apng, self).__init__( + mime=Apng.MIME, + extension=Apng.EXTENSION + ) + + def match(self, buf): + if (len(buf) > 8 and + buf[:8] == bytearray([0x89, 0x50, 0x4e, 0x47, + 0x0d, 0x0a, 0x1a, 0x0a])): + # cursor in buf, skip already readed 8 bytes + i = 8 + while len(buf) > i: + data_length = int.from_bytes(buf[i:i+4], byteorder="big") + i += 4 + + chunk_type = buf[i:i+4].decode("ascii", errors='ignore') + i += 4 + + # acTL chunk in APNG should appears first than IDAT + # IEND is end of PNG + if (chunk_type == "IDAT" or chunk_type == "IEND"): + return False + elif (chunk_type == "acTL"): + return True + + # move to the next chunk by skipping data and crc (4 bytes) + i += data_length + 4 + + return False + + +class Png(Type): + """ + Implements the PNG image type matcher. + """ + MIME = 'image/png' + EXTENSION = 'png' + + def __init__(self): + super(Png, self).__init__( + mime=Png.MIME, + extension=Png.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x89 and + buf[1] == 0x50 and + buf[2] == 0x4E and + buf[3] == 0x47) + + +class Gif(Type): + """ + Implements the GIF image type matcher. + """ + MIME = 'image/gif' + EXTENSION = 'gif' + + def __init__(self): + super(Gif, self).__init__( + mime=Gif.MIME, + extension=Gif.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 2 and + buf[0] == 0x47 and + buf[1] == 0x49 and + buf[2] == 0x46) + + +class Webp(Type): + """ + Implements the WEBP image type matcher. + """ + MIME = 'image/webp' + EXTENSION = 'webp' + + def __init__(self): + super(Webp, self).__init__( + mime=Webp.MIME, + extension=Webp.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 13 and + buf[0] == 0x52 and + buf[1] == 0x49 and + buf[2] == 0x46 and + buf[3] == 0x46 and + buf[8] == 0x57 and + buf[9] == 0x45 and + buf[10] == 0x42 and + buf[11] == 0x50 and + buf[12] == 0x56 and + buf[13] == 0x50) + + +class Cr2(Type): + """ + Implements the CR2 image type matcher. + """ + MIME = 'image/x-canon-cr2' + EXTENSION = 'cr2' + + def __init__(self): + super(Cr2, self).__init__( + mime=Cr2.MIME, + extension=Cr2.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 9 and + ((buf[0] == 0x49 and buf[1] == 0x49 and + buf[2] == 0x2A and buf[3] == 0x0) or + (buf[0] == 0x4D and buf[1] == 0x4D and + buf[2] == 0x0 and buf[3] == 0x2A)) and + buf[8] == 0x43 and buf[9] == 0x52) + + +class Tiff(Type): + """ + Implements the TIFF image type matcher. + """ + MIME = 'image/tiff' + EXTENSION = 'tif' + + def __init__(self): + super(Tiff, self).__init__( + mime=Tiff.MIME, + extension=Tiff.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 9 and + ((buf[0] == 0x49 and buf[1] == 0x49 and + buf[2] == 0x2A and buf[3] == 0x0) or + (buf[0] == 0x4D and buf[1] == 0x4D and + buf[2] == 0x0 and buf[3] == 0x2A)) + and not (buf[8] == 0x43 and buf[9] == 0x52)) + + +class Bmp(Type): + """ + Implements the BMP image type matcher. + """ + MIME = 'image/bmp' + EXTENSION = 'bmp' + + def __init__(self): + super(Bmp, self).__init__( + mime=Bmp.MIME, + extension=Bmp.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 1 and + buf[0] == 0x42 and + buf[1] == 0x4D) + + +class Jxr(Type): + """ + Implements the JXR image type matcher. + """ + MIME = 'image/vnd.ms-photo' + EXTENSION = 'jxr' + + def __init__(self): + super(Jxr, self).__init__( + mime=Jxr.MIME, + extension=Jxr.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 2 and + buf[0] == 0x49 and + buf[1] == 0x49 and + buf[2] == 0xBC) + + +class Psd(Type): + """ + Implements the PSD image type matcher. + """ + MIME = 'image/vnd.adobe.photoshop' + EXTENSION = 'psd' + + def __init__(self): + super(Psd, self).__init__( + mime=Psd.MIME, + extension=Psd.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x38 and + buf[1] == 0x42 and + buf[2] == 0x50 and + buf[3] == 0x53) + + +class Ico(Type): + """ + Implements the ICO image type matcher. + """ + MIME = 'image/x-icon' + EXTENSION = 'ico' + + def __init__(self): + super(Ico, self).__init__( + mime=Ico.MIME, + extension=Ico.EXTENSION, + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x00 and + buf[1] == 0x00 and + buf[2] == 0x01 and + buf[3] == 0x00) + + +class Heic(IsoBmff): + """ + Implements the HEIC image type matcher. + """ + MIME = 'image/heic' + EXTENSION = 'heic' + + def __init__(self): + super(Heic, self).__init__( + mime=Heic.MIME, + extension=Heic.EXTENSION + ) + + def match(self, buf): + if not self._is_isobmff(buf): + return False + + major_brand, minor_version, compatible_brands = self._get_ftyp(buf) + if major_brand == 'heic': + return True + if major_brand in ['mif1', 'msf1'] and 'heic' in compatible_brands: + return True + return False + + +class Dcm(Type): + + MIME = 'application/dicom' + EXTENSION = 'dcm' + OFFSET = 128 + + def __init__(self): + super(Dcm, self).__init__( + mime=Dcm.MIME, + extension=Dcm.EXTENSION + ) + + def match(self, buf): + return (len(buf) > Dcm.OFFSET + 4 and + buf[Dcm.OFFSET + 0] == 0x44 and + buf[Dcm.OFFSET + 1] == 0x49 and + buf[Dcm.OFFSET + 2] == 0x43 and + buf[Dcm.OFFSET + 3] == 0x4D) + + +class Dwg(Type): + """Implements the Dwg image type matcher.""" + + MIME = 'image/vnd.dwg' + EXTENSION = 'dwg' + + def __init__(self): + super(Dwg, self).__init__( + mime=Dwg.MIME, + extension=Dwg.EXTENSION + ) + + def match(self, buf): + return buf[:4] == bytearray([0x41, 0x43, 0x31, 0x30]) + + +class Xcf(Type): + """Implements the Xcf image type matcher.""" + + MIME = 'image/x-xcf' + EXTENSION = 'xcf' + + def __init__(self): + super(Xcf, self).__init__( + mime=Xcf.MIME, + extension=Xcf.EXTENSION + ) + + def match(self, buf): + return buf[:10] == bytearray([0x67, 0x69, 0x6d, 0x70, 0x20, + 0x78, 0x63, 0x66, 0x20, 0x76]) + + +class Avif(IsoBmff): + """ + Implements the AVIF image type matcher. + """ + MIME = 'image/avif' + EXTENSION = 'avif' + + def __init__(self): + super(Avif, self).__init__( + mime=Avif.MIME, + extension=Avif.EXTENSION + ) + + def match(self, buf): + if not self._is_isobmff(buf): + return False + + major_brand, minor_version, compatible_brands = self._get_ftyp(buf) + if major_brand == 'avif': + return True + if major_brand in ['mif1', 'msf1'] and 'avif' in compatible_brands: + return True + return False diff --git a/.venv/lib/python3.12/site-packages/filetype/types/isobmff.py b/.venv/lib/python3.12/site-packages/filetype/types/isobmff.py new file mode 100644 index 00000000..2ac0ffe8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/isobmff.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +import codecs + +from .base import Type + + +class IsoBmff(Type): + """ + Implements the ISO-BMFF base type. + """ + def __init__(self, mime, extension): + super(IsoBmff, self).__init__( + mime=mime, + extension=extension + ) + + def _is_isobmff(self, buf): + if len(buf) < 16 or buf[4:8] != b'ftyp': + return False + if len(buf) < int(codecs.encode(buf[0:4], 'hex'), 16): + return False + return True + + def _get_ftyp(self, buf): + ftyp_len = int(codecs.encode(buf[0:4], 'hex'), 16) + major_brand = buf[8:12].decode(errors='ignore') + minor_version = int(codecs.encode(buf[12:16], 'hex'), 16) + compatible_brands = [] + for i in range(16, ftyp_len, 4): + compatible_brands.append(buf[i:i+4].decode(errors='ignore')) + + return major_brand, minor_version, compatible_brands diff --git a/.venv/lib/python3.12/site-packages/filetype/types/video.py b/.venv/lib/python3.12/site-packages/filetype/types/video.py new file mode 100644 index 00000000..336b2526 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/filetype/types/video.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +from .base import Type +from .isobmff import IsoBmff + + +class Mp4(IsoBmff): + """ + Implements the MP4 video type matcher. + """ + MIME = 'video/mp4' + EXTENSION = 'mp4' + + def __init__(self): + super(Mp4, self).__init__( + mime=Mp4.MIME, + extension=Mp4.EXTENSION + ) + + def match(self, buf): + if not self._is_isobmff(buf): + return False + + major_brand, minor_version, compatible_brands = self._get_ftyp(buf) + for brand in compatible_brands: + if brand in ['mp41', 'mp42', 'isom']: + return True + return major_brand in ['mp41', 'mp42', 'isom'] + + +class M4v(Type): + """ + Implements the M4V video type matcher. + """ + MIME = 'video/x-m4v' + EXTENSION = 'm4v' + + def __init__(self): + super(M4v, self).__init__( + mime=M4v.MIME, + extension=M4v.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 10 and + buf[0] == 0x0 and buf[1] == 0x0 and + buf[2] == 0x0 and buf[3] == 0x1C and + buf[4] == 0x66 and buf[5] == 0x74 and + buf[6] == 0x79 and buf[7] == 0x70 and + buf[8] == 0x4D and buf[9] == 0x34 and + buf[10] == 0x56) + + +class Mkv(Type): + """ + Implements the MKV video type matcher. + """ + MIME = 'video/x-matroska' + EXTENSION = 'mkv' + + def __init__(self): + super(Mkv, self).__init__( + mime=Mkv.MIME, + extension=Mkv.EXTENSION + ) + + def match(self, buf): + contains_ebml_element = buf.startswith(b'\x1A\x45\xDF\xA3') + contains_doctype_element = buf.find(b'\x42\x82\x88matroska') > -1 + return contains_ebml_element and contains_doctype_element + + +class Webm(Type): + """ + Implements the WebM video type matcher. + """ + MIME = 'video/webm' + EXTENSION = 'webm' + + def __init__(self): + super(Webm, self).__init__( + mime=Webm.MIME, + extension=Webm.EXTENSION + ) + + def match(self, buf): + contains_ebml_element = buf.startswith(b'\x1A\x45\xDF\xA3') + contains_doctype_element = buf.find(b'\x42\x82\x84webm') > -1 + return contains_ebml_element and contains_doctype_element + + +class Mov(IsoBmff): + """ + Implements the MOV video type matcher. + """ + MIME = 'video/quicktime' + EXTENSION = 'mov' + + def __init__(self): + super(Mov, self).__init__( + mime=Mov.MIME, + extension=Mov.EXTENSION + ) + + def match(self, buf): + if not self._is_isobmff(buf): + return False + + major_brand, minor_version, compatible_brands = self._get_ftyp(buf) + return major_brand == 'qt ' + + +class Avi(Type): + """ + Implements the AVI video type matcher. + """ + MIME = 'video/x-msvideo' + EXTENSION = 'avi' + + def __init__(self): + super(Avi, self).__init__( + mime=Avi.MIME, + extension=Avi.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 11 and + buf[0] == 0x52 and + buf[1] == 0x49 and + buf[2] == 0x46 and + buf[3] == 0x46 and + buf[8] == 0x41 and + buf[9] == 0x56 and + buf[10] == 0x49 and + buf[11] == 0x20) + + +class Wmv(Type): + """ + Implements the WMV video type matcher. + """ + MIME = 'video/x-ms-wmv' + EXTENSION = 'wmv' + + def __init__(self): + super(Wmv, self).__init__( + mime=Wmv.MIME, + extension=Wmv.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 9 and + buf[0] == 0x30 and + buf[1] == 0x26 and + buf[2] == 0xB2 and + buf[3] == 0x75 and + buf[4] == 0x8E and + buf[5] == 0x66 and + buf[6] == 0xCF and + buf[7] == 0x11 and + buf[8] == 0xA6 and + buf[9] == 0xD9) + + +class Flv(Type): + """ + Implements the FLV video type matcher. + """ + MIME = 'video/x-flv' + EXTENSION = 'flv' + + def __init__(self): + super(Flv, self).__init__( + mime=Flv.MIME, + extension=Flv.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x46 and + buf[1] == 0x4C and + buf[2] == 0x56 and + buf[3] == 0x01) + + +class Mpeg(Type): + """ + Implements the MPEG video type matcher. + """ + MIME = 'video/mpeg' + EXTENSION = 'mpg' + + def __init__(self): + super(Mpeg, self).__init__( + mime=Mpeg.MIME, + extension=Mpeg.EXTENSION + ) + + def match(self, buf): + return (len(buf) > 3 and + buf[0] == 0x0 and + buf[1] == 0x0 and + buf[2] == 0x1 and + buf[3] >= 0xb0 and + buf[3] <= 0xbf) + + +class M3gp(Type): + """Implements the 3gp image type matcher.""" + + MIME = 'video/3gpp' + EXTENSION = '3gp' + + def __init__(self): + super(M3gp, self).__init__( + mime=M3gp.MIME, + extension=M3gp.EXTENSION + ) + + def match(self, buf): + return buf[:7] == bytearray([0x66, 0x74, 0x79, 0x70, 0x33, 0x67, 0x70]) |