Merge pull request #1396 from floogulinc/svg

Add SVG file support
This commit is contained in:
Hydrus Network Developer 2023-07-08 15:05:19 -05:00 committed by GitHub
commit 64f946c58f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 7 deletions

View File

@ -744,7 +744,7 @@ class ThumbnailCache( object ):
self._special_thumbs = {}
names = [ 'hydrus', 'pdf', 'psd', 'clip', 'sai', 'krita', 'audio', 'video', 'zip' ]
names = [ 'hydrus', 'pdf', 'psd', 'clip', 'sai', 'krita', 'svg', 'audio', 'video', 'zip' ]
bounding_dimensions = self._controller.options[ 'thumbnail_dimensions' ]
thumbnail_scale_type = self._controller.new_options.GetInteger( 'thumbnail_scale_type' )
@ -862,6 +862,7 @@ class ThumbnailCache( object ):
elif mime == HC.APPLICATION_PSD: return self._special_thumbs[ 'psd' ]
elif mime == HC.APPLICATION_SAI2: return self._special_thumbs[ 'sai' ]
elif mime == HC.APPLICATION_KRITA: return self._special_thumbs[ 'krita' ]
elif mime == HC.IMAGE_SVG: return self._special_thumbs[ 'svg' ]
elif mime in HC.ARCHIVES: return self._special_thumbs[ 'zip' ]
else: return self._special_thumbs[ 'hydrus' ]

View File

