Add support for procreate file format (#1425)

* Add function to get zip as Path and use it to avoid temp file for krita

* Add support for procreate format
This commit is contained in:
Paul Friederichsen 2023-08-26 14:05:45 -05:00 committed by GitHub
parent a46d6f1790
commit 785e37345f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 17 deletions

View File

@ -23,4 +23,6 @@ def ReadSingleFileFromZip( path_to_zip, filename_to_extract ):
return reader.read()
def GetZipAsPath( path_to_zip, path_in_zip="" ):
return zipfile.Path( path_to_zip, at=path_in_zip )

View File

@ -728,6 +728,7 @@ IMAGE_AVIF = 65
IMAGE_AVIF_SEQUENCE = 66
UNDETERMINED_GIF = 67
IMAGE_GIF = 68
APPLICATION_PROCREATE = 69
IMAGE_QOI = 70
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
@ -774,6 +775,7 @@ SEARCHABLE_MIMES = {
APPLICATION_SAI2,
APPLICATION_KRITA,
APPLICATION_XCF,
APPLICATION_PROCREATE,
APPLICATION_PDF,
APPLICATION_ZIP,
APPLICATION_RAR,
@ -869,7 +871,8 @@ IMAGE_PROJECT_FILES = [
APPLICATION_SAI2,
APPLICATION_KRITA,
IMAGE_SVG,
APPLICATION_XCF
APPLICATION_XCF,
APPLICATION_PROCREATE
]
ARCHIVES = [
@ -913,7 +916,7 @@ PIL_HEIF_MIMES = {
MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) )
MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) )
APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_KRITA } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_KRITA, APPLICATION_PROCREATE } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( APPLICATIONS_WITH_THUMBNAILS )
@ -955,9 +958,10 @@ mime_enum_lookup = {
'application/x-photoshop' : APPLICATION_PSD,
'image/vnd.adobe.photoshop' : APPLICATION_PSD,
'application/vnd.adobe.photoshop' : APPLICATION_PSD,
'application/clip' : APPLICATION_CLIP,
'application/sai2': APPLICATION_SAI2,
'application/clip' : APPLICATION_CLIP, # made up
'application/sai2': APPLICATION_SAI2, # made up
'application/x-krita': APPLICATION_KRITA,
'application/x-procreate': APPLICATION_PROCREATE, # made up
'image/x-xcf' : APPLICATION_XCF,
'application/octet-stream' : APPLICATION_OCTET_STREAM,
'application/x-yaml' : APPLICATION_YAML,
@ -1031,6 +1035,7 @@ mime_string_lookup = {
APPLICATION_SAI2 : 'sai2',
APPLICATION_KRITA : 'krita',
APPLICATION_XCF : 'xcf',
APPLICATION_PROCREATE : 'procreate',
APPLICATION_ZIP : 'zip',
APPLICATION_RAR : 'rar',
APPLICATION_7Z : '7z',
@ -1103,10 +1108,11 @@ mime_mimetype_string_lookup = {
APPLICATION_CBOR : 'application/cbor',
APPLICATION_PDF : 'application/pdf',
APPLICATION_PSD : 'image/vnd.adobe.photoshop',
APPLICATION_CLIP : 'application/clip',
APPLICATION_SAI2: 'application/sai2',
APPLICATION_CLIP : 'application/clip', # made up
APPLICATION_SAI2: 'application/sai2', # made up
APPLICATION_KRITA: 'application/x-krita',
APPLICATION_XCF : 'image/x-xcf',
APPLICATION_PROCREATE : 'application/x-procreate', # made up
APPLICATION_ZIP : 'application/zip',
APPLICATION_RAR : 'application/vnd.rar',
APPLICATION_7Z : 'application/x-7z-compressed',
@ -1181,6 +1187,7 @@ mime_ext_lookup = {
APPLICATION_SAI2: '.sai2',
APPLICATION_KRITA: '.kra',
APPLICATION_XCF : '.xcf',
APPLICATION_PROCREATE : '.procreate',
APPLICATION_ZIP : '.zip',
APPLICATION_RAR : '.rar',
APPLICATION_7Z : '.7z',

View File

@ -12,6 +12,7 @@ from hydrus.core import HydrusExceptions
from hydrus.core import HydrusFlashHandling
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusKritaHandling
from hydrus.core import HydrusProcreateHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusSVGHandling
@ -184,6 +185,25 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
elif mime == HC.APPLICATION_PROCREATE:
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath()
try:
HydrusProcreateHandling.ExtractZippedThumbnailToPath( path, temp_path )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
except Exception as e:
thumb_path = os.path.join( HC.STATIC_DIR, 'procreate.png' )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
finally:
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
elif mime == HC.IMAGE_SVG:
@ -389,6 +409,10 @@ def GetFileInfo( path, mime = None, ok_to_look_for_hydrus_updates = False ):
elif mime == HC.APPLICATION_KRITA:
( width, height ) = HydrusKritaHandling.GetKraProperties( path )
elif mime == HC.APPLICATION_PROCREATE:
( width, height ) = HydrusProcreateHandling.GetProcreateResolution( path )
elif mime == HC.IMAGE_SVG:
@ -527,6 +551,10 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ):
if HydrusKritaHandling.ZipLooksLikeAKrita( path ):
return HC.APPLICATION_KRITA
elif HydrusProcreateHandling.ZipLooksLikeProcreate( path ):
return HC.APPLICATION_PROCREATE
else:

View File

@ -34,8 +34,6 @@ def ExtractZippedImageToPath( path_to_zip, temp_path_file ):
# TODO: animation and frame stuff which is also in the maindoc.xml
def GetKraProperties( path ):
( os_file_handle, maindoc_xml ) = HydrusTemp.GetTempPath()
DOCUMENT_INFO_FILE = "maindoc.xml"
# TODO: probably actually parse the xml instead of using regex
@ -46,9 +44,7 @@ def GetKraProperties( path ):
try:
HydrusArchiveHandling.ExtractSingleFileFromZip( path, DOCUMENT_INFO_FILE, maindoc_xml )
with open(maindoc_xml, "r") as reader:
with HydrusArchiveHandling.GetZipAsPath( path, DOCUMENT_INFO_FILE ).open('r') as reader:
for line in reader:
@ -75,11 +71,7 @@ def GetKraProperties( path ):
except KeyError:
raise HydrusExceptions.DamagedOrUnusualFileException( f'This krita file had no {DOCUMENT_INFO_FILE}, so no information could be extracted!' )
finally:
HydrusTemp.CleanUpTempPath( os_file_handle, maindoc_xml )
return width, height

View File

@ -26,6 +26,7 @@ mimes_to_default_thumbnail_paths[ HC.APPLICATION_PSD ] = os.path.join( HC.STATIC
mimes_to_default_thumbnail_paths[ HC.APPLICATION_CLIP ] = os.path.join( HC.STATIC_DIR, 'clip.png' )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_SAI2 ] = os.path.join( HC.STATIC_DIR, 'sai.png' )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_KRITA ] = os.path.join( HC.STATIC_DIR, 'krita.png' )
mimes_to_default_thumbnail_paths[ HC.APPLICATION_PROCREATE ] = os.path.join( HC.STATIC_DIR, 'procreate.png' )
mimes_to_default_thumbnail_paths[ HC.IMAGE_SVG ] = os.path.join( HC.STATIC_DIR, 'svg.png' )
for mime in HC.AUDIO:

View File

@ -0,0 +1,86 @@
from hydrus.core import HydrusArchiveHandling
from hydrus.core import HydrusExceptions
import plistlib
# Mostly based on https://github.com/jaromvogel/ProcreateViewer/blob/master/ProcreatePython/ProcreateImageData.py
PROCREATE_THUMBNAIL_FILE_PATH = 'QuickLook/Thumbnail.png'
PROCREATE_DOCUMENT_ARCHIVE = 'Document.archive'
# object key in plist to start from (trunk)
PROCREATE_PROJECT_KEY = 1
def ExtractZippedThumbnailToPath( path_to_zip, temp_path_file ):
try:
HydrusArchiveHandling.ExtractSingleFileFromZip( path_to_zip, PROCREATE_THUMBNAIL_FILE_PATH, temp_path_file )
except KeyError:
raise HydrusExceptions.DamagedOrUnusualFileException( f'This procreate file had no thumbnail file!' )
def GetProcreatePlist( path ):
plist_file = HydrusArchiveHandling.GetZipAsPath( path, PROCREATE_DOCUMENT_ARCHIVE )
if not plist_file.exists():
raise HydrusExceptions.DamagedOrUnusualFileException('Procreate file has no plist!')
with HydrusArchiveHandling.GetZipAsPath( path, PROCREATE_DOCUMENT_ARCHIVE ).open('rb') as document:
return plistlib.load(document)
def ZipLooksLikeProcreate( path ) -> bool:
try:
document = GetProcreatePlist( path )
objects = document['$objects']
class_pointer = objects[PROCREATE_PROJECT_KEY]['$class']
class_name = objects[class_pointer]['$classname']
return class_name == 'SilicaDocument'
except:
return False
def GetProcreateResolution( path ):
# TODO: animation stuff from plist
document = GetProcreatePlist( path )
objects = document['$objects']
dimension_pointer = objects[PROCREATE_PROJECT_KEY]['size'].data
# eg '{2894, 4093}'
size_string = objects[dimension_pointer]
size = size_string.strip('{').strip('}').split(', ')
orientation = objects[PROCREATE_PROJECT_KEY]['orientation']
if orientation in [3,4]:
# canvas is rotated 90 or -90 degrees
height = size[1]
width = size[0]
else:
height = size[0]
width = size[1]
return int(width), int(height)

BIN
static/procreate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB