Add "viewable application" general filetype and make psd_tools optional

This commit is contained in:
Paul Friederichsen 2023-07-21 16:22:46 -05:00
parent 710f5f71cd
commit a2ef817c61
9 changed files with 98 additions and 23 deletions

View File

@ -2,7 +2,7 @@ import os
from qtpy import QtGui as QG
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusConstants as HC, HydrusPSDHandling
#
@ -217,7 +217,8 @@ media_viewer_capabilities = {
HC.GENERAL_IMAGE : static_full_support,
HC.GENERAL_VIDEO : animated_full_support,
HC.GENERAL_AUDIO : audio_full_support,
HC.GENERAL_APPLICATION : no_support
HC.GENERAL_APPLICATION : no_support,
HC.GENERAL_APPLICATION_VIEWABLE: static_full_support
}
for mime in HC.SEARCHABLE_MIMES:
@ -237,6 +238,10 @@ for mime in HC.SEARCHABLE_MIMES:
elif mime in HC.AUDIO:
media_viewer_capabilities[ mime ] = audio_full_support
elif mime in HC.VIEWABLE_APPLICATIONS:
media_viewer_capabilities[ mime ] = static_full_support
else:

View File

@ -76,6 +76,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
media_view[ HC.GENERAL_APPLICATION ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
media_view[ HC.GENERAL_APPLICATION_VIEWABLE ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, image_zoom_info )
return media_view

View File

@ -633,6 +633,13 @@ class ThumbnailCache( object ):
# override because these are a bit more
self._magic_mime_thumbnail_ease_score_lookup[ HC.IMAGE_APNG ] = 2
self._magic_mime_thumbnail_ease_score_lookup[ HC.IMAGE_GIF ] = 2
# could get more specific here because some applications will probably be even worse than videos
for mime in HC.APPLICATIONS_WITH_THUMBNAILS:
self._magic_mime_thumbnail_ease_score_lookup[ mime ] = 2
# ffmpeg hellzone

View File

@ -21,7 +21,7 @@ from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from hydrus.core import HydrusCompression
from hydrus.core import HydrusCompression, HydrusPSDHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusEncryption
@ -823,6 +823,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
library_version_lines.append( 'lz4 present: {}'.format( HydrusCompression.LZ4_OK ) )
library_version_lines.append( 'pympler present: {}'.format( HydrusMemory.PYMPLER_OK ) )
library_version_lines.append( 'pyopenssl present: {}'.format( HydrusEncryption.OPENSSL_OK ) )
library_version_lines.append( 'psd_tools present: {}'.format( HydrusPSDHandling.PSD_TOOLS_OK ) )
library_version_lines.append( 'speedcopy (experimental test) present: {}'.format( HydrusFileHandling.SPEEDCOPY_OK ) )
library_version_lines.append( 'install dir: {}'.format( HC.BASE_DIR ) )
library_version_lines.append( 'db dir: {}'.format( HG.client_controller.db_dir ) )

View File

@ -17,7 +17,7 @@ except:
pass
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusConstants as HC, HydrusPSDHandling
from hydrus.core import HydrusData
from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
@ -303,16 +303,21 @@ def GetShowAction( media: ClientMedia.MediaSingleton, canvas_type: int ):
if mime not in HC.ALLOWED_MIMES: # stopgap to catch a collection or application_unknown due to unusual import order/media moving
return bad_result
if canvas_type == CC.CANVAS_PREVIEW:
return HG.client_controller.new_options.GetPreviewShowAction( mime )
action = HG.client_controller.new_options.GetPreviewShowAction( mime )
else:
return HG.client_controller.new_options.GetMediaShowAction( mime )
action = HG.client_controller.new_options.GetMediaShowAction( mime )
if mime == HC.APPLICATION_PSD and not HydrusPSDHandling.PSD_TOOLS_OK and action[0] == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE:
# fall back to open externally button when psd_tools not available
action = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, start_paused, start_with_embed )
return action
def ShouldHaveAnimationBar( media, show_action ):

View File

@ -2015,7 +2015,7 @@ class MediaSingleton( Media ):
def IsImage( self ):
return self._media_result.GetMime() in HC.IMAGES
return self._media_result.GetMime() in HC.IMAGES or self._media_result.GetMime() in HC.VIEWABLE_APPLICATIONS
def IsSizeDefinite( self ): return self._media_result.GetSize() is not None

View File

@ -1,6 +1,6 @@
import typing
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusConstants as HC, HydrusPSDHandling
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
@ -173,10 +173,19 @@ class MediaResult( object ):
def IsStaticImage( self ):
image = self._file_info_manager.mime in HC.IMAGES
static_animation = self._file_info_manager.mime in HC.ANIMATIONS and self._file_info_manager.duration in ( 0, None )
if self._file_info_manager.mime in HC.IMAGES:
return True
return image or static_animation
if self._file_info_manager.mime in HC.ANIMATIONS and self._file_info_manager.duration in ( 0, None ):
return True
if self._file_info_manager.mime in HC.VIEWABLE_APPLICATIONS:
return True
return False
def ProcessContentUpdate( self, service_key, content_update ):

View File

@ -716,10 +716,11 @@ AUDIO_WAVPACK = 53
APPLICATION_SAI2 = 54
APPLICATION_KRITA = 55
IMAGE_SVG = 56
GENERAL_APPLICATION_VIEWABLE = 57
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
GENERAL_FILETYPES = { GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO, GENERAL_ANIMATION }
GENERAL_FILETYPES = { GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO, GENERAL_ANIMATION, GENERAL_APPLICATION_VIEWABLE }
SEARCHABLE_MIMES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, IMAGE_SVG, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_OGV, VIDEO_MPEG, APPLICATION_CLIP, APPLICATION_PSD, APPLICATION_SAI2, APPLICATION_KRITA, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_REALMEDIA, AUDIO_OGG, AUDIO_FLAC, AUDIO_WAVE, AUDIO_TRUEAUDIO, AUDIO_WMA, VIDEO_WMV, AUDIO_MKV, AUDIO_MP4, AUDIO_WAVPACK }
@ -729,7 +730,7 @@ ALLOWED_MIMES = set( STORABLE_MIMES ).union( { IMAGE_BMP } )
DECOMPRESSION_BOMB_IMAGES = { IMAGE_JPEG, IMAGE_PNG }
IMAGES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_PSD }
IMAGES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON }
ANIMATIONS = { IMAGE_GIF, IMAGE_APNG }
@ -739,8 +740,11 @@ VIDEO = { VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDE
APPLICATIONS = { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_SAI2, APPLICATION_KRITA, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z }
VIEWABLE_APPLICATIONS = { APPLICATION_PSD }
general_mimetypes_to_mime_groups = {
GENERAL_APPLICATION : APPLICATIONS,
GENERAL_APPLICATION_VIEWABLE : VIEWABLE_APPLICATIONS,
GENERAL_AUDIO : AUDIO,
GENERAL_IMAGE : IMAGES,
GENERAL_VIDEO : VIDEO,
@ -762,7 +766,9 @@ MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + li
ARCHIVES = { APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP, APPLICATION_RAR, APPLICATION_7Z }
MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_PSD, APPLICATION_KRITA } )
APPLICATIONS_WITH_THUMBNAILS = set({ IMAGE_SVG, APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_KRITA }).union( VIEWABLE_APPLICATIONS )
MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( APPLICATIONS_WITH_THUMBNAILS )
FILES_THAT_CAN_HAVE_ICC_PROFILE = { IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_TIFF }
@ -770,8 +776,9 @@ FILES_THAT_CAN_HAVE_EXIF = { IMAGE_JPEG, IMAGE_TIFF, IMAGE_PNG, IMAGE_WEBP }
# 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_PIXEL_HASH = set( IMAGES ).union( { IMAGE_GIF } )
FILES_THAT_HAVE_PERCEPTUAL_HASH = set( IMAGES )
FILES_THAT_CAN_HAVE_PIXEL_HASH = set( IMAGES ).union( VIEWABLE_APPLICATIONS ).union( { IMAGE_GIF } )
FILES_THAT_HAVE_PERCEPTUAL_HASH = set( IMAGES ).union( VIEWABLE_APPLICATIONS )
HYDRUS_UPDATE_FILES = ( APPLICATION_HYDRUS_UPDATE_DEFINITIONS, APPLICATION_HYDRUS_UPDATE_CONTENT )
@ -811,6 +818,7 @@ mime_enum_lookup = {
'application/hydrus-update-content' : APPLICATION_HYDRUS_UPDATE_CONTENT,
'application/hydrus-update-definitions' : APPLICATION_HYDRUS_UPDATE_DEFINITIONS,
'application' : APPLICATIONS,
'viewable application' : VIEWABLE_APPLICATIONS,
'audio/mp4' : AUDIO_M4A,
'audio/mp3' : AUDIO_MP3,
'audio/ogg' : AUDIO_OGG,
@ -898,7 +906,8 @@ mime_string_lookup = {
GENERAL_AUDIO : 'audio',
GENERAL_IMAGE : 'image',
GENERAL_VIDEO : 'video',
GENERAL_ANIMATION : 'animation'
GENERAL_ANIMATION : 'animation',
GENERAL_APPLICATION_VIEWABLE: 'viewable application'
}
mime_mimetype_string_lookup = {
@ -957,7 +966,8 @@ mime_mimetype_string_lookup = {
GENERAL_AUDIO : 'audio',
GENERAL_IMAGE : 'image',
GENERAL_VIDEO : 'video',
GENERAL_ANIMATION : 'animation'
GENERAL_ANIMATION : 'animation',
GENERAL_APPLICATION_VIEWABLE: 'viewable application'
}
mime_mimetype_string_lookup[ UNDETERMINED_WM ] = '{} or {}'.format( mime_mimetype_string_lookup[ AUDIO_WMA ], mime_mimetype_string_lookup[ VIDEO_WMV ] )

View File

@ -1,11 +1,22 @@
from psd_tools import PSDImage
from psd_tools.constants import ChannelID, Tag, ColorMode, Resource
import struct
import typing
from PIL import Image as PILImage
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusImageHandling
try:
from psd_tools import PSDImage
from psd_tools.constants import ChannelID, Tag, ColorMode, Resource
PSD_TOOLS_OK = False
except:
PSD_TOOLS_OK = False
def MergedPILImageFromPSD(path: str) -> PILImage:
@ -18,6 +29,7 @@ def MergedPILImageFromPSD(path: str) -> PILImage:
if(HydrusImageHandling.PILImageHasTransparency(pil_image) and no_alpha):
# merged image from psd-tools has transparency when it shouldn't
# see https://github.com/psd-tools/psd-tools/issues/369
# and https://github.com/psd-tools/psd-tools/pull/370
# I think it's fine to convert to RGB in all cases since eventually
# that has to happen for the thumbnail anyway.
@ -26,8 +38,12 @@ def MergedPILImageFromPSD(path: str) -> PILImage:
return pil_image
def GenerateThumbnailBytesFromPSDPath(path: str, target_resolution: tuple[int, int], clip_rect = None) -> bytes:
def GenerateThumbnailBytesFromPSDPath(path: str, target_resolution: typing.Tuple[int, int], clip_rect = None) -> bytes:
if not PSD_TOOLS_OK:
raise Exception( 'psd_tools unavailable' )
pil_image = MergedPILImageFromPSD(path)
if clip_rect is not None:
@ -45,8 +61,28 @@ def GenerateThumbnailBytesFromPSDPath(path: str, target_resolution: tuple[int, i
def GetPSDResolution(path: str):
if not PSD_TOOLS_OK:
return GetPSDResolutionFallback(path)
psd = PSDImage.open(path)
resolution = (psd.width, psd.height)
return resolution
return resolution
def GetPSDResolutionFallback(path: str):
with open( path, 'rb' ) as f:
f.seek( 14 )
height_bytes = f.read( 4 )
width_bytes = f.read( 4 )
height: int = struct.unpack( '>L', height_bytes )[0]
width: int = struct.unpack( '>L', width_bytes )[0]
return ( width, height )