Skip to content

Commit a502e75

Browse files
committed
Refactor sphinx.util.images.guess_mimetype
1 parent 6079089 commit a502e75

File tree

1 file changed

+64
-42
lines changed

1 file changed

+64
-42
lines changed

sphinx/util/images.py

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,29 @@
33
from __future__ import annotations
44

55
import base64
6-
import imghdr
7-
from collections import OrderedDict
86
from os import path
9-
from typing import IO, BinaryIO, NamedTuple
7+
from typing import TYPE_CHECKING, NamedTuple
108

119
import imagesize
1210

11+
if TYPE_CHECKING:
12+
from os import PathLike
13+
1314
try:
1415
from PIL import Image
1516
except 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

2931
class 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

7171
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)
7773

7874

7975
def 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'\x89PNG\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

Comments
 (0)