Add the ability to get Ugoiras converted to webp or apng via the render api (#1619)

* Add ability to get ugoiras converted to webp or apng via the render api

* Fix using the wrong type for media result in render API

* Add ugoira rendering to API docs
This commit is contained in:
Paul Friederichsen 2024-10-26 13:33:30 -05:00 committed by GitHub
parent e6373b5101
commit 6a801ce5b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 160 additions and 66 deletions

View File

@ -2332,7 +2332,7 @@ Also, it won't be long before the client supports moving to _some_ form of three
### **GET `/get_files/render`** { id="get_files_render" }
_Get an image file as rendered by Hydrus._
_Get an image or ugoira file as rendered by Hydrus._
Restricted access:
: YES. Search for Files permission needed. Additional search permission limits may apply.
@ -2344,19 +2344,24 @@ Arguments :
* `file_id`: (selective, numerical file id for the file)
* `hash`: (selective, a hexadecimal SHA256 hash for the file)
* `download`: (optional, boolean, default `false`)
* `render_format`: (optional, integer, the filetype enum value to render the file to, default `2` for PNG)
* `render_quality`: (optional, integer, the quality or PNG compression level to use for encoding the image, default `1` for PNG and `80` for JPEG and WEBP)
* `width` and `height`: (optional but must provide both if used, integer, the width and height to scale the image to)
* `render_format`: (optional, integer, the filetype enum value to render the file to, for still images it defaults `2` for PNG, for Ugoiras it defaults to `23` for APNG)
* `render_quality`: (optional, integer, the quality or PNG compression level to use for encoding the image, default `1` for PNG and `80` for JPEG and WEBP, has no effect for Ugoiras using APNG)
* `width` and `height`: (optional but must provide both if used, integer, the width and height to scale the image to. Doesn't apply to Ugoiras)
Only use one of file_id or hash. As with metadata fetching, you may only use the hash argument if you have access to all files. If you are tag-restricted, you will have to use a file_id in the last search you ran.
Currently the only accepted values for `render_format` are:
Currently the accepted values for `render_format` for image files are:
* `1` for JPEG (`quality` sets JPEG quality 0 to 100, always progressive 4:2:0 encoding)
* `2` for PNG (`quality` sets the compression level from 0 to 9. A higher value means a smaller size and longer compression time)
* `33` for WEBP (`quality` sets WEBP quality 1 to 100, for values over 100 lossless compression is used)
The accepted values for Ugoiras are:
* `23` for APNG (`quality` does nothing for this format)
* `83` for animated WEBP (`quality` sets WEBP quality 1 to 100, for values over 100 lossless compression is used)
The file you request must be a still image file that Hydrus can render (this includes PSD files). This request uses the client image cache.
The file you request must be a still image file that Hydrus can render (this includes PSD files) or a Ugoira file. This request uses the client image cache for images.
``` title="Example request"
/get_files/render?file_id=452158
@ -2366,7 +2371,7 @@ The file you request must be a still image file that Hydrus can render (this inc
```
Response:
: A PNG, JPEG, or WEBP file of the image as would be rendered in the client, optionally resized as specified in the query parameters. It will be converted to sRGB color if the file had a color profile but the rendered file will not have any color profile.
: A PNG (or APNG), JPEG, or WEBP file of the image as would be rendered in the client, optionally resized as specified in the query parameters. It will be converted to sRGB color if the file had a color profile but the rendered file will not have any color profile.
By default, this will set the `Content-Disposition` header to `inline`, which causes a web browser to show the file. If you set `download=true`, it will set it to `attachment`, which triggers the browser to automatically download it (or open the 'save as' dialog) instead.

View File

@ -1,3 +1,6 @@
import io
from hydrus.core import HydrusConstants as HC
from hydrus.core.files import HydrusUgoiraHandling
from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.files import HydrusArchiveHandling
@ -160,3 +163,49 @@ class UgoiraRenderer(object):
return numpy_image
def ConvertUgoiraToBytesForAPI( media: ClientMediaResult.MediaResult, format: int, quality: int ):
client_files_manager: ClientFiles.ClientFilesManager = CG.client_controller.client_files_manager
path = client_files_manager.GetFilePath( media.GetHash(), media.GetMime() )
frame_paths = HydrusUgoiraHandling.GetFramePathsUgoira( path )
zip = HydrusArchiveHandling.GetZipAsPath( path )
frames = [HydrusImageHandling.GeneratePILImage( zip.joinpath(frame_path_from_zip).open('rb') ) for frame_path_from_zip in frame_paths]
frame_durations = GetFrameDurationsUgoira( media )
file = io.BytesIO()
if format == HC.ANIMATION_APNG:
frames[0].save(
file,
'PNG',
save_all=True,
append_images=frames[1:],
duration=frame_durations, # duration of each frame in milliseconds
loop=0, # loop forever
#compress_level = quality # seems to have no effect for APNG
)
elif format == HC.ANIMATION_WEBP:
frames[0].save(
file,
'WEBP',
save_all=True,
append_images=frames[1:],
duration=frame_durations, # duration of each frame in milliseconds
loop=0, # loop forever
quality = quality - 100 if quality > 100 else quality,
lossless = quality > 100
)
file_bytes = file.getvalue()
file.close()
return file_bytes

View File

@ -17,6 +17,7 @@ from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientRendering
from hydrus.client import ClientThreading
from hydrus.client import ClientUgoiraHandling
from hydrus.client.media import ClientMedia
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
@ -203,22 +204,11 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
if 'render_format' in request.parsed_request_args:
format = request.parsed_request_args.GetValue( 'render_format', int )
if not format in [ HC.IMAGE_PNG, HC.IMAGE_JPEG, HC.IMAGE_WEBP ]:
raise HydrusExceptions.BadRequestException( 'Invalid render format!' )
else:
format = HC.IMAGE_PNG
try:
media_result: ClientMedia.MediaSingleton
media_result: ClientMediaResult.MediaResult
if 'file_id' in request.parsed_request_args:
@ -246,64 +236,114 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
raise HydrusExceptions.NotFoundException( 'One or more of those file identifiers was missing!' )
if not media_result.IsStaticImage():
if media_result.IsStaticImage():
raise HydrusExceptions.BadRequestException('Requested file is not an image!')
renderer: ClientRendering.ImageRenderer = CG.client_controller.GetCache( 'images' ).GetImageRenderer( media_result )
while not renderer.IsReady():
if request.disconnected:
if 'render_format' in request.parsed_request_args:
return
format = request.parsed_request_args.GetValue( 'render_format', int )
time.sleep( 0.01 )
numpy_image = renderer.GetNumPyImage()
if 'width' in request.parsed_request_args and 'height' in request.parsed_request_args:
width = request.parsed_request_args.GetValue( 'width', int )
height = request.parsed_request_args.GetValue( 'height', int )
if width < 1:
raise HydrusExceptions.BadRequestException( 'Width must be greater than 0!' )
if height < 1:
raise HydrusExceptions.BadRequestException( 'Height must be greater than 0!' )
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, ( width, height ) )
if 'render_quality' in request.parsed_request_args:
quality = request.parsed_request_args.GetValue( 'render_quality', int )
else:
if format == HC.IMAGE_PNG:
quality = 1 # fastest png compression
if not format in [ HC.IMAGE_PNG, HC.IMAGE_JPEG, HC.IMAGE_WEBP ]:
raise HydrusExceptions.BadRequestException( 'Invalid render format!' )
else:
quality = 80
format = HC.IMAGE_PNG
renderer: ClientRendering.ImageRenderer = CG.client_controller.GetCache( 'images' ).GetImageRenderer( media_result )
while not renderer.IsReady():
if request.disconnected:
return
time.sleep( 0.01 )
numpy_image = renderer.GetNumPyImage()
if 'width' in request.parsed_request_args and 'height' in request.parsed_request_args:
width = request.parsed_request_args.GetValue( 'width', int )
height = request.parsed_request_args.GetValue( 'height', int )
if width < 1:
raise HydrusExceptions.BadRequestException( 'Width must be greater than 0!' )
if height < 1:
raise HydrusExceptions.BadRequestException( 'Height must be greater than 0!' )
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, ( width, height ) )
if 'render_quality' in request.parsed_request_args:
quality = request.parsed_request_args.GetValue( 'render_quality', int )
else:
if format == HC.IMAGE_PNG:
quality = 1 # fastest png compression
else:
quality = 80
max_age = 86400 * 365
body = HydrusImageHandling.GenerateFileBytesForRenderAPI( numpy_image, format, quality )
body = HydrusImageHandling.GenerateFileBytesForRenderAPI( numpy_image, format, quality )
elif media_result.GetMime() == HC.ANIMATION_UGOIRA:
if 'render_format' in request.parsed_request_args:
format = request.parsed_request_args.GetValue( 'render_format', int )
if not format in [ HC.ANIMATION_APNG, HC.ANIMATION_WEBP ]:
raise HydrusExceptions.BadRequestException( 'Invalid render format!' )
else:
format = HC.ANIMATION_APNG # maybe we should default to animated webp, it is much faster
if 'render_quality' in request.parsed_request_args:
quality = request.parsed_request_args.GetValue( 'render_quality', int )
else:
quality = 80 # compress_level has no effect for APNG so we don't use quality in that case.
body = ClientUgoiraHandling.ConvertUgoiraToBytesForAPI( media_result, format, quality )
if media_result.GetDurationMS() is not None:
# if a ugoira has a duration, it has valid animation.json
# thus frame timings and the resulting render are immutable
max_age = 86400 * 365
else:
# frame timing could change with notes!
max_age = 3600
else:
raise HydrusExceptions.BadRequestException('Requested file is not an image!')
is_attachment = request.parsed_request_args.GetValue( 'download', bool, default_value = False )
response_context = HydrusServerResources.ResponseContext( 200, mime = format, body = body, is_attachment = is_attachment, max_age = 86400 * 365 )
response_context = HydrusServerResources.ResponseContext( 200, mime = format, body = body, is_attachment = is_attachment, max_age = max_age )
return response_context