33from __future__ import annotations
44
55import base64
6- import imghdr
7- from collections import OrderedDict
86from os import path
9- from typing import IO , BinaryIO , NamedTuple
7+ from typing import TYPE_CHECKING , NamedTuple
108
119import imagesize
1210
11+ if TYPE_CHECKING :
12+ from os import PathLike
13+
1314try :
1415 from PIL import Image
1516except ImportError :
1617 Image = None
1718
18- mime_suffixes = OrderedDict ([
19- ('.gif' , 'image/gif' ),
20- ('.jpg' , 'image/jpeg' ),
21- ('.png' , 'image/png' ),
22- ('.pdf' , 'application/pdf' ),
23- ('.svg' , 'image/svg+xml' ),
24- ('.svgz' , 'image/svg+xml' ),
25- ('.ai' , 'application/illustrator' ),
26- ])
19+ mime_suffixes = {
20+ '.gif' : 'image/gif' ,
21+ '.jpg' : 'image/jpeg' ,
22+ '.png' : 'image/png' ,
23+ '.pdf' : 'application/pdf' ,
24+ '.svg' : 'image/svg+xml' ,
25+ '.svgz' : 'image/svg+xml' ,
26+ '.ai' : 'application/illustrator'
27+ }
28+ _suffix_from_mime = {v : k for k , v in reversed (mime_suffixes .items ())}
2729
2830
2931class DataURI (NamedTuple ):
@@ -49,31 +51,25 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
4951 return None
5052
5153
52- def guess_mimetype_for_stream (stream : IO , default : str | None = None ) -> str | None :
53- imgtype = imghdr .what (stream )
54- if imgtype :
55- return 'image/' + imgtype
56- else :
57- return default
58-
59-
60- def guess_mimetype (filename : str = '' , default : str | None = None ) -> str | None :
61- _ , ext = path .splitext (filename .lower ())
54+ def guess_mimetype (
55+ filename : PathLike [str ] | str = '' ,
56+ default : str | None = None ,
57+ ) -> str | None :
58+ ext = path .splitext (filename )[1 ].lower ()
6259 if ext in mime_suffixes :
6360 return mime_suffixes [ext ]
64- elif path .exists (filename ):
65- with open (filename , 'rb' ) as f :
66- return guess_mimetype_for_stream (f , default = default )
67-
61+ if path .exists (filename ):
62+ try :
63+ imgtype = _image_type_from_file (filename )
64+ except ValueError :
65+ pass
66+ else :
67+ return 'image/' + imgtype
6868 return default
6969
7070
7171def get_image_extension (mimetype : str ) -> str | None :
72- for ext , _mimetype in mime_suffixes .items ():
73- if mimetype == _mimetype :
74- return ext
75-
76- return None
72+ return _suffix_from_mime .get (mimetype )
7773
7874
7975def parse_data_uri (uri : str ) -> DataURI | None :
@@ -97,17 +93,43 @@ def parse_data_uri(uri: str) -> DataURI | None:
9793 return DataURI (mimetype , charset , image_data )
9894
9995
100- def test_svg (h : bytes , f : BinaryIO | None ) -> str | None :
101- """An additional imghdr library helper; test the header is SVG's or not."""
102- try :
103- if '<svg' in h .decode ().lower ():
104- return 'svg+xml'
105- except UnicodeDecodeError :
106- pass
96+ def _image_type_from_file (filename : PathLike [str ] | str ) -> str :
97+ with open (filename , 'rb' ) as f :
98+ header = f .read (32 ) # 32 bytes
99+
100+ # Bitmap
101+ # https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
102+ if header .startswith (b'BM' ):
103+ return 'bmp'
104+
105+ # GIF
106+ # https://en.wikipedia.org/wiki/GIF#File_format
107+ if header .startswith ((b'GIF87a' , b'GIF89a' )):
108+ return 'gif'
109+
110+ # JPEG data
111+ # https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#File_format_structure
112+ if header .startswith (b'\xFF \xD8 ' ):
113+ return 'jpeg'
114+
115+ # Portable Network Graphics
116+ # https://en.wikipedia.org/wiki/PNG#File_header
117+ if header .startswith (b'\x89 PNG\r \n \x1A \n ' ):
118+ return 'png'
119+
120+ # Scalable Vector Graphics
121+ # https://svgwg.org/svg2-draft/struct.html
122+ if b'<svg' in header .lower ():
123+ return 'svg+xml'
107124
108- return None
125+ # TIFF
126+ # https://en.wikipedia.org/wiki/TIFF#Byte_order
127+ if header .startswith ((b'MM' , b'II' )):
128+ return 'tiff'
109129
130+ # WebP
131+ # https://en.wikipedia.org/wiki/WebP#Technology
132+ if header .startswith (b'RIFF' ) and header [8 :12 ] == b'WEBP' :
133+ return 'webp'
110134
111- # install test_svg() to imghdr
112- # refs: https://docs.python.org/3.9/library/imghdr.html#imghdr.tests
113- imghdr .tests .append (test_svg )
135+ raise ValueError ('Could not detect image type!' )
0 commit comments