Coverage for api/utils/image_proxy/wikimedia.py: 29%
18 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 06:14 +0000
1import re
2from math import inf
3from urllib.parse import ParseResult
5from django.conf import settings
7from api.utils.image_proxy.dataclasses import MediaInfo, RequestConfig
10# e.g.:
11# https://upload.wikimedia.org/wikipedia/commons/9/9e/Color_icon_yellow.svg
12# https://upload.wikimedia.org/wikipedia/commons/f/f3/Open_book_01.svg
13# The length of the hashes is constant
14# For each example, the thumbnail URL is:
15# https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Color_icon_yellow.svg/<WIDTH>px-Color_icon_yellow.svg.jpg
16# https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Open_book_01.svg/<WIDTH>px-Open_book_01.svg.jpg
17# A regex is slightly more reliable for this in case for some reason wikipedia/commons is not a constant prefix
18# or the filename somehow has an additional `/` in it (though that does not appear to be possible)
19WIKIMEDIA_PATH_PARSE = re.compile(
20 r"^(?P<path_prefix>.*?)(?P<hash>/[a-z0-9]{1}/[a-z0-9]{2}/)(?P<filename>.*)$"
21)
24def get_wikimedia_thumbnail_url(
25 media_info: MediaInfo, url: ParseResult, request_config: RequestConfig
26) -> ParseResult | None:
27 path_match = WIKIMEDIA_PATH_PARSE.match(url.path)
28 if path_match is None:
29 return None
31 groups = path_match.groupdict()
33 if request_config.is_full_size:
34 width = media_info.width or settings.THUMBNAIL_WIDTH_PX
35 else:
36 # If not full size, prevent Wikimedia from upscaling the image by using the smaller of
37 # the default thumbnail size or media_info.width
38 # Wikimedia _requires_ sending a width, so there's no other fallback than the default thumbnail size
39 width = min(settings.THUMBNAIL_WIDTH_PX, media_info.width or inf)
41 params = f"{width}px"
43 if request_config.is_compressed:
44 params = f"lossy-{params}"
46 # always retrieve a PNG, and Site Accelerator will convert it as necessary based on the accept headers
47 return url._replace(
48 path=f"{groups['path_prefix']}/thumb{groups['hash']}{groups['filename']}/{params}-{groups['filename']}.png"
49 )