@ -715,12 +715,13 @@ APPLICATION_WINDOWS_EXE = 52
AUDIO_WAVPACK = 53
APPLICATION_SAI2 = 54
APPLICATION_KRITA = 55
IMAGE_SVG = 56
APPLICATION_OCTET_STREAM = 100
APPLICATION_UNKNOWN = 101
GENERAL_FILETYPES = { GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO, GENERAL_ANIMATION }
SEARCHABLE_MIMES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, 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 }
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 }
STORABLE_MIMES = set( SEARCHABLE_MIMES ).union( { APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS } )
@ -736,7 +737,7 @@ AUDIO = { AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WAVE, AUDIO_WMA, AU
VIDEO = { VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_OGV, VIDEO_MPEG }
APPLICATIONS = { APPLICATION_FLASH, APPLICATION_PSD, APPLICATION_CLIP, APPLICATION_SAI2, APPLICATION_KRITA, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z }
APPLICATIONS = { IMAGE_SVG, APPLICATION_FLASH, APPLICATION_PSD, APPLICATION_CLIP, APPLICATION_SAI2, APPLICATION_KRITA, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z }
general_mimetypes_to_mime_groups = {
GENERAL_APPLICATION : APPLICATIONS,
@ -761,7 +762,7 @@ 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( { APPLICATION_FLASH, APPLICATION_CLIP, APPLICATION_PSD, APPLICATION_KRITA } )
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 }
@ -787,6 +788,7 @@ mime_enum_lookup = {
'image/webp' : IMAGE_WEBP,
'image/tiff' : IMAGE_TIFF,
'image/x-icon' : IMAGE_ICON,
'image/svg+xml': IMAGE_SVG,
'image/vnd.microsoft.icon' : IMAGE_ICON,
'image' : IMAGES,
'application/x-shockwave-flash' : APPLICATION_FLASH,
@ -846,6 +848,7 @@ mime_string_lookup = {
IMAGE_WEBP : 'webp',
IMAGE_TIFF : 'tiff',
IMAGE_ICON : 'icon',
IMAGE_SVG : 'svg',
APPLICATION_FLASH : 'flash',
APPLICATION_OCTET_STREAM : 'application/octet-stream',
APPLICATION_YAML : 'yaml',
@ -907,6 +910,7 @@ mime_mimetype_string_lookup = {
IMAGE_WEBP : 'image/webp',
IMAGE_TIFF : 'image/tiff',
IMAGE_ICON : 'image/x-icon',
IMAGE_SVG : 'image/svg+xml',
APPLICATION_FLASH : 'application/x-shockwave-flash',
APPLICATION_OCTET_STREAM : 'application/octet-stream',
APPLICATION_YAML : 'application/x-yaml',
@ -969,6 +973,7 @@ mime_ext_lookup = {
IMAGE_WEBP : '.webp',
IMAGE_TIFF : '.tiff',
IMAGE_ICON : '.ico',
IMAGE_SVG : '.svg',
APPLICATION_FLASH : '.swf',
APPLICATION_OCTET_STREAM : '.bin',
APPLICATION_YAML : '.yaml',

View File

@ -5,6 +5,7 @@ import struct
from hydrus.core import HydrusAudioHandling
from hydrus.core import HydrusClipHandling
from hydrus.core import HydrusKritaHandling
from hydrus.core import HydrusSVGHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusDocumentHandling
@ -156,7 +157,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
finally:
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
elif mime == HC.APPLICATION_KRITA:
@ -178,7 +179,23 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
HydrusTemp.CleanUpTempPath( os_file_handle, temp_path )
elif mime == HC.IMAGE_SVG:
try:
thumbnail_bytes = HydrusSVGHandling.GenerateThumbnailBytesFromSVGPath( path, target_resolution, clip_rect = clip_rect )
except Exception as e:
HydrusData.Print( 'Problem generating thumbnail for "{}":'.format( path ) )
HydrusData.PrintException( e )
thumb_path = os.path.join( HC.STATIC_DIR, 'svg.png' )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
elif mime == HC.APPLICATION_FLASH:
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath()
@ -344,6 +361,10 @@ def GetFileInfo( path, mime = None, ok_to_look_for_hydrus_updates = False ):
( width, height ) = HydrusKritaHandling.GetKraProperties( path )
elif mime == HC.IMAGE_SVG:
( width, height ) = HydrusSVGHandling.GetSVGResolution( path )
elif mime == HC.APPLICATION_FLASH:
( ( width, height ), duration, num_frames ) = HydrusFlashHandling.GetFlashProperties( path )
@ -490,6 +511,9 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ):
return HC.TEXT_HTML
if HydrusText.LooksLikeSVG( bit_to_check ):
return HC.IMAGE_SVG
# it is important this goes at the end, because ffmpeg has a billion false positives!
# for instance, it once thought some hydrus update files were mpegs

View File

@ -879,6 +879,7 @@ def GetThumbnailResolutionAndClipRegion( image_resolution: typing.Tuple[ int, in
bounding_height = int( bounding_height * thumbnail_dpr )
bounding_width = int( bounding_width * thumbnail_dpr )
# TODO SVG thumbs should always scale up to the bounding dimensions
if thumbnail_scale_type == THUMBNAIL_SCALE_DOWN_ONLY:

View File

@ -25,6 +25,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.IMAGE_SVG ] = os.path.join( HC.STATIC_DIR, 'svg.png' )
for mime in HC.AUDIO:

View File

@ -0,0 +1,65 @@
from qtpy import QtSvg
from qtpy import QtGui as QG
from qtpy import QtCore as QC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusImageHandling
from hydrus.client.gui import ClientGUIFunctions
def LoadSVGRenderer(path: str):
renderer = QtSvg.QSvgRenderer();
try:
renderer.load(path)
except:
raise HydrusExceptions.DamagedOrUnusualFileException('Could not load SVG file.')
if not renderer.isValid():
raise HydrusExceptions.DamagedOrUnusualFileException('SVG file is invalid!')
return renderer
def GenerateThumbnailBytesFromSVGPath(path: str, target_resolution: tuple[int, int], clip_rect = None) -> bytes:
# TODO handle clipping
( target_width, target_height ) = target_resolution
renderer = LoadSVGRenderer(path)
# Seems to help for some weird floating point dimension SVGs
renderer.setAspectRatioMode(QC.Qt.AspectRatioMode.KeepAspectRatio)
try:
qt_image = QG.QImage( target_width, target_height, QG.QImage.Format_RGBA8888 )
qt_image.fill( QC.Qt.transparent )
painter = QG.QPainter(qt_image)
renderer.render(painter)
numpy_image = ClientGUIFunctions.ConvertQtImageToNumPy(qt_image)
painter.end()
return HydrusImageHandling.GenerateThumbnailBytesNumPy(numpy_image)
except:
raise HydrusExceptions.UnsupportedFileException()
def GetSVGResolution( path: str ):
renderer = LoadSVGRenderer(path)
resolution = renderer.defaultSize().toTuple()
return resolution

View File

@ -96,11 +96,33 @@ def LooksLikeHTML( file_data ):
if isinstance( file_data, bytes ):
search_elements = ( b'<html', b'<HTML', b'<title', b'<TITLE' )
search_elements = ( b'<html', b'<HTML', b'<!DOCTYPE html', b'<!DOCTYPE HTML' )
else:
search_elements = ( '<html', '<HTML', '<title', '<TITLE' )
search_elements = ( '<html', '<HTML', '<!DOCTYPE html', '<!DOCTYPE HTML' )
for s_e in search_elements:
if s_e in file_data:
return True
return False
def LooksLikeSVG( file_data ):
if isinstance( file_data, bytes ):
search_elements = ( b'<svg', b'<SVG', b'<!DOCTYPE svg', b'<!DOCTYPE SVG' )
else:
search_elements = ( '<svg', '<SVG', '<!DOCTYPE svg', '<!DOCTYPE SVG' )
for s_e in search_elements:

BIN
static/svg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB