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:
parent
e6373b5101
commit
6a801ce5b2
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue