Removed avifEncOptions (#14)

* Fixed indentation

* Removed avifEncOptions

* Destroy encoder on failure

* Destroy image on failure

* Delete encoder object on failure

* Corrected comment

* Allow libavif to install rav1e on manylinux2014

---------

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
This commit is contained in:
Andrew Murray 2025-01-04 02:56:02 +11:00 committed by GitHub
parent 3a9a3ab9cc
commit 93289324c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 196 additions and 215 deletions

View File

@ -106,8 +106,7 @@ function build_harfbuzz {
function build_libavif { function build_libavif {
if [ -e libavif-stamp ]; then return; fi if [ -e libavif-stamp ]; then return; fi
if [[ "$MB_ML_VER" == 2014 ]] || [[ "$PLAT" == "aarch64" ]]; then if [[ "$PLAT" == "aarch64" ]]; then
# Once Amazon 2 is EOL on 30 June 2025, manylinux2014 will no longer be needed
# Once GitHub Actions supports aarch64 without emulation, this will no longer needed as building will be faster # Once GitHub Actions supports aarch64 without emulation, this will no longer needed as building will be faster
if [[ "$PLAT" == "aarch64" ]]; then if [[ "$PLAT" == "aarch64" ]]; then
suffix="aarch64" suffix="aarch64"
@ -137,6 +136,9 @@ EOF
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum install -y perl yum install -y perl
if [[ "$MB_ML_VER" == 2014 ]]; then
yum install -y perl-IPC-Cmd
fi
fi fi
rav1e=LOCAL rav1e=LOCAL

View File

@ -3,20 +3,6 @@
#include <Python.h> #include <Python.h>
#include "avif/avif.h" #include "avif/avif.h"
typedef struct {
avifPixelFormat subsampling;
int qmin;
int qmax;
int quality;
int speed;
avifCodecChoice codec;
avifRange range;
avifBool alpha_premultiplied;
int tile_rows_log2;
int tile_cols_log2;
avifBool autotiling;
} avifEncOptions;
// Encoder type // Encoder type
typedef struct { typedef struct {
PyObject_HEAD avifEncoder *encoder; PyObject_HEAD avifEncoder *encoder;
@ -241,9 +227,8 @@ _add_codec_specific_options(avifEncoder *encoder, PyObject *opts) {
PyObject * PyObject *
AvifEncoderNew(PyObject *self_, PyObject *args) { AvifEncoderNew(PyObject *self_, PyObject *args) {
unsigned int width, height; unsigned int width, height;
avifEncOptions enc_options;
AvifEncoderObject *self = NULL; AvifEncoderObject *self = NULL;
avifEncoder *encoder = NULL; avifEncoder *encoder;
char *subsampling; char *subsampling;
int qmin; int qmin;
@ -291,130 +276,115 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
return NULL; return NULL;
} }
// Create a new animation encoder and picture frame
avifImage *image = avifImageCreateEmpty();
// Set these in advance so any upcoming RGB -> YUV use the proper coefficients
if (strcmp(range, "full") == 0) {
image->yuvRange = AVIF_RANGE_FULL;
} else if (strcmp(range, "limited") == 0) {
image->yuvRange = AVIF_RANGE_LIMITED;
} else {
PyErr_SetString(PyExc_ValueError, "Invalid range");
return NULL;
}
if (strcmp(subsampling, "4:0:0") == 0) { if (strcmp(subsampling, "4:0:0") == 0) {
enc_options.subsampling = AVIF_PIXEL_FORMAT_YUV400; image->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
} else if (strcmp(subsampling, "4:2:0") == 0) { } else if (strcmp(subsampling, "4:2:0") == 0) {
enc_options.subsampling = AVIF_PIXEL_FORMAT_YUV420; image->yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
} else if (strcmp(subsampling, "4:2:2") == 0) { } else if (strcmp(subsampling, "4:2:2") == 0) {
enc_options.subsampling = AVIF_PIXEL_FORMAT_YUV422; image->yuvFormat = AVIF_PIXEL_FORMAT_YUV422;
} else if (strcmp(subsampling, "4:4:4") == 0) { } else if (strcmp(subsampling, "4:4:4") == 0) {
enc_options.subsampling = AVIF_PIXEL_FORMAT_YUV444; image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
} else { } else {
PyErr_Format(PyExc_ValueError, "Invalid subsampling: %s", subsampling); PyErr_Format(PyExc_ValueError, "Invalid subsampling: %s", subsampling);
return NULL; return NULL;
} }
if (qmin == -1 || qmax == -1) { image->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
#if AVIF_VERSION >= 1000000 image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
enc_options.qmin = -1; image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
enc_options.qmax = -1;
#else
enc_options.qmin = normalize_quantize_value(64 - quality);
enc_options.qmax = normalize_quantize_value(100 - quality);
#endif
} else {
enc_options.qmin = normalize_quantize_value(qmin);
enc_options.qmax = normalize_quantize_value(qmax);
}
enc_options.quality = quality;
if (speed < AVIF_SPEED_SLOWEST) {
speed = AVIF_SPEED_SLOWEST;
} else if (speed > AVIF_SPEED_FASTEST) {
speed = AVIF_SPEED_FASTEST;
}
enc_options.speed = speed;
if (strcmp(codec, "auto") == 0) {
enc_options.codec = AVIF_CODEC_CHOICE_AUTO;
} else {
enc_options.codec = avifCodecChoiceFromName(codec);
}
if (strcmp(range, "full") == 0) {
enc_options.range = AVIF_RANGE_FULL;
} else if (strcmp(range, "limited") == 0) {
enc_options.range = AVIF_RANGE_LIMITED;
} else {
PyErr_SetString(PyExc_ValueError, "Invalid range");
return NULL;
}
// Validate canvas dimensions // Validate canvas dimensions
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions"); PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions");
avifImageDestroy(image);
return NULL; return NULL;
} }
image->width = width;
image->height = height;
enc_options.tile_rows_log2 = normalize_tiles_log2(tile_rows_log2); image->depth = 8;
enc_options.tile_cols_log2 = normalize_tiles_log2(tile_cols_log2); #if AVIF_VERSION >= 90000
enc_options.alpha_premultiplied = image->alphaPremultiplied = alpha_premultiplied == Py_True ? AVIF_TRUE : AVIF_FALSE;
(alpha_premultiplied == Py_True) ? AVIF_TRUE : AVIF_FALSE; #endif
enc_options.autotiling = (autotiling == Py_True) ? AVIF_TRUE : AVIF_FALSE;
// Create a new animation encoder and picture frame
self = PyObject_New(AvifEncoderObject, &AvifEncoder_Type);
if (self) {
self->icc_bytes = NULL;
self->exif_bytes = NULL;
self->xmp_bytes = NULL;
encoder = avifEncoderCreate(); encoder = avifEncoderCreate();
int is_aom_encode = strcmp(codec, "aom") == 0 || int is_aom_encode = strcmp(codec, "aom") == 0 ||
(strcmp(codec, "auto") == 0 && (strcmp(codec, "auto") == 0 &&
_codec_available("aom", AVIF_CODEC_FLAG_CAN_ENCODE)); _codec_available("aom", AVIF_CODEC_FLAG_CAN_ENCODE));
encoder->maxThreads = is_aom_encode && max_threads > 64 ? 64 : max_threads; encoder->maxThreads = is_aom_encode && max_threads > 64 ? 64 : max_threads;
if (qmin == -1 || qmax == -1) {
#if AVIF_VERSION >= 1000000 #if AVIF_VERSION >= 1000000
if (enc_options.qmin != -1 && enc_options.qmax != -1) { encoder->quality = quality;
encoder->minQuantizer = enc_options.qmin;
encoder->maxQuantizer = enc_options.qmax;
} else {
encoder->quality = enc_options.quality;
}
#else #else
encoder->minQuantizer = enc_options.qmin; encoder->minQuantizer = normalize_quantize_value(64 - quality);
encoder->maxQuantizer = enc_options.qmax; encoder->maxQuantizer = normalize_quantize_value(100 - quality);
#endif #endif
encoder->codecChoice = enc_options.codec; } else {
encoder->speed = enc_options.speed; encoder->minQuantizer = normalize_quantize_value(qmin);
encoder->maxQuantizer = normalize_quantize_value(qmax);
}
if (strcmp(codec, "auto") == 0) {
encoder->codecChoice = AVIF_CODEC_CHOICE_AUTO;
} else {
encoder->codecChoice = avifCodecChoiceFromName(codec);
}
if (speed < AVIF_SPEED_SLOWEST) {
speed = AVIF_SPEED_SLOWEST;
} else if (speed > AVIF_SPEED_FASTEST) {
speed = AVIF_SPEED_FASTEST;
}
encoder->speed = speed;
encoder->timescale = (uint64_t)1000; encoder->timescale = (uint64_t)1000;
encoder->tileRowsLog2 = enc_options.tile_rows_log2; encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2);
encoder->tileColsLog2 = enc_options.tile_cols_log2; encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2);
#if AVIF_VERSION >= 110000 #if AVIF_VERSION >= 110000
encoder->autoTiling = enc_options.autotiling; encoder->autoTiling = autotiling == Py_True ? AVIF_TRUE : AVIF_FALSE;
#endif #endif
if (advanced != Py_None) { if (advanced != Py_None) {
#if AVIF_VERSION >= 80200 #if AVIF_VERSION >= 80200
if (_add_codec_specific_options(encoder, advanced)) { if (_add_codec_specific_options(encoder, advanced)) {
avifImageDestroy(image);
avifEncoderDestroy(encoder);
return NULL; return NULL;
} }
#else #else
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, "Advanced codec options require libavif >= 0.8.2" PyExc_ValueError, "Advanced codec options require libavif >= 0.8.2"
); );
avifImageDestroy(image);
avifEncoderDestroy(encoder);
return NULL; return NULL;
#endif #endif
} }
self->encoder = encoder; self = PyObject_New(AvifEncoderObject, &AvifEncoder_Type);
if (!self) {
avifImage *image = avifImageCreateEmpty(); PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
// Set these in advance so any upcoming RGB -> YUV use the proper coefficients avifImageDestroy(image);
image->yuvRange = enc_options.range; avifEncoderDestroy(encoder);
image->yuvFormat = enc_options.subsampling; return NULL;
image->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED; }
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; self->frame_index = -1;
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; self->icc_bytes = NULL;
image->width = width; self->exif_bytes = NULL;
image->height = height; self->xmp_bytes = NULL;
image->depth = 8;
#if AVIF_VERSION >= 90000
image->alphaPremultiplied = enc_options.alpha_premultiplied;
#endif
avifResult result; avifResult result;
if (PyBytes_GET_SIZE(icc_bytes)) { if (PyBytes_GET_SIZE(icc_bytes)) {
@ -422,9 +392,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
Py_INCREF(icc_bytes); Py_INCREF(icc_bytes);
result = avifImageSetProfileICC( result = avifImageSetProfileICC(
image, image, (uint8_t *)PyBytes_AS_STRING(icc_bytes), PyBytes_GET_SIZE(icc_bytes)
(uint8_t *)PyBytes_AS_STRING(icc_bytes),
PyBytes_GET_SIZE(icc_bytes)
); );
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -432,6 +400,10 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
"Setting ICC profile failed: %s", "Setting ICC profile failed: %s",
avifResultToString(result) avifResultToString(result)
); );
avifImageDestroy(image);
avifEncoderDestroy(encoder);
Py_XDECREF(self->icc_bytes);
PyObject_Del(self);
return NULL; return NULL;
} }
} else { } else {
@ -454,6 +426,11 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
"Setting EXIF data failed: %s", "Setting EXIF data failed: %s",
avifResultToString(result) avifResultToString(result)
); );
avifImageDestroy(image);
avifEncoderDestroy(encoder);
Py_XDECREF(self->icc_bytes);
Py_XDECREF(self->exif_bytes);
PyObject_Del(self);
return NULL; return NULL;
} }
} }
@ -462,9 +439,7 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
Py_INCREF(xmp_bytes); Py_INCREF(xmp_bytes);
result = avifImageSetMetadataXMP( result = avifImageSetMetadataXMP(
image, image, (uint8_t *)PyBytes_AS_STRING(xmp_bytes), PyBytes_GET_SIZE(xmp_bytes)
(uint8_t *)PyBytes_AS_STRING(xmp_bytes),
PyBytes_GET_SIZE(xmp_bytes)
); );
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -472,6 +447,12 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
"Setting XMP data failed: %s", "Setting XMP data failed: %s",
avifResultToString(result) avifResultToString(result)
); );
avifImageDestroy(image);
avifEncoderDestroy(encoder);
Py_XDECREF(self->icc_bytes);
Py_XDECREF(self->exif_bytes);
Py_XDECREF(self->xmp_bytes);
PyObject_Del(self);
return NULL; return NULL;
} }
} }
@ -480,12 +461,9 @@ AvifEncoderNew(PyObject *self_, PyObject *args) {
} }
self->image = image; self->image = image;
self->frame_index = -1; self->encoder = encoder;
return (PyObject *)self; return (PyObject *)self;
}
PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
return NULL;
} }
PyObject * PyObject *
@ -606,8 +584,9 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) {
// rgb.pixels is safe for writes // rgb.pixels is safe for writes
memcpy(rgb.pixels, rgb_bytes, size); memcpy(rgb.pixels, rgb_bytes, size);
Py_BEGIN_ALLOW_THREADS result = avifImageRGBToYUV(frame, &rgb); Py_BEGIN_ALLOW_THREADS;
Py_END_ALLOW_THREADS result = avifImageRGBToYUV(frame, &rgb);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -624,9 +603,9 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) {
addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE; addImageFlags |= AVIF_ADD_IMAGE_FLAG_SINGLE;
} }
Py_BEGIN_ALLOW_THREADS result = Py_BEGIN_ALLOW_THREADS;
avifEncoderAddImage(encoder, frame, duration, addImageFlags); result = avifEncoderAddImage(encoder, frame, duration, addImageFlags);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -660,8 +639,9 @@ _encoder_finish(AvifEncoderObject *self) {
avifResult result; avifResult result;
PyObject *ret = NULL; PyObject *ret = NULL;
Py_BEGIN_ALLOW_THREADS result = avifEncoderFinish(encoder, &raw); Py_BEGIN_ALLOW_THREADS;
Py_END_ALLOW_THREADS result = avifEncoderFinish(encoder, &raw);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -685,6 +665,7 @@ PyObject *
AvifDecoderNew(PyObject *self_, PyObject *args) { AvifDecoderNew(PyObject *self_, PyObject *args) {
PyObject *avif_bytes; PyObject *avif_bytes;
AvifDecoderObject *self = NULL; AvifDecoderObject *self = NULL;
avifDecoder *decoder;
char *codec_str; char *codec_str;
avifCodecChoice codec; avifCodecChoice codec;
@ -707,29 +688,26 @@ AvifDecoderNew(PyObject *self_, PyObject *args) {
PyErr_SetString(PyExc_RuntimeError, "could not create decoder object"); PyErr_SetString(PyExc_RuntimeError, "could not create decoder object");
return NULL; return NULL;
} }
self->decoder = NULL;
Py_INCREF(avif_bytes); Py_INCREF(avif_bytes);
self->data = avif_bytes; self->data = avif_bytes;
self->decoder = avifDecoderCreate(); decoder = avifDecoderCreate();
#if AVIF_VERSION >= 80400 #if AVIF_VERSION >= 80400
self->decoder->maxThreads = max_threads; decoder->maxThreads = max_threads;
#endif #endif
#if AVIF_VERSION >= 90200 #if AVIF_VERSION >= 90200
// Turn off libavif's 'clap' (clean aperture) property validation. // Turn off libavif's 'clap' (clean aperture) property validation.
self->decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID; decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
// Allow the PixelInformationProperty ('pixi') to be missing in AV1 image // Allow the PixelInformationProperty ('pixi') to be missing in AV1 image
// items. libheif v1.11.0 and older does not add the 'pixi' item property to // items. libheif v1.11.0 and older does not add the 'pixi' item property to
// AV1 image items. // AV1 image items.
self->decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED; decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
#endif #endif
self->decoder->codecChoice = codec; decoder->codecChoice = codec;
result = avifDecoderSetIOMemory( result = avifDecoderSetIOMemory(
self->decoder, decoder, (uint8_t *)PyBytes_AS_STRING(self->data), PyBytes_GET_SIZE(self->data)
(uint8_t *)PyBytes_AS_STRING(self->data),
PyBytes_GET_SIZE(self->data)
); );
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -737,31 +715,31 @@ AvifDecoderNew(PyObject *self_, PyObject *args) {
"Setting IO memory failed: %s", "Setting IO memory failed: %s",
avifResultToString(result) avifResultToString(result)
); );
avifDecoderDestroy(self->decoder); avifDecoderDestroy(decoder);
self->decoder = NULL; PyObject_Del(self);
Py_DECREF(self);
return NULL; return NULL;
} }
result = avifDecoderParse(self->decoder); result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
exc_type_for_avif_result(result), exc_type_for_avif_result(result),
"Failed to decode image: %s", "Failed to decode image: %s",
avifResultToString(result) avifResultToString(result)
); );
avifDecoderDestroy(self->decoder); avifDecoderDestroy(decoder);
self->decoder = NULL; PyObject_Del(self);
Py_DECREF(self);
return NULL; return NULL;
} }
if (self->decoder->alphaPresent) { if (decoder->alphaPresent) {
self->mode = "RGBA"; self->mode = "RGBA";
} else { } else {
self->mode = "RGB"; self->mode = "RGB";
} }
self->decoder = decoder;
return (PyObject *)self; return (PyObject *)self;
} }
@ -876,8 +854,9 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
return NULL; return NULL;
} }
Py_BEGIN_ALLOW_THREADS result = avifImageYUVToRGB(image, &rgb); Py_BEGIN_ALLOW_THREADS;
Py_END_ALLOW_THREADS result = avifImageYUVToRGB(image, &rgb);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) { if (result != AVIF_RESULT_OK) {
PyErr_Format( PyErr_Format(
@ -918,7 +897,7 @@ static struct PyMethodDef _encoder_methods[] = {
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
// AvifDecoder type definition // AvifEncoder type definition
static PyTypeObject AvifEncoder_Type = { static PyTypeObject AvifEncoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "AvifEncoder", PyVarObject_HEAD_INIT(NULL, 0).tp_name = "AvifEncoder",
.tp_basicsize = sizeof(AvifEncoderObject), .tp_basicsize = sizeof(AvifEncoderObject),