3
3
from __future__ import annotations
4
4
5
5
import base64
6
- import imghdr
7
- from collections import OrderedDict
8
6
from os import path
9
- from typing import IO , BinaryIO , NamedTuple
7
+ from typing import TYPE_CHECKING , NamedTuple
10
8
11
9
import imagesize
12
10
11
+ if TYPE_CHECKING :
12
+ from os import PathLike
13
+
13
14
try :
14
15
from PIL import Image
15
16
except ImportError :
16
17
Image = None
17
18
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 ())}
27
29
28
30
29
31
class DataURI (NamedTuple ):
@@ -49,31 +51,25 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
49
51
return None
50
52
51
53
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 ()
62
59
if ext in mime_suffixes :
63
60
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
68
68
return default
69
69
70
70
71
71
def 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 )
77
73
78
74
79
75
def parse_data_uri (uri : str ) -> DataURI | None :
@@ -97,17 +93,43 @@ def parse_data_uri(uri: str) -> DataURI | None:
97
93
return DataURI (mimetype , charset , image_data )
98
94
99
95
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'
107
124
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'
109
129
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'
110
134
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