Add blurhash (#1443)
* Start on blurhash * More blurhash db stuff * Refactor GenerateThumbnailBytes to add GenerateThumbnailNumPy * Add blurhash gen to import * Add blurhashes to db * Add blurhash to file metadata api * Add API docs for blurhash * Make sure we regen blurhash after thumb regen
This commit is contained in:
parent
428372fb57
commit
850a8c452e
|
@ -1486,6 +1486,7 @@ Arguments (in percent-encoded JSON):
|
|||
* `only_return_identifiers`: true or false (optional, defaulting to false)
|
||||
* `only_return_basic_information`: true or false (optional, defaulting to false)
|
||||
* `detailed_url_information`: true or false (optional, defaulting to false)
|
||||
* `include_blurhash`: true or false (optional, defaulting to false. Only applies when `only_return_basic_information` is true)
|
||||
* `include_notes`: true or false (optional, defaulting to false)
|
||||
* `include_services_object`: true or false (optional, defaulting to true)
|
||||
* `hide_service_keys_tags`: **Deprecated, will be deleted soon!** true or false (optional, defaulting to true)
|
||||
|
@ -1534,6 +1535,7 @@ Response:
|
|||
},
|
||||
"ipfs_multihashes" : {},
|
||||
"has_audio" : false,
|
||||
"blurhash" : "U6PZfSi_.AyE_3t7t7R**0o#DgR4_3R*D%xt",
|
||||
"num_frames" : null,
|
||||
"num_words" : null,
|
||||
"is_inbox" : false,
|
||||
|
@ -1605,6 +1607,7 @@ Response:
|
|||
"55af93e0deabd08ce15ffb2b164b06d1254daab5a18d145e56fa98f71ddb6f11" : "QmReHtaET3dsgh7ho5NVyHb5U13UgJoGipSWbZsnuuM8tb"
|
||||
},
|
||||
"has_audio" : true,
|
||||
"blurhash" : "UHF5?xYk^6#M@-5b,1J5@[or[k6.};FxngOZ",
|
||||
"num_frames" : 102,
|
||||
"num_words" : null,
|
||||
"is_inbox" : false,
|
||||
|
@ -1725,6 +1728,8 @@ Size is in bytes. Duration is in milliseconds, and may be an int or a float.
|
|||
|
||||
The `thumbnail_width` and `thumbnail_height` are a generally reliable prediction but aren't a promise. The actual thumbnail you get from [/get\_files/thumbnail](#get_files_thumbnail) will be different if the user hasn't looked at it since changing their thumbnail options. You only get these rows for files that hydrus actually generates an actual thumbnail for. Things like pdf won't have it. You can use your own thumb, or ask the api and it'll give you a fixed fallback; those are mostly 200x200, but you can and should size them to whatever you want.
|
||||
|
||||
`blurhash` gives a base 83 encoded string of a [blurhash](https://blurha.sh/) generated from the file's thumbnail if the file has a thumbnail.
|
||||
|
||||
#### tags
|
||||
|
||||
The `tags` structure is similar to the [/add\_tags/add\_tags](#add_tags_add_tags) scheme, excepting that the status numbers are:
|
||||
|
@ -1779,7 +1784,7 @@ You can change this behaviour with `create_new_file_ids=true`, but bear in mind
|
|||
|
||||
If you ask about file_ids that do not exist, you'll get 404.
|
||||
|
||||
If you set `only_return_basic_information=true`, this will be much faster for first-time requests than the full metadata result, but it will be slower for repeat requests. The full metadata object is cached after first fetch, the limited file info object is not.
|
||||
If you set `only_return_basic_information=true`, this will be much faster for first-time requests than the full metadata result, but it will be slower for repeat requests. The full metadata object is cached after first fetch, the limited file info object is not. You can optionally set `include_blurhash` when using this option to fetch blurhash strings for the files.
|
||||
|
||||
If you add `detailed_url_information=true`, a new entry, `detailed_known_urls`, will be added for each file, with a list of the same structure as /`add_urls/get_url_info`. This may be an expensive request if you are querying thousands of files at once.
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_LOG_ONLY = 18
|
|||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA = 19
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF = 20
|
||||
REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_DELETE_RECORD = 21
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH = 22
|
||||
|
||||
regen_file_enum_to_str_lookup = {
|
||||
REGENERATE_FILE_DATA_JOB_FILE_METADATA : 'regenerate file metadata',
|
||||
|
@ -72,7 +73,8 @@ regen_file_enum_to_str_lookup = {
|
|||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : 'determine if the file has EXIF metadata',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 'determine if the file has non-EXIF human-readable embedded metadata',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : 'determine if the file has an icc profile',
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 'regenerate pixel duplicate data'
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 'regenerate pixel duplicate data',
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH: 'regenerate blurhash'
|
||||
}
|
||||
|
||||
regen_file_enum_to_description_lookup = {
|
||||
|
@ -129,7 +131,8 @@ All missing/Incorrect files will also have their hashes, tags, and URLs exported
|
|||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : 'This loads the file to see if it has EXIF metadata, which can be shown in the media viewer and searched with "system:image has exif".',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 'This loads the file to see if it has non-EXIF human-readable metadata, which can be shown in the media viewer and searched with "system:image has human-readable embedded metadata".',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : 'This loads the file to see if it has an ICC profile, which is used in "system:has icc profile" search.',
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 'This generates a fast unique identifier for the pixels in a still image, which is used in duplicate pixel searches.'
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 'This generates a fast unique identifier for the pixels in a still image, which is used in duplicate pixel searches.',
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH: 'This generates a very small version of the file\'s thumbnail that can be used as a placeholder while the thumbnail loads'
|
||||
}
|
||||
|
||||
NORMALISED_BIG_JOB_WEIGHT = 100
|
||||
|
@ -156,7 +159,8 @@ regen_file_enum_to_job_weight_lookup = {
|
|||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : 25,
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 25,
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : 25,
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 100
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : 100,
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH: 25
|
||||
}
|
||||
|
||||
regen_file_enum_to_overruled_jobs = {
|
||||
|
@ -181,7 +185,8 @@ regen_file_enum_to_overruled_jobs = {
|
|||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : [],
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : [],
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : [],
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : []
|
||||
REGENERATE_FILE_DATA_JOB_PIXEL_HASH : [],
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH: []
|
||||
}
|
||||
|
||||
ALL_REGEN_JOBS_IN_PREFERRED_ORDER = [
|
||||
|
@ -197,6 +202,7 @@ ALL_REGEN_JOBS_IN_PREFERRED_ORDER = [
|
|||
REGENERATE_FILE_DATA_JOB_FILE_METADATA,
|
||||
REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL,
|
||||
REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL,
|
||||
REGENERATE_FILE_DATA_JOB_BLURHASH,
|
||||
REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA,
|
||||
REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP,
|
||||
REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS,
|
||||
|
@ -1677,7 +1683,8 @@ class ClientFilesManager( object ):
|
|||
|
||||
self._AddThumbnailFromBytes( hash, thumbnail_bytes )
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def RegenerateThumbnailIfWrongSize( self, media ):
|
||||
|
||||
|
@ -1725,6 +1732,29 @@ class ClientFilesManager( object ):
|
|||
|
||||
|
||||
return do_it
|
||||
|
||||
def RegenerateImageBlurHash( self, media ):
|
||||
|
||||
hash = media.GetHash()
|
||||
mime = media.GetMime()
|
||||
|
||||
if mime not in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
thumbnail_path = self._GenerateExpectedThumbnailPath( hash )
|
||||
|
||||
thumbnail_mime = HydrusFileHandling.GetThumbnailMime( thumbnail_path )
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumPyImage( thumbnail_path, thumbnail_mime )
|
||||
|
||||
return HydrusImageHandling.GetImageBlurHashNumPy(numpy_image)
|
||||
|
||||
except:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def Reinit( self ):
|
||||
|
@ -2371,7 +2401,8 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
try:
|
||||
|
||||
self._controller.client_files_manager.RegenerateThumbnail( media_result )
|
||||
return self._controller.client_files_manager.RegenerateThumbnail( media_result )
|
||||
|
||||
|
||||
except HydrusExceptions.FileMissingException:
|
||||
|
||||
|
@ -2439,6 +2470,9 @@ class FilesMaintenanceManager( object ):
|
|||
return None
|
||||
|
||||
|
||||
def _RegenBlurHash( self, media ):
|
||||
|
||||
return self._controller.client_files_manager.RegenerateImageBlurHash( media )
|
||||
|
||||
def _RegenSimilarFilesMetadata( self, media_result ):
|
||||
|
||||
|
@ -2560,11 +2594,13 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
elif job_type == REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL:
|
||||
|
||||
self._RegenFileThumbnailForce( media_result )
|
||||
additional_data = self._RegenFileThumbnailForce( media_result )
|
||||
|
||||
elif job_type == REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL:
|
||||
|
||||
was_regenerated = self._RegenFileThumbnailRefit( media_result )
|
||||
|
||||
additional_data = was_regenerated
|
||||
|
||||
if was_regenerated:
|
||||
|
||||
|
@ -2595,6 +2631,10 @@ class FilesMaintenanceManager( object ):
|
|||
elif job_type == REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS:
|
||||
|
||||
self._FixFilePermissions( media_result )
|
||||
|
||||
elif job_type == REGENERATE_FILE_DATA_JOB_BLURHASH:
|
||||
|
||||
additional_data = self._RegenBlurHash( media_result )
|
||||
|
||||
elif job_type in (
|
||||
REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_REMOVE_RECORD,
|
||||
|
|
|
@ -75,7 +75,7 @@ def LoadPDF( path: str ):
|
|||
return document
|
||||
|
||||
|
||||
def GenerateThumbnailBytesFromPDFPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
def GenerateThumbnailNumPyFromPDFPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
try:
|
||||
|
||||
|
@ -114,7 +114,7 @@ def GenerateThumbnailBytesFromPDFPath( path: str, target_resolution: typing.Tupl
|
|||
thumbnail_numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution )
|
||||
|
||||
|
||||
return HydrusImageHandling.GenerateThumbnailBytesNumPy( thumbnail_numpy_image )
|
||||
return thumbnail_numpy_image
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -126,7 +126,7 @@ def GenerateThumbnailBytesFromPDFPath( path: str, target_resolution: typing.Tupl
|
|||
|
||||
|
||||
|
||||
HydrusPDFHandling.GenerateThumbnailBytesFromPDFPath = GenerateThumbnailBytesFromPDFPath
|
||||
HydrusPDFHandling.GenerateThumbnailNumPyFromPDFPath = GenerateThumbnailNumPyFromPDFPath
|
||||
|
||||
PDF_ASSUMED_DPI = 300
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ def LoadSVGRenderer( path: str ):
|
|||
return renderer
|
||||
|
||||
|
||||
def GenerateThumbnailBytesFromSVGPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
def GenerateThumbnailNumPyFromSVGPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
# TODO: SVGs have no inherent resolution, so all this is pretty stupid. we should render to exactly the res we want and then clip the result, not beforehand
|
||||
|
||||
|
@ -74,7 +74,7 @@ def GenerateThumbnailBytesFromSVGPath( path: str, target_resolution: typing.Tupl
|
|||
thumbnail_numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution )
|
||||
|
||||
|
||||
return HydrusImageHandling.GenerateThumbnailBytesNumPy( thumbnail_numpy_image )
|
||||
return thumbnail_numpy_image
|
||||
|
||||
except:
|
||||
|
||||
|
@ -82,7 +82,7 @@ def GenerateThumbnailBytesFromSVGPath( path: str, target_resolution: typing.Tupl
|
|||
|
||||
|
||||
|
||||
HydrusSVGHandling.GenerateThumbnailBytesFromSVGPath = GenerateThumbnailBytesFromSVGPath
|
||||
HydrusSVGHandling.GenerateThumbnailNumPyFromSVGPath = GenerateThumbnailNumPyFromSVGPath
|
||||
|
||||
def GetSVGResolution( path: str ):
|
||||
|
||||
|
|
|
@ -2775,7 +2775,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return boned_stats
|
||||
|
||||
|
||||
def _GetFileInfoManagers( self, hash_ids: typing.Collection[ int ], sorted = False ) -> typing.List[ ClientMediaManagers.FileInfoManager ]:
|
||||
def _GetFileInfoManagers( self, hash_ids: typing.Collection[ int ], sorted = False, blurhash = False ) -> typing.List[ ClientMediaManagers.FileInfoManager ]:
|
||||
|
||||
( cached_media_results, missing_hash_ids ) = self._weakref_media_result_cache.GetMediaResultsAndMissing( hash_ids )
|
||||
|
||||
|
@ -2790,6 +2790,9 @@ class DB( HydrusDB.HydrusDB ):
|
|||
# temp hashes to metadata
|
||||
hash_ids_to_info = { hash_id : ClientMediaManagers.FileInfoManager( hash_id, missing_hash_ids_to_hashes[ hash_id ], size, mime, width, height, duration, num_frames, has_audio, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, has_audio, num_words ) in self._Execute( 'SELECT * FROM {} CROSS JOIN files_info USING ( hash_id );'.format( temp_table_name ) ) }
|
||||
|
||||
if blurhash:
|
||||
|
||||
hash_ids_to_blurhash = self.modules_files_metadata_basic.GetHashIdsToBlurHash( temp_table_name )
|
||||
|
||||
# build it
|
||||
|
||||
|
@ -2798,6 +2801,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
if hash_id in hash_ids_to_info:
|
||||
|
||||
file_info_manager = hash_ids_to_info[ hash_id ]
|
||||
|
||||
if blurhash and hash_id in hash_ids_to_blurhash:
|
||||
|
||||
file_info_manager.blurhash = hash_ids_to_blurhash[hash_id]
|
||||
|
||||
else:
|
||||
|
||||
|
@ -3295,6 +3302,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
hash_ids_to_tags_managers = self._GetForceRefreshTagsManagersWithTableHashIds( missing_hash_ids, temp_table_name, hash_ids_to_current_file_service_ids = hash_ids_to_current_file_service_ids )
|
||||
|
||||
hash_ids_to_blurhash = self.modules_files_metadata_basic.GetHashIdsToBlurHash( temp_table_name )
|
||||
|
||||
has_exif_hash_ids = self.modules_files_metadata_basic.GetHasEXIFHashIds( temp_table_name )
|
||||
has_human_readable_embedded_metadata_hash_ids = self.modules_files_metadata_basic.GetHasHumanReadableEmbeddedMetadataHashIds( temp_table_name )
|
||||
has_icc_profile_hash_ids = self.modules_files_metadata_basic.GetHasICCProfileHashIds( temp_table_name )
|
||||
|
@ -3412,6 +3421,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
file_info_manager.has_exif = hash_id in has_exif_hash_ids
|
||||
file_info_manager.has_human_readable_embedded_metadata = hash_id in has_human_readable_embedded_metadata_hash_ids
|
||||
file_info_manager.has_icc_profile = hash_id in has_icc_profile_hash_ids
|
||||
|
||||
if hash_id in hash_ids_to_blurhash:
|
||||
|
||||
file_info_manager.blurhash = hash_ids_to_blurhash[hash_id]
|
||||
|
||||
missing_media_results.append( ClientMediaResult.MediaResult( file_info_manager, tags_manager, timestamps_manager, locations_manager, ratings_manager, notes_manager, file_viewing_stats_manager ) )
|
||||
|
||||
|
@ -4614,6 +4627,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
self.modules_files_metadata_basic.SetHasHumanReadableEmbeddedMetadata( hash_id, file_import_job.HasHumanReadableEmbeddedMetadata() )
|
||||
self.modules_files_metadata_basic.SetHasICCProfile( hash_id, file_import_job.HasICCProfile() )
|
||||
|
||||
self.modules_files_metadata_basic.SetBlurHash( hash_id, file_import_job.GetBlurhash())
|
||||
|
||||
#
|
||||
|
||||
file_modified_timestamp = file_import_job.GetFileModifiedTimestamp()
|
||||
|
|
|
@ -180,8 +180,16 @@ class ClientDBFilesMaintenance( ClientDBModule.ClientDBModule ):
|
|||
if self.modules_similar_files.FileIsInSystem( hash_id ):
|
||||
|
||||
self.modules_similar_files.StopSearchingFile( hash_id )
|
||||
|
||||
elif job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL or job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL:
|
||||
|
||||
self.modules_files_maintenance_queue.AddJobs( ( hash_id, ), ClientFiles.REGENERATE_FILE_DATA_JOB_BLURHASH )
|
||||
|
||||
|
||||
elif job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_BLURHASH:
|
||||
|
||||
blurhash: str = additional_data
|
||||
|
||||
self.modules_files_metadata_basic.SetBlurHash( hash_id, blurhash )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ class ClientDBFilesMetadataBasic( ClientDBModule.ClientDBModule ):
|
|||
'main.files_info' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY, size INTEGER, mime INTEGER, width INTEGER, height INTEGER, duration INTEGER, num_frames INTEGER, has_audio INTEGER_BOOLEAN, num_words INTEGER );', 400 ),
|
||||
'main.has_icc_profile' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY );', 465 ),
|
||||
'main.has_exif' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY );', 505 ),
|
||||
'main.has_human_readable_embedded_metadata' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY );', 505 )
|
||||
'main.has_human_readable_embedded_metadata' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY );', 505 ),
|
||||
'main.blurhash' : ( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY, blurhash TEXT );', 545 ),
|
||||
}
|
||||
|
||||
|
||||
|
@ -107,7 +108,8 @@ class ClientDBFilesMetadataBasic( ClientDBModule.ClientDBModule ):
|
|||
( 'files_info', 'hash_id' ),
|
||||
( 'has_exif', 'hash_id' ),
|
||||
( 'has_human_readable_embedded_metadata', 'hash_id' ),
|
||||
( 'has_icc_profile', 'hash_id' )
|
||||
( 'has_icc_profile', 'hash_id' ),
|
||||
( 'blurhash', 'hash_id' )
|
||||
]
|
||||
|
||||
|
||||
|
@ -215,6 +217,31 @@ class ClientDBFilesMetadataBasic( ClientDBModule.ClientDBModule ):
|
|||
else:
|
||||
|
||||
self._Execute( 'DELETE FROM has_icc_profile WHERE hash_id = ?;', ( hash_id, ) )
|
||||
|
||||
def SetBlurHash( self, hash_id: int, blurhash: str ):
|
||||
|
||||
# TODO blurhash db stuff
|
||||
|
||||
self._Execute('INSERT OR REPLACE INTO blurhash ( hash_id, blurhash ) VALUES ( ?, ?);', (hash_id, blurhash))
|
||||
|
||||
|
||||
def GetBlurHash( self, hash_id: int ) -> str:
|
||||
|
||||
result = self._Execute( 'SELECT blurhash FROM blurhash WHERE hash_id = ?;', ( hash_id, ) ).fetchone()
|
||||
|
||||
# TODO blurhash db stuff
|
||||
|
||||
if result is None:
|
||||
|
||||
raise HydrusExceptions.DataMissing( 'Did not have blurhash information for that file!' )
|
||||
|
||||
|
||||
( blurhash, ) = result
|
||||
|
||||
return blurhash
|
||||
|
||||
def GetHashIdsToBlurHash( self, hash_ids_table_name: str ):
|
||||
|
||||
return dict( self._Execute( 'SELECT hash_id, blurhash FROM {} CROSS JOIN blurhash USING ( hash_id );'.format( hash_ids_table_name ) ) )
|
||||
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ class FileImportJob( object ):
|
|||
self._has_icc_profile = None
|
||||
self._pixel_hash = None
|
||||
self._file_modified_timestamp = None
|
||||
self._blurhash = None
|
||||
|
||||
|
||||
def CheckIsGoodToImport( self ):
|
||||
|
@ -353,9 +354,20 @@ class FileImportJob( object ):
|
|||
|
||||
percentage_in = HG.client_controller.new_options.GetInteger( 'video_thumbnail_percentage_in' )
|
||||
|
||||
thumbnail_numpy = HydrusFileHandling.GenerateThumbnailNumPy(self._temp_path, target_resolution, mime, duration, num_frames, clip_rect = clip_rect, percentage_in = percentage_in)
|
||||
|
||||
# this guy handles almost all his own exceptions now, so no need for clever catching. if it fails, we are prob talking an I/O failure, which is not a 'thumbnail failed' error
|
||||
self._thumbnail_bytes = HydrusFileHandling.GenerateThumbnailBytes( self._temp_path, target_resolution, mime, duration, num_frames, clip_rect = clip_rect, percentage_in = percentage_in )
|
||||
self._thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesNumPy( thumbnail_numpy )
|
||||
|
||||
try:
|
||||
|
||||
self._blurhash = HydrusImageHandling.GetImageBlurHashNumPy( thumbnail_numpy )
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if mime in HC.FILES_THAT_HAVE_PERCEPTUAL_HASH:
|
||||
|
||||
|
@ -503,6 +515,10 @@ class FileImportJob( object ):
|
|||
def HasICCProfile( self ) -> bool:
|
||||
|
||||
return self._has_icc_profile
|
||||
|
||||
def GetBlurhash( self ) -> str:
|
||||
|
||||
return self._blurhash
|
||||
|
||||
|
||||
def PubsubContentUpdates( self ):
|
||||
|
|
|
@ -76,6 +76,7 @@ class FileInfoManager( object ):
|
|||
self.has_exif = False
|
||||
self.has_human_readable_embedded_metadata = False
|
||||
self.has_icc_profile = False
|
||||
self.blurhash = None
|
||||
|
||||
|
||||
def Duplicate( self ):
|
||||
|
|
|
@ -66,7 +66,7 @@ LOCAL_BOORU_JSON_BYTE_LIST_PARAMS = set()
|
|||
CLIENT_API_INT_PARAMS = { 'file_id', 'file_sort_type', 'potentials_search_type', 'pixel_duplicates', 'max_hamming_distance', 'max_num_pairs' }
|
||||
CLIENT_API_BYTE_PARAMS = { 'hash', 'destination_page_key', 'page_key', 'service_key', 'Hydrus-Client-API-Access-Key', 'Hydrus-Client-API-Session-Key', 'file_service_key', 'deleted_file_service_key', 'tag_service_key', 'tag_service_key_1', 'tag_service_key_2', 'rating_service_key' }
|
||||
CLIENT_API_STRING_PARAMS = { 'name', 'url', 'domain', 'search', 'service_name', 'reason', 'tag_display_type', 'source_hash_type', 'desired_hash_type' }
|
||||
CLIENT_API_JSON_PARAMS = { 'basic_permissions', 'tags', 'tags_1', 'tags_2', 'file_ids', 'download', 'only_return_identifiers', 'only_return_basic_information', 'create_new_file_ids', 'detailed_url_information', 'hide_service_keys_tags', 'simple', 'file_sort_asc', 'return_hashes', 'return_file_ids', 'include_notes', 'include_services_object', 'notes', 'note_names', 'doublecheck_file_system' }
|
||||
CLIENT_API_JSON_PARAMS = { 'basic_permissions', 'tags', 'tags_1', 'tags_2', 'file_ids', 'download', 'only_return_identifiers', 'only_return_basic_information', 'include_blurhash', 'create_new_file_ids', 'detailed_url_information', 'hide_service_keys_tags', 'simple', 'file_sort_asc', 'return_hashes', 'return_file_ids', 'include_notes', 'include_services_object', 'notes', 'note_names', 'doublecheck_file_system' }
|
||||
CLIENT_API_JSON_BYTE_LIST_PARAMS = { 'file_service_keys', 'deleted_file_service_keys', 'hashes' }
|
||||
CLIENT_API_JSON_BYTE_DICT_PARAMS = { 'service_keys_to_tags', 'service_keys_to_actions_to_tags', 'service_keys_to_additional_tags' }
|
||||
|
||||
|
@ -2958,6 +2958,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
include_notes = request.parsed_request_args.GetValue( 'include_notes', bool, default_value = False )
|
||||
include_services_object = request.parsed_request_args.GetValue( 'include_services_object', bool, default_value = True )
|
||||
create_new_file_ids = request.parsed_request_args.GetValue( 'create_new_file_ids', bool, default_value = False )
|
||||
include_blurhash = request.parsed_request_args.GetValue( 'include_blurhash', bool, default_value = False )
|
||||
|
||||
hashes = ParseHashes( request )
|
||||
|
||||
|
@ -2994,7 +2995,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
|
||||
elif only_return_basic_information:
|
||||
|
||||
file_info_managers = HG.client_controller.Read( 'file_info_managers_from_ids', hash_ids )
|
||||
file_info_managers = HG.client_controller.Read( 'file_info_managers_from_ids', hash_ids, blurhash = include_blurhash )
|
||||
|
||||
hashes_to_file_info_managers = { file_info_manager.hash : file_info_manager for file_info_manager in file_info_managers }
|
||||
|
||||
|
@ -3019,6 +3020,10 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
'num_words' : file_info_manager.num_words,
|
||||
'has_audio' : file_info_manager.has_audio
|
||||
}
|
||||
|
||||
if include_blurhash:
|
||||
|
||||
metadata_row['blurhash'] = file_info_manager.blurhash
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
@ -3072,7 +3077,8 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
'duration' : file_info_manager.duration,
|
||||
'num_frames' : file_info_manager.num_frames,
|
||||
'num_words' : file_info_manager.num_words,
|
||||
'has_audio' : file_info_manager.has_audio
|
||||
'has_audio' : file_info_manager.has_audio,
|
||||
'blurhash' : file_info_manager.blurhash
|
||||
}
|
||||
|
||||
if file_info_manager.mime in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
|
|
@ -116,6 +116,11 @@ headers_and_mime.extend( [
|
|||
|
||||
def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames, clip_rect = None, percentage_in = 35 ):
|
||||
|
||||
thumbnail_numpy = GenerateThumbnailNumPy(path, target_resolution, mime, duration, num_frames, clip_rect, percentage_in )
|
||||
|
||||
return HydrusImageHandling.GenerateThumbnailBytesNumPy(thumbnail_numpy)
|
||||
|
||||
def GenerateThumbnailNumPy( path, target_resolution, mime, duration, num_frames, clip_rect = None, percentage_in = 35 ):
|
||||
if target_resolution == ( 0, 0 ):
|
||||
|
||||
target_resolution = ( 128, 128 )
|
||||
|
@ -125,7 +130,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
try:
|
||||
|
||||
thumbnail_bytes = HydrusPSDHandling.GenerateThumbnailBytesFromPSDPath( path, target_resolution, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusPSDHandling.GenerateThumbnailNumPyFromPSDPath( path, target_resolution, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -139,13 +144,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
HydrusVideoHandling.RenderImageToImagePath( path, temp_path )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'psd.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -161,13 +166,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
HydrusClipHandling.ExtractDBPNGToPath( path, temp_path )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
except:
|
||||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'clip.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -182,13 +187,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
HydrusKritaHandling.ExtractZippedImageToPath( path, temp_path )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'krita.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -202,13 +207,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
HydrusProcreateHandling.ExtractZippedThumbnailToPath( path, temp_path )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( 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 )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -219,7 +224,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
try:
|
||||
|
||||
thumbnail_bytes = HydrusSVGHandling.GenerateThumbnailBytesFromSVGPath( path, target_resolution, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusSVGHandling.GenerateThumbnailNumPyFromSVGPath( path, target_resolution, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -231,14 +236,14 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
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 )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
|
||||
elif mime == HC.APPLICATION_PDF:
|
||||
|
||||
try:
|
||||
|
||||
thumbnail_bytes = HydrusPDFHandling.GenerateThumbnailBytesFromPDFPath( path, target_resolution, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusPDFHandling.GenerateThumbnailNumPyFromPDFPath( path, target_resolution, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -250,7 +255,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'pdf.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
|
||||
elif mime == HC.APPLICATION_FLASH:
|
||||
|
@ -261,13 +266,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
HydrusFlashHandling.RenderPageToFile( path, temp_path, 1 )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
except:
|
||||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'flash.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
finally:
|
||||
|
||||
|
@ -280,7 +285,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
try:
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( path, target_resolution, mime, clip_rect = clip_rect )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -289,7 +294,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'hydrus.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -340,13 +345,11 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
thumb_path = os.path.join( HC.STATIC_DIR, 'hydrus.png' )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
thumbnail_numpy = HydrusImageHandling.GenerateThumbnailNumPyFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
|
||||
|
||||
else:
|
||||
|
||||
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution ) # just in case ffmpeg doesn't deliver right
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesNumPy( numpy_image )
|
||||
thumbnail_numpy = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution ) # just in case ffmpeg doesn't deliver right
|
||||
|
||||
|
||||
if renderer is not None:
|
||||
|
@ -355,7 +358,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
|
|||
|
||||
|
||||
|
||||
return thumbnail_bytes
|
||||
return thumbnail_numpy
|
||||
|
||||
def GetExtraHashesFromPath( path ):
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ from hydrus.core import HydrusPaths
|
|||
from hydrus.core import HydrusTemp
|
||||
from hydrus.core import HydrusPSDHandling
|
||||
|
||||
from hydrus.external import blurhash
|
||||
|
||||
PIL_SRGB_PROFILE = PILImageCms.createProfile( 'sRGB' )
|
||||
|
||||
def EnableLoadTruncatedImages():
|
||||
|
@ -436,7 +438,7 @@ def GeneratePILImageFromNumPyImage( numpy_image: numpy.array ) -> PILImage.Image
|
|||
|
||||
return pil_image
|
||||
|
||||
def GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, clip_rect = None ) -> bytes:
|
||||
def GenerateThumbnailNumPyFromStaticImagePath( path, target_resolution, mime, clip_rect = None ):
|
||||
|
||||
if OPENCV_OK:
|
||||
|
||||
|
@ -449,18 +451,9 @@ def GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, cl
|
|||
|
||||
thumbnail_numpy_image = ResizeNumPyImage( numpy_image, target_resolution )
|
||||
|
||||
try:
|
||||
|
||||
thumbnail_bytes = GenerateThumbnailBytesNumPy( thumbnail_numpy_image )
|
||||
|
||||
return thumbnail_bytes
|
||||
|
||||
except HydrusExceptions.CantRenderWithCVException:
|
||||
|
||||
pass # fallback to PIL
|
||||
return thumbnail_numpy_image
|
||||
|
||||
|
||||
|
||||
pil_image = GeneratePILImage( path )
|
||||
|
||||
if clip_rect is not None:
|
||||
|
@ -470,9 +463,9 @@ def GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, cl
|
|||
|
||||
thumbnail_pil_image = pil_image.resize( target_resolution, PILImage.LANCZOS )
|
||||
|
||||
thumbnail_bytes = GenerateThumbnailBytesPIL( thumbnail_pil_image )
|
||||
thumbnail_numpy_image = GenerateNumPyImageFromPILImage(thumbnail_pil_image)
|
||||
|
||||
return thumbnail_bytes
|
||||
return thumbnail_numpy_image
|
||||
|
||||
def GenerateThumbnailBytesNumPy( numpy_image ) -> bytes:
|
||||
|
||||
|
@ -1253,3 +1246,6 @@ def StripOutAnyUselessAlphaChannel( numpy_image: numpy.array ) -> numpy.array:
|
|||
|
||||
return numpy_image
|
||||
|
||||
def GetImageBlurHashNumPy( numpy_image, components_x = 4, components_y = 4 ):
|
||||
|
||||
return blurhash.blurhash_encode( numpy_image, components_x, components_y )
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing
|
|||
|
||||
from hydrus.core import HydrusExceptions
|
||||
|
||||
def BaseGenerateThumbnailBytesFromPDFPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
def BaseGenerateThumbnailNumPyFromPDFPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
raise HydrusExceptions.NoThumbnailFileException()
|
||||
|
||||
|
@ -12,5 +12,5 @@ def BaseGetPDFInfo( path: str ):
|
|||
raise HydrusExceptions.LimitedSupportFileException()
|
||||
|
||||
|
||||
GenerateThumbnailBytesFromPDFPath = BaseGenerateThumbnailBytesFromPDFPath
|
||||
GenerateThumbnailNumPyFromPDFPath = BaseGenerateThumbnailNumPyFromPDFPath
|
||||
GetPDFInfo = BaseGetPDFInfo
|
||||
|
|
|
@ -37,7 +37,7 @@ def MergedPILImageFromPSD( path: str ) -> PILImage:
|
|||
return HydrusPSDTools.MergedPILImageFromPSD( path )
|
||||
|
||||
|
||||
def GenerateThumbnailBytesFromPSDPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
def GenerateThumbnailNumPyFromPSDPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
pil_image = MergedPILImageFromPSD( path )
|
||||
|
||||
|
@ -48,9 +48,7 @@ def GenerateThumbnailBytesFromPSDPath( path: str, target_resolution: typing.Tupl
|
|||
|
||||
thumbnail_pil_image = pil_image.resize( target_resolution, PILImage.LANCZOS )
|
||||
|
||||
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesPIL( thumbnail_pil_image )
|
||||
|
||||
return thumbnail_bytes
|
||||
return HydrusImageHandling.GenerateNumPyImageFromPILImage(thumbnail_pil_image)
|
||||
|
||||
|
||||
def GetPSDResolution( path: str ):
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing
|
|||
|
||||
from hydrus.core import HydrusExceptions
|
||||
|
||||
def BaseGenerateThumbnailBytesFromSVGPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
def BaseGenerateThumbnailNumPyFromSVGPath( path: str, target_resolution: typing.Tuple[int, int], clip_rect = None ) -> bytes:
|
||||
|
||||
raise HydrusExceptions.NoThumbnailFileException()
|
||||
|
||||
|
@ -12,5 +12,5 @@ def BaseGetSVGResolution( path: str ):
|
|||
raise HydrusExceptions.NoResolutionFileException()
|
||||
|
||||
|
||||
GenerateThumbnailBytesFromSVGPath = BaseGenerateThumbnailBytesFromSVGPath
|
||||
GenerateThumbnailNumPyFromSVGPath = BaseGenerateThumbnailNumPyFromSVGPath
|
||||
GetSVGResolution = BaseGetSVGResolution
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
"""
|
||||
Pure python blurhash decoder with no additional dependencies, for
|
||||
both de- and encoding.
|
||||
|
||||
Very close port of the original Swift implementation by Dag Ågren.
|
||||
"""
|
||||
|
||||
"""
|
||||
From https://github.com/halcy/blurhash-python
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Lorenz Diener
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
# Alphabet for base 83
|
||||
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
|
||||
alphabet_values = dict(zip(alphabet, range(len(alphabet))))
|
||||
|
||||
def base83_decode(base83_str):
|
||||
"""
|
||||
Decodes a base83 string, as used in blurhash, to an integer.
|
||||
"""
|
||||
value = 0
|
||||
for base83_char in base83_str:
|
||||
value = value * 83 + alphabet_values[base83_char]
|
||||
return value
|
||||
|
||||
def base83_encode(value, length):
|
||||
"""
|
||||
Decodes an integer to a base83 string, as used in blurhash.
|
||||
|
||||
Length is how long the resulting string should be. Will complain
|
||||
if the specified length is too short.
|
||||
"""
|
||||
if int(value) // (83 ** (length)) != 0:
|
||||
raise ValueError("Specified length is too short to encode given value.")
|
||||
|
||||
result = ""
|
||||
for i in range(1, length + 1):
|
||||
digit = int(value) // (83 ** (length - i)) % 83
|
||||
result += alphabet[int(digit)]
|
||||
return result
|
||||
|
||||
def srgb_to_linear(value):
|
||||
"""
|
||||
srgb 0-255 integer to linear 0.0-1.0 floating point conversion.
|
||||
"""
|
||||
value = float(value) / 255.0
|
||||
if value <= 0.04045:
|
||||
return value / 12.92
|
||||
return math.pow((value + 0.055) / 1.055, 2.4)
|
||||
|
||||
def sign_pow(value, exp):
|
||||
"""
|
||||
Sign-preserving exponentiation.
|
||||
"""
|
||||
return math.copysign(math.pow(abs(value), exp), value)
|
||||
|
||||
def linear_to_srgb(value):
|
||||
"""
|
||||
linear 0.0-1.0 floating point to srgb 0-255 integer conversion.
|
||||
"""
|
||||
value = max(0.0, min(1.0, value))
|
||||
if value <= 0.0031308:
|
||||
return int(value * 12.92 * 255 + 0.5)
|
||||
return int((1.055 * math.pow(value, 1 / 2.4) - 0.055) * 255 + 0.5)
|
||||
|
||||
def blurhash_components(blurhash):
|
||||
"""
|
||||
Decodes and returns the number of x and y components in the given blurhash.
|
||||
"""
|
||||
if len(blurhash) < 6:
|
||||
raise ValueError("BlurHash must be at least 6 characters long.")
|
||||
|
||||
# Decode metadata
|
||||
size_info = base83_decode(blurhash[0])
|
||||
size_y = int(size_info / 9) + 1
|
||||
size_x = (size_info % 9) + 1
|
||||
|
||||
return size_x, size_y
|
||||
|
||||
def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
|
||||
"""
|
||||
Decodes the given blurhash to an image of the specified size.
|
||||
|
||||
Returns the resulting image a list of lists of 3-value sRGB 8 bit integer
|
||||
lists. Set linear to True if you would prefer to get linear floating point
|
||||
RGB back.
|
||||
|
||||
The punch parameter can be used to de- or increase the contrast of the
|
||||
resulting image.
|
||||
|
||||
As per the original implementation it is suggested to only decode
|
||||
to a relatively small size and then scale the result up, as it
|
||||
basically looks the same anyways.
|
||||
"""
|
||||
if len(blurhash) < 6:
|
||||
raise ValueError("BlurHash must be at least 6 characters long.")
|
||||
|
||||
# Decode metadata
|
||||
size_info = base83_decode(blurhash[0])
|
||||
size_y = int(size_info / 9) + 1
|
||||
size_x = (size_info % 9) + 1
|
||||
|
||||
quant_max_value = base83_decode(blurhash[1])
|
||||
real_max_value = (float(quant_max_value + 1) / 166.0) * punch
|
||||
|
||||
# Make sure we at least have the right number of characters
|
||||
if len(blurhash) != 4 + 2 * size_x * size_y:
|
||||
raise ValueError("Invalid BlurHash length.")
|
||||
|
||||
# Decode DC component
|
||||
dc_value = base83_decode(blurhash[2:6])
|
||||
colours = [(
|
||||
srgb_to_linear(dc_value >> 16),
|
||||
srgb_to_linear((dc_value >> 8) & 255),
|
||||
srgb_to_linear(dc_value & 255)
|
||||
)]
|
||||
|
||||
# Decode AC components
|
||||
for component in range(1, size_x * size_y):
|
||||
ac_value = base83_decode(blurhash[4+component*2:4+(component+1)*2])
|
||||
colours.append((
|
||||
sign_pow((float(int(ac_value / (19 * 19))) - 9.0) / 9.0, 2.0) * real_max_value,
|
||||
sign_pow((float(int(ac_value / 19) % 19) - 9.0) / 9.0, 2.0) * real_max_value,
|
||||
sign_pow((float(ac_value % 19) - 9.0) / 9.0, 2.0) * real_max_value
|
||||
))
|
||||
|
||||
# Return image RGB values, as a list of lists of lists,
|
||||
# consumable by something like numpy or PIL.
|
||||
pixels = []
|
||||
for y in range(height):
|
||||
pixel_row = []
|
||||
for x in range(width):
|
||||
pixel = [0.0, 0.0, 0.0]
|
||||
|
||||
for j in range(size_y):
|
||||
for i in range(size_x):
|
||||
basis = math.cos(math.pi * float(x) * float(i) / float(width)) * \
|
||||
math.cos(math.pi * float(y) * float(j) / float(height))
|
||||
colour = colours[i + j * size_x]
|
||||
pixel[0] += colour[0] * basis
|
||||
pixel[1] += colour[1] * basis
|
||||
pixel[2] += colour[2] * basis
|
||||
if linear == False:
|
||||
pixel_row.append([
|
||||
linear_to_srgb(pixel[0]),
|
||||
linear_to_srgb(pixel[1]),
|
||||
linear_to_srgb(pixel[2]),
|
||||
])
|
||||
else:
|
||||
pixel_row.append(pixel)
|
||||
pixels.append(pixel_row)
|
||||
return pixels
|
||||
|
||||
def blurhash_encode(image, components_x = 4, components_y = 4, linear = False):
|
||||
"""
|
||||
Calculates the blurhash for an image using the given x and y component counts.
|
||||
|
||||
Image should be a 3-dimensional array, with the first dimension being y, the second
|
||||
being x, and the third being the three rgb components that are assumed to be 0-255
|
||||
srgb integers (incidentally, this is the format you will get from a PIL RGB image).
|
||||
|
||||
You can also pass in already linear data - to do this, set linear to True. This is
|
||||
useful if you want to encode a version of your image resized to a smaller size (which
|
||||
you should ideally do in linear colour).
|
||||
"""
|
||||
if components_x < 1 or components_x > 9 or components_y < 1 or components_y > 9:
|
||||
raise ValueError("x and y component counts must be between 1 and 9 inclusive.")
|
||||
height = float(len(image))
|
||||
width = float(len(image[0]))
|
||||
|
||||
# Convert to linear if neeeded
|
||||
image_linear = []
|
||||
if linear == False:
|
||||
for y in range(int(height)):
|
||||
image_linear_line = []
|
||||
for x in range(int(width)):
|
||||
image_linear_line.append([
|
||||
srgb_to_linear(image[y][x][0]),
|
||||
srgb_to_linear(image[y][x][1]),
|
||||
srgb_to_linear(image[y][x][2])
|
||||
])
|
||||
image_linear.append(image_linear_line)
|
||||
else:
|
||||
image_linear = image
|
||||
|
||||
# Calculate components
|
||||
components = []
|
||||
max_ac_component = 0.0
|
||||
for j in range(components_y):
|
||||
for i in range(components_x):
|
||||
norm_factor = 1.0 if (i == 0 and j == 0) else 2.0
|
||||
component = [0.0, 0.0, 0.0]
|
||||
for y in range(int(height)):
|
||||
for x in range(int(width)):
|
||||
basis = norm_factor * math.cos(math.pi * float(i) * float(x) / width) * \
|
||||
math.cos(math.pi * float(j) * float(y) / height)
|
||||
component[0] += basis * image_linear[y][x][0]
|
||||
component[1] += basis * image_linear[y][x][1]
|
||||
component[2] += basis * image_linear[y][x][2]
|
||||
|
||||
component[0] /= (width * height)
|
||||
component[1] /= (width * height)
|
||||
component[2] /= (width * height)
|
||||
components.append(component)
|
||||
|
||||
if not (i == 0 and j == 0):
|
||||
max_ac_component = max(max_ac_component, abs(component[0]), abs(component[1]), abs(component[2]))
|
||||
|
||||
# Encode components
|
||||
dc_value = (linear_to_srgb(components[0][0]) << 16) + \
|
||||
(linear_to_srgb(components[0][1]) << 8) + \
|
||||
linear_to_srgb(components[0][2])
|
||||
|
||||
quant_max_ac_component = int(max(0, min(82, math.floor(max_ac_component * 166 - 0.5))))
|
||||
ac_component_norm_factor = float(quant_max_ac_component + 1) / 166.0
|
||||
|
||||
ac_values = []
|
||||
for r, g, b in components[1:]:
|
||||
ac_values.append(
|
||||
int(max(0.0, min(18.0, math.floor(sign_pow(r / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 * 19 + \
|
||||
int(max(0.0, min(18.0, math.floor(sign_pow(g / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 + \
|
||||
int(max(0.0, min(18.0, math.floor(sign_pow(b / ac_component_norm_factor, 0.5) * 9.0 + 9.5))))
|
||||
)
|
||||
|
||||
# Build final blurhash
|
||||
blurhash = ""
|
||||
blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1)
|
||||
blurhash += base83_encode(quant_max_ac_component, 1)
|
||||
blurhash += base83_encode(dc_value, 4)
|
||||
for ac_value in ac_values:
|
||||
blurhash += base83_encode(ac_value, 2)
|
||||
|
||||
return blurhash
|
Loading…
Reference in New Issue