Krita full preview support (#1454)
* Krita full preview support Mark krita files as viewable Use same data that's used for thumbnails to generate full preview * Parse maindoc.xml instead of doing regex magic This should fix getting dimensions wrong for some files * Use direct PIL image loading for Krita thumbs * Remove unused HydrusArchiveHandling.ReadSingleFileFromZip * Update Krita in filetype docs --------- Co-authored-by: Paul Friederichsen <floogulinc@gmail.com> Co-authored-by: Hydrus Network Developer <hydrus.admin@gmail.com>
This commit is contained in:
parent
882eaa2147
commit
408ac2aaa5
|
@ -77,15 +77,15 @@ This is a list of all filetypes Hydrus can import. Hydrus determines the filetyp
|
|||
|
||||
## Image Project Files
|
||||
|
||||
| Filetype | Extension | MIME type | Thumbnails | Viewable in Hydrus | Notes |
|
||||
| --------- | ------------ | ----------------------------- | :--------: | :----------------: | ----------------- |
|
||||
| psd | `.psd` | `image/vnd.adobe.photoshop` | ✅ | ✅ | Adobe Photoshop |
|
||||
| clip | `.clip` | `application/clip`[^1] | ✅ | ❌ | Clip Studio Paint |
|
||||
| sai2 | `.sai2` | `application/sai2`[^1] | ❌ | ❌ | PaintTool SAI2 |
|
||||
| krita | `.kra` | `application/x-krita` | ✅ | ❌ | Krita |
|
||||
| svg | `.svg` | `image/svg+xml` | ✅ | ❌ | |
|
||||
| xcf | `.xcf` | `application/x-xcf` | ❌ | ❌ | GIMP |
|
||||
| procreate | `.procreate` | `application/x-procreate`[^1] | ✅ | ❌ | Procreate app |
|
||||
| Filetype | Extension | MIME type | Thumbnails | Viewable in Hydrus | Notes |
|
||||
| --------- | ------------ | ----------------------------- | :--------: | :----------------: | -------------------------------------------------------------------------------- |
|
||||
| psd | `.psd` | `image/vnd.adobe.photoshop` | ✅ | ✅ | Adobe Photoshop. Hydrus shows the embedded preview image if present in the file. |
|
||||
| clip | `.clip` | `application/clip`[^1] | ✅ | ❌ | Clip Studio Paint |
|
||||
| sai2 | `.sai2` | `application/sai2`[^1] | ❌ | ❌ | PaintTool SAI2 |
|
||||
| krita | `.kra` | `application/x-krita` | ✅ | ✅ | Krita. Hydrus shows the embedded preview image if present in the file. |
|
||||
| svg | `.svg` | `image/svg+xml` | ✅ | ❌ | |
|
||||
| xcf | `.xcf` | `application/x-xcf` | ❌ | ❌ | GIMP |
|
||||
| procreate | `.procreate` | `application/x-procreate`[^1] | ✅ | ❌ | Procreate app |
|
||||
|
||||
|
||||
## Archives
|
||||
|
|
|
@ -81,6 +81,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
media_view[ HC.APPLICATION_PSD ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )
|
||||
|
||||
media_view[ HC.APPLICATION_KRITA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )
|
||||
|
||||
return media_view
|
||||
|
||||
|
||||
|
|
|
@ -11,20 +11,6 @@ def ExtractSingleFileFromZip( path_to_zip, filename_to_extract, extract_into_fil
|
|||
with open( extract_into_file_path, "wb" ) as writer:
|
||||
|
||||
writer.write( reader.read() )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def ReadSingleFileFromZip( path_to_zip, filename_to_extract ):
|
||||
|
||||
with zipfile.ZipFile( path_to_zip ) as zip_handle:
|
||||
|
||||
with zip_handle.open( filename_to_extract ) as reader:
|
||||
|
||||
return reader.read()
|
||||
|
||||
|
||||
|
||||
|
||||
def GetZipAsPath( path_to_zip, path_in_zip="" ):
|
||||
|
|
|
@ -890,7 +890,7 @@ ARCHIVES = [
|
|||
APPLICATION_ZIP
|
||||
]
|
||||
|
||||
VIEWABLE_IMAGE_PROJECT_FILES = { APPLICATION_PSD }
|
||||
VIEWABLE_IMAGE_PROJECT_FILES = { APPLICATION_PSD, APPLICATION_KRITA }
|
||||
|
||||
# zip files that have a `mimetype` file inside
|
||||
OPEN_DOCUMENT_ZIPS = { APPLICATION_KRITA, APPLICATION_EPUB }
|
||||
|
@ -927,7 +927,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_PDF, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_KRITA, APPLICATION_PROCREATE } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
|
||||
APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_PDF, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_PROCREATE } ).union( VIEWABLE_IMAGE_PROJECT_FILES )
|
||||
|
||||
MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( APPLICATIONS_WITH_THUMBNAILS )
|
||||
|
||||
|
|
|
@ -111,24 +111,21 @@ def GenerateThumbnailNumPy( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
elif mime == HC.APPLICATION_KRITA:
|
||||
|
||||
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath()
|
||||
|
||||
try:
|
||||
|
||||
HydrusKritaHandling.ExtractZippedImageToPath( path, temp_path )
|
||||
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
thumbnail_numpy = HydrusKritaHandling.GenerateThumbnailNumPyFromKraPath( path, target_resolution, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance( e, HydrusExceptions.NoThumbnailFileException ):
|
||||
|
||||
HydrusData.Print( 'Problem generating thumbnail for "{}":'.format( path ) )
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'krita.png' )
|
||||
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
|
||||
|
||||
|
||||
elif mime == HC.APPLICATION_PROCREATE:
|
||||
|
||||
|
|
|
@ -1,72 +1,83 @@
|
|||
import typing
|
||||
|
||||
from hydrus.core import HydrusArchiveHandling
|
||||
from hydrus.core import HydrusExceptions
|
||||
from hydrus.core import HydrusTemp
|
||||
from hydrus.core.images import HydrusImageHandling
|
||||
|
||||
from PIL import Image as PILImage
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import re
|
||||
|
||||
KRITA_FILE_THUMB = "preview.png"
|
||||
KRITA_FILE_MERGED = "mergedimage.png"
|
||||
|
||||
def ExtractZippedImageToPath( path_to_zip, temp_path_file ):
|
||||
|
||||
|
||||
def MergedPILImageFromKra(path):
|
||||
|
||||
try:
|
||||
|
||||
file_obj = HydrusArchiveHandling.GetZipAsPath( path, KRITA_FILE_MERGED ).open('rb')
|
||||
|
||||
return HydrusImageHandling.GeneratePILImage( file_obj )
|
||||
|
||||
HydrusArchiveHandling.ExtractSingleFileFromZip( path_to_zip, KRITA_FILE_MERGED, temp_path_file )
|
||||
except FileNotFoundError:
|
||||
|
||||
return
|
||||
|
||||
except KeyError:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
raise HydrusExceptions.UnsupportedFileException( f'Could not read {KRITA_FILE_MERGED} from this Krita file' )
|
||||
|
||||
|
||||
def ThumbnailPILImageFromKra(path):
|
||||
|
||||
try:
|
||||
|
||||
file_obj = HydrusArchiveHandling.GetZipAsPath( path, KRITA_FILE_THUMB ).open('rb')
|
||||
|
||||
return HydrusImageHandling.GeneratePILImage( file_obj )
|
||||
|
||||
HydrusArchiveHandling.ExtractSingleFileFromZip( path_to_zip, KRITA_FILE_THUMB, temp_path_file )
|
||||
except FileNotFoundError:
|
||||
|
||||
except KeyError:
|
||||
raise HydrusExceptions.NoThumbnailFileException( f'Could not read {KRITA_FILE_THUMB} from this Krita file' )
|
||||
|
||||
|
||||
def GenerateThumbnailNumPyFromKraPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
try:
|
||||
|
||||
pil_image = MergedPILImageFromKra( path )
|
||||
|
||||
except:
|
||||
|
||||
pil_image = ThumbnailPILImageFromKra( path )
|
||||
|
||||
if clip_rect is not None:
|
||||
|
||||
raise HydrusExceptions.NoThumbnailFileException( f'This krita file had no {KRITA_FILE_MERGED} or {KRITA_FILE_THUMB}!' )
|
||||
pil_image = HydrusImageHandling.ClipPILImage( pil_image, clip_rect )
|
||||
|
||||
thumbnail_pil_image = pil_image.resize( target_resolution, PILImage.LANCZOS )
|
||||
|
||||
numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( thumbnail_pil_image )
|
||||
|
||||
return numpy_image
|
||||
|
||||
|
||||
# TODO: animation and frame stuff which is also in the maindoc.xml
|
||||
def GetKraProperties( path ):
|
||||
|
||||
DOCUMENT_INFO_FILE = "maindoc.xml"
|
||||
|
||||
# TODO: probably actually parse the xml instead of using regex
|
||||
FIND_KEY_VALUE = re.compile(r"([a-z\-_]+)\s*=\s*['\"]([^'\"]+)", re.IGNORECASE)
|
||||
|
||||
width = None
|
||||
height = None
|
||||
|
||||
try:
|
||||
|
||||
with HydrusArchiveHandling.GetZipAsPath( path, DOCUMENT_INFO_FILE ).open('r') as reader:
|
||||
|
||||
for line in reader:
|
||||
|
||||
for match in FIND_KEY_VALUE.findall( line ):
|
||||
|
||||
key, value = match
|
||||
|
||||
if key == "width" and value.isdigit():
|
||||
|
||||
width = int(value)
|
||||
|
||||
if key == "height" and value.isdigit():
|
||||
|
||||
height = int(value)
|
||||
|
||||
if width is not None and height is not None:
|
||||
|
||||
break
|
||||
|
||||
|
||||
except KeyError:
|
||||
try:
|
||||
|
||||
data_file = HydrusArchiveHandling.GetZipAsPath( path, DOCUMENT_INFO_FILE ).open('rb')
|
||||
|
||||
root = ET.parse(data_file)
|
||||
|
||||
image_tag = root.find('{http://www.calligra.org/DTD/krita}IMAGE')
|
||||
|
||||
width = int(image_tag.attrib['width'])
|
||||
|
||||
raise HydrusExceptions.NoResolutionFileException( f'This krita file had no {DOCUMENT_INFO_FILE}!' )
|
||||
height = int(image_tag.attrib['height'])
|
||||
|
||||
|
||||
return ( width, height )
|
||||
return ( width, height )
|
||||
|
||||
except:
|
||||
|
||||
raise HydrusExceptions.NoResolutionFileException( f'This krita file had no {DOCUMENT_INFO_FILE} or it contains no resolution!' )
|
||||
|
|
|
@ -30,6 +30,7 @@ from hydrus.core import HydrusData
|
|||
from hydrus.core import HydrusExceptions
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
from hydrus.core import HydrusPSDHandling
|
||||
from hydrus.core import HydrusKritaHandling
|
||||
from hydrus.core.images import HydrusImageColours
|
||||
from hydrus.core.images import HydrusImageMetadata
|
||||
from hydrus.core.images import HydrusImageNormalisation
|
||||
|
@ -144,6 +145,16 @@ def GenerateNumPyImage( path, mime, force_pil = False ) -> numpy.array:
|
|||
|
||||
return HydrusImageNormalisation.StripOutAnyUselessAlphaChannel( numpy_image )
|
||||
|
||||
|
||||
if mime == HC.APPLICATION_KRITA:
|
||||
|
||||
if HG.media_load_report_mode:
|
||||
|
||||
HydrusData.ShowText( 'Loading KRA' )
|
||||
|
||||
pil_image = HydrusKritaHandling.MergedPILImageFromKra( path )
|
||||
|
||||
return GenerateNumPyImageFromPILImage( pil_image )
|
||||
|
||||
if mime in PIL_ONLY_MIMETYPES:
|
||||
|
||||
|
|
Loading…
Reference in New Issue