Skip to content

Commit

Permalink
Add partial support for avif
Browse files Browse the repository at this point in the history
From the spec, this only covers the most common format for AVIF images.
I can't do more because the alternatives are speced behind a paywall.
AVIF: https://aomediacodec.github.io/av1-avif/v1.1.0.html
Points to HEIF: https://www.iso.org/standard/66067.html
  • Loading branch information
brunoais committed May 21, 2023
1 parent fb25377 commit 1ed40d7
Showing 1 changed file with 45 additions and 1 deletion.
46 changes: 45 additions & 1 deletion get_image_size.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import os
import io
import struct
import time

FILE_UNKNOWN = "Sorry, don't know how to get size for this file."

Expand Down Expand Up @@ -119,7 +120,7 @@ def get_image_metadata_from_bytesio(input, size, file_path=None):
"""
height = -1
width = -1
data = input.read(26)
data = input.read(28)
msg = " raised while trying to decode as JPEG."

if (size >= 10) and data[:6] in (b'GIF87a', b'GIF89a'):
Expand Down Expand Up @@ -245,6 +246,49 @@ def get_image_metadata_from_bytesio(input, size, file_path=None):
break
except Exception as e:
raise UnknownImageFormat(str(e))
elif (size >= 28) and data[4:8] == b'ftyp' and \
any(bytes(brand) in (b'avif', b'avis') for brand in zip(*((iter(data[8:]),) * 4))):
MSG = 'Failed to parse image as avif. '
HEADER_PATH = ('meta', 'iprp', 'ipco', 'ispe')
MAX_HEAD_SEARCH = 2 << 10 # Don't waste time if missed 'mdat' & it's processing mdat as part of the header
try:
header_size = struct.unpack('>I', data[0:4])[0]
input.seek(header_size, 0)

def _find(what, up_to_bytes):
while input.tell() < up_to_bytes:
block_size_bytes = input.read(4)
# https://docs.python.org/3/library/io.html#io.RawIOBase.read
if block_size_bytes is None:
time.sleep(0.1)
continue
if not block_size_bytes:
raise UnknownImageFormat(MSG + "Reached end of file")

block_size = struct.unpack('>I', block_size_bytes)[0]
block_type = input.read(4).decode('ascii')
if block_type == 'mdat':
raise UnknownImageFormat(MSG + "Reached image data without finding '{}' data".format(HEADER_PATH[-1]))
if block_type == what:
return input.tell() + block_size

input.seek(block_size - 8, 1)

raise UnknownImageFormat(MSG + "Reached end of block but couldn't find '{}'".format(what))

sub_block_end = _find(HEADER_PATH[0], MAX_HEAD_SEARCH)
input.seek(4, 1) # Jumping over meta's version and flags.
for find in HEADER_PATH[1:]:
sub_block_end = _find(find, sub_block_end)

input.seek(4, 1) # Jumping over ispe's version and flags.
width = struct.unpack('>I', input.read(4))[0]
height = struct.unpack('>I', input.read(4))[0]

except struct.error:
raise UnknownImageFormat(MSG + "See cause exception for details")
except UnicodeDecodeError:
raise UnknownImageFormat(MSG + "Header name not ASCII convertible")
elif size >= 2:
# see http://en.wikipedia.org/wiki/ICO_(file_format)
imgtype = 'ICO'
Expand Down

0 comments on commit 1ed40d7

Please sign in to comment.