From 2887be614c144c7d6373e79e1527a7733c8907e2 Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Wed, 2 Aug 2023 05:37:45 -0500 Subject: [PATCH] Start adding HEIF support --- hydrus/core/HydrusConstants.py | 60 +++++++++++++++++++++++++++--- hydrus/core/HydrusFileHandling.py | 19 +++++++++- hydrus/core/HydrusImageHandling.py | 14 +++++-- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py index 9533c89f..3bf7a93d 100644 --- a/hydrus/core/HydrusConstants.py +++ b/hydrus/core/HydrusConstants.py @@ -718,6 +718,12 @@ APPLICATION_KRITA = 55 IMAGE_SVG = 56 APPLICATION_XCF = 57 APPLICATION_GZIP = 58 +IMAGE_HEIF = 59 +IMAGE_HEIF_SEQUENCE = 60 +IMAGE_HEIC = 61 +IMAGE_HEIC_SEQUENCE = 62 +IMAGE_AVIF = 63 +IMAGE_AVIF_SEQUENCE = 64 APPLICATION_OCTET_STREAM = 100 APPLICATION_UNKNOWN = 101 @@ -738,6 +744,12 @@ SEARCHABLE_MIMES = { IMAGE_TIFF, IMAGE_ICON, IMAGE_SVG, + IMAGE_HEIF, + IMAGE_HEIF_SEQUENCE, + IMAGE_HEIC, + IMAGE_HEIC_SEQUENCE, + IMAGE_AVIF, + IMAGE_AVIF_SEQUENCE, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, @@ -787,12 +799,18 @@ IMAGES = { IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, - IMAGE_ICON + IMAGE_ICON, + IMAGE_HEIF, + IMAGE_HEIC, + IMAGE_AVIF, } ANIMATIONS = { IMAGE_GIF, - IMAGE_APNG + IMAGE_APNG, + IMAGE_HEIF_SEQUENCE, + IMAGE_HEIC_SEQUENCE, + IMAGE_AVIF_SEQUENCE, } AUDIO = { @@ -853,7 +871,13 @@ for ( general_mime_type, mimes_in_type ) in general_mimetypes_to_mime_groups.ite mimes_to_general_mimetypes[ mime ] = general_mime_type - +PIL_HEIF_MIMES = { + IMAGE_HEIF, + IMAGE_HEIF_SEQUENCE, + IMAGE_HEIC, + IMAGE_HEIC_SEQUENCE, + IMAGE_AVIF, +} MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) ) MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) ) @@ -862,11 +886,11 @@ ARCHIVES = { APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP, APPLICATION_RAR, MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_PSD, APPLICATION_KRITA } ) -FILES_THAT_CAN_HAVE_ICC_PROFILE = { IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_TIFF } +FILES_THAT_CAN_HAVE_ICC_PROFILE = { IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_TIFF }.union( PIL_HEIF_MIMES ) -FILES_THAT_CAN_HAVE_EXIF = { IMAGE_JPEG, IMAGE_TIFF, IMAGE_PNG, IMAGE_WEBP } +FILES_THAT_CAN_HAVE_EXIF = { IMAGE_JPEG, IMAGE_TIFF, IMAGE_PNG, IMAGE_WEBP }.union( PIL_HEIF_MIMES ) # images and animations that PIL can handle -FILES_THAT_CAN_HAVE_HUMAN_READABLE_EMBEDDED_METADATA = { IMAGE_JPEG, IMAGE_PNG, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, IMAGE_GIF, IMAGE_APNG } +FILES_THAT_CAN_HAVE_HUMAN_READABLE_EMBEDDED_METADATA = { IMAGE_JPEG, IMAGE_PNG, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, IMAGE_GIF, IMAGE_APNG }.union( PIL_HEIF_MIMES ) FILES_THAT_CAN_HAVE_PIXEL_HASH = set( IMAGES ).union( { IMAGE_GIF } ) FILES_THAT_HAVE_PERCEPTUAL_HASH = set( IMAGES ) @@ -887,6 +911,12 @@ mime_enum_lookup = { 'image/tiff' : IMAGE_TIFF, 'image/x-icon' : IMAGE_ICON, 'image/svg+xml': IMAGE_SVG, + 'image/heif' : IMAGE_HEIF, + 'image/heif-sequence' : IMAGE_HEIF_SEQUENCE, + 'image/heic' : IMAGE_HEIC, + 'image/heic-sequence' : IMAGE_HEIC_SEQUENCE, + 'image/avif' : IMAGE_AVIF, + 'image/avif-sequence' : IMAGE_AVIF_SEQUENCE, 'image/vnd.microsoft.icon' : IMAGE_ICON, 'image' : IMAGES, 'application/x-shockwave-flash' : APPLICATION_FLASH, @@ -950,6 +980,12 @@ mime_string_lookup = { IMAGE_TIFF : 'tiff', IMAGE_ICON : 'icon', IMAGE_SVG : 'svg', + IMAGE_HEIF: 'heif', + IMAGE_HEIF_SEQUENCE: 'heif sequence', + IMAGE_HEIC: 'heic', + IMAGE_HEIC_SEQUENCE: 'heic sequence', + IMAGE_AVIF: 'avif', + IMAGE_AVIF_SEQUENCE: 'avif sequence', APPLICATION_FLASH : 'flash', APPLICATION_OCTET_STREAM : 'application/octet-stream', APPLICATION_YAML : 'yaml', @@ -1014,6 +1050,12 @@ mime_mimetype_string_lookup = { IMAGE_TIFF : 'image/tiff', IMAGE_ICON : 'image/x-icon', IMAGE_SVG : 'image/svg+xml', + IMAGE_HEIF: 'image/heif', + IMAGE_HEIF_SEQUENCE: 'image/heif-sequence', + IMAGE_HEIC: 'image/heic', + IMAGE_HEIC_SEQUENCE: 'image/heic-sequence', + IMAGE_AVIF: 'image/avif', + IMAGE_AVIF_SEQUENCE: 'image/avif-sequence', APPLICATION_FLASH : 'application/x-shockwave-flash', APPLICATION_OCTET_STREAM : 'application/octet-stream', APPLICATION_YAML : 'application/x-yaml', @@ -1079,6 +1121,12 @@ mime_ext_lookup = { IMAGE_TIFF : '.tiff', IMAGE_ICON : '.ico', IMAGE_SVG : '.svg', + IMAGE_HEIF: 'heif', + IMAGE_HEIF_SEQUENCE: 'heifs', + IMAGE_HEIC: 'heic', + IMAGE_HEIC_SEQUENCE: 'heics', + IMAGE_AVIF: 'avif', + IMAGE_AVIF_SEQUENCE: 'avifs', APPLICATION_FLASH : '.swf', APPLICATION_OCTET_STREAM : '.bin', APPLICATION_YAML : '.yaml', diff --git a/hydrus/core/HydrusFileHandling.py b/hydrus/core/HydrusFileHandling.py index ea36cb6d..b1261ade 100644 --- a/hydrus/core/HydrusFileHandling.py +++ b/hydrus/core/HydrusFileHandling.py @@ -79,6 +79,21 @@ headers_and_mime.extend( [ ( ( ( 0, b'\x52\x61\x72\x21\x1A\x07\x01\x00' ), ), HC.APPLICATION_RAR ), ( ( ( 0, b'\x1f\x8b' ), ), HC.APPLICATION_GZIP ), ( ( ( 0, b'hydrus encrypted zip' ), ), HC.APPLICATION_HYDRUS_ENCRYPTED_ZIP ), + ( ( ( 4, b'ftypavif' ), ), HC.IMAGE_AVIF ), + ( ( ( 4, b'ftypavis' ), ), HC.IMAGE_AVIF_SEQUENCE ), + ( ( ( 4, b'ftypmif1' ), ( 16, b'avif' ), ), HC.IMAGE_AVIF ), + ( ( ( 4, b'ftypmif1' ), ( 20, b'avif' ), ), HC.IMAGE_AVIF ), + ( ( ( 4, b'ftypmif1' ), ( 24, b'avif' ), ), HC.IMAGE_AVIF ), + ( ( ( 4, b'ftypheic' ), ), HC.IMAGE_HEIC ), + ( ( ( 4, b'ftypheix' ), ), HC.IMAGE_HEIC ), + ( ( ( 4, b'ftypheim' ), ), HC.IMAGE_HEIC ), + ( ( ( 4, b'ftypheis' ), ), HC.IMAGE_HEIC ), + ( ( ( 4, b'ftyphevc' ), ), HC.IMAGE_HEIC_SEQUENCE ), + ( ( ( 4, b'ftyphevx' ), ), HC.IMAGE_HEIC_SEQUENCE ), + ( ( ( 4, b'ftyphevm' ), ), HC.IMAGE_HEIC_SEQUENCE ), + ( ( ( 4, b'ftyphevs' ), ), HC.IMAGE_HEIC_SEQUENCE ), + ( ( ( 4, b'ftypmif1' ), ), HC.IMAGE_HEIF ), + ( ( ( 4, b'ftypsf1' ), ), HC.IMAGE_HEIF_SEQUENCE ), ( ( ( 4, b'ftypmp4' ), ), HC.UNDETERMINED_MP4 ), ( ( ( 4, b'ftypisom' ), ), HC.UNDETERMINED_MP4 ), ( ( ( 4, b'ftypM4V' ), ), HC.UNDETERMINED_MP4 ), @@ -102,7 +117,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames, target_resolution = ( 128, 128 ) - if mime in ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON ): # not apng atm + if mime in { HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON }.union( HC.PIL_HEIF_MIMES ): # not apng atm try: @@ -353,7 +368,7 @@ def GetFileInfo( path, mime = None, ok_to_look_for_hydrus_updates = False ): has_audio = False - if mime in ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON ): + if mime in { HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON }.union( HC.PIL_HEIF_MIMES ): ( ( width, height ), duration, num_frames ) = HydrusImageHandling.GetImageProperties( path, mime ) diff --git a/hydrus/core/HydrusImageHandling.py b/hydrus/core/HydrusImageHandling.py index efa9881b..e49cff9d 100644 --- a/hydrus/core/HydrusImageHandling.py +++ b/hydrus/core/HydrusImageHandling.py @@ -26,6 +26,12 @@ from PIL import ImageFile as PILImageFile from PIL import Image as PILImage from PIL import ImageCms as PILImageCms +from pillow_heif import register_heif_opener +from pillow_heif import register_avif_opener + +register_heif_opener(thumbnails=False) +register_avif_opener(thumbnails=False) + from hydrus.core import HydrusConstants as HC from hydrus.core import HydrusData from hydrus.core import HydrusExceptions @@ -79,7 +85,7 @@ warnings.simplefilter( 'ignore', PILImage.DecompressionBombError ) OLD_PIL_MAX_IMAGE_PIXELS = PILImage.MAX_IMAGE_PIXELS PILImage.MAX_IMAGE_PIXELS = None # this turns off decomp check entirely, wew -PIL_ONLY_MIMETYPES = { HC.IMAGE_GIF, HC.IMAGE_ICON } +PIL_ONLY_MIMETYPES = { HC.IMAGE_GIF, HC.IMAGE_ICON }.union( HC.PIL_HEIF_MIMES ) try: @@ -817,8 +823,8 @@ def GetResolutionNumPy( numpy_image ): def GetResolutionAndNumFramesPIL( path, mime ): - pil_image = GeneratePILImage( path, dequantize = False ) - + pil_image = GeneratePILImage( path, dequantize = False ) + ( x, y ) = pil_image.size if mime == HC.IMAGE_GIF: # some jpegs came up with 2 frames and 'duration' because of some embedded thumbnail in the metadata @@ -1222,6 +1228,8 @@ def RawOpenPILImage( path ) -> PILImage.Image: pil_image = PILImage.open( path ) except Exception as e: + + print(e) raise HydrusExceptions.DamagedOrUnusualFileException( 'Could not load the image--it was likely malformed!' )