Add "viewable application" general filetype and make psd_tools optional
This commit is contained in:
parent
710f5f71cd
commit
a2ef817c61
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ) )
|
||||
|
|
|
@ -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 ):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ):
|
||||
|
|
|
@ -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 ] )
|
||||
|
|
|
@ -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 )
|
||||
|
||||
|
|
Loading…
Reference in New Issue