about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/filetype/types
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/filetype/types
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/filetype/types')
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/__init__.py118
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/application.py22
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/archive.py687
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/audio.py212
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/base.py29
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/document.py256
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/font.py115
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/image.py383
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/isobmff.py33
-rw-r--r--.venv/lib/python3.12/site-packages/filetype/types/video.py223
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])