Pillow/src/_avif.c
2025-05-28 23:50:18 +10:00

903 lines
26 KiB
C

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "avif/avif.h"
// Encoder type
typedef struct {
PyObject_HEAD avifEncoder *encoder;
avifImage *image;
int first_frame;
} AvifEncoderObject;
static PyTypeObject AvifEncoder_Type;
// Decoder type
typedef struct {
PyObject_HEAD avifDecoder *decoder;
Py_buffer buffer;
} AvifDecoderObject;
static PyTypeObject AvifDecoder_Type;
static int
normalize_tiles_log2(int value) {
if (value < 0) {
return 0;
} else if (value > 6) {
return 6;
} else {
return value;
}
}
static PyObject *
exc_type_for_avif_result(avifResult result) {
switch (result) {
case AVIF_RESULT_INVALID_EXIF_PAYLOAD:
case AVIF_RESULT_INVALID_CODEC_SPECIFIC_OPTION:
return PyExc_ValueError;
case AVIF_RESULT_INVALID_FTYP:
case AVIF_RESULT_BMFF_PARSE_FAILED:
case AVIF_RESULT_TRUNCATED_DATA:
case AVIF_RESULT_NO_CONTENT:
return PyExc_SyntaxError;
default:
return PyExc_RuntimeError;
}
}
static uint8_t
irot_imir_to_exif_orientation(const avifImage *image) {
uint8_t axis = image->imir.axis;
int imir = image->transformFlags & AVIF_TRANSFORM_IMIR;
int irot = image->transformFlags & AVIF_TRANSFORM_IROT;
if (irot) {
uint8_t angle = image->irot.angle;
if (angle == 1) {
if (imir) {
return axis ? 7 // 90 degrees anti-clockwise then swap left and right.
: 5; // 90 degrees anti-clockwise then swap top and bottom.
}
return 8; // 90 degrees anti-clockwise.
}
if (angle == 2) {
if (imir) {
return axis
? 4 // 180 degrees anti-clockwise then swap left and right.
: 2; // 180 degrees anti-clockwise then swap top and bottom.
}
return 3; // 180 degrees anti-clockwise.
}
if (angle == 3) {
if (imir) {
return axis
? 5 // 270 degrees anti-clockwise then swap left and right.
: 7; // 270 degrees anti-clockwise then swap top and bottom.
}
return 6; // 270 degrees anti-clockwise.
}
}
if (imir) {
return axis ? 2 // Swap left and right.
: 4; // Swap top and bottom.
}
return 1; // Default orientation ("top-left", no-op).
}
static void
exif_orientation_to_irot_imir(avifImage *image, int orientation) {
// Mapping from Exif orientation as defined in JEITA CP-3451C section 4.6.4.A
// Orientation to irot and imir boxes as defined in HEIF ISO/IEC 28002-12:2021
// sections 6.5.10 and 6.5.12.
switch (orientation) {
case 2: // The 0th row is at the visual top of the image, and the 0th column is
// the visual right-hand side.
image->transformFlags |= AVIF_TRANSFORM_IMIR;
image->imir.axis = 1;
break;
case 3: // The 0th row is at the visual bottom of the image, and the 0th column
// is the visual right-hand side.
image->transformFlags |= AVIF_TRANSFORM_IROT;
image->irot.angle = 2;
break;
case 4: // The 0th row is at the visual bottom of the image, and the 0th column
// is the visual left-hand side.
image->transformFlags |= AVIF_TRANSFORM_IMIR;
break;
case 5: // The 0th row is the visual left-hand side of the image, and the 0th
// column is the visual top.
image->transformFlags |= AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
image->irot.angle = 1; // applied before imir according to MIAF spec
// ISO/IEC 28002-12:2021 - section 7.3.6.7
break;
case 6: // The 0th row is the visual right-hand side of the image, and the 0th
// column is the visual top.
image->transformFlags |= AVIF_TRANSFORM_IROT;
image->irot.angle = 3;
break;
case 7: // The 0th row is the visual right-hand side of the image, and the 0th
// column is the visual bottom.
image->transformFlags |= AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
image->irot.angle = 3; // applied before imir according to MIAF spec
// ISO/IEC 28002-12:2021 - section 7.3.6.7
break;
case 8: // The 0th row is the visual left-hand side of the image, and the 0th
// column is the visual bottom.
image->transformFlags |= AVIF_TRANSFORM_IROT;
image->irot.angle = 1;
break;
}
}
static int
_codec_available(const char *name, avifCodecFlags flags) {
avifCodecChoice codec = avifCodecChoiceFromName(name);
if (codec == AVIF_CODEC_CHOICE_AUTO) {
return 0;
}
const char *codec_name = avifCodecName(codec, flags);
return (codec_name == NULL) ? 0 : 1;
}
PyObject *
_decoder_codec_available(PyObject *self, PyObject *args) {
char *codec_name;
if (!PyArg_ParseTuple(args, "s", &codec_name)) {
return NULL;
}
int is_available = _codec_available(codec_name, AVIF_CODEC_FLAG_CAN_DECODE);
return PyBool_FromLong(is_available);
}
PyObject *
_encoder_codec_available(PyObject *self, PyObject *args) {
char *codec_name;
if (!PyArg_ParseTuple(args, "s", &codec_name)) {
return NULL;
}
int is_available = _codec_available(codec_name, AVIF_CODEC_FLAG_CAN_ENCODE);
return PyBool_FromLong(is_available);
}
PyObject *
_codec_versions(PyObject *self, PyObject *args) {
char buffer[256];
avifCodecVersions(buffer);
return PyUnicode_FromString(buffer);
}
static int
_add_codec_specific_options(avifEncoder *encoder, PyObject *opts) {
Py_ssize_t i, size;
PyObject *keyval, *py_key, *py_val;
if (!PyTuple_Check(opts)) {
PyErr_SetString(PyExc_ValueError, "Invalid advanced codec options");
return 1;
}
size = PyTuple_GET_SIZE(opts);
for (i = 0; i < size; i++) {
keyval = PyTuple_GetItem(opts, i);
if (!PyTuple_Check(keyval) || PyTuple_GET_SIZE(keyval) != 2) {
PyErr_SetString(PyExc_ValueError, "Invalid advanced codec options");
return 1;
}
py_key = PyTuple_GetItem(keyval, 0);
py_val = PyTuple_GetItem(keyval, 1);
if (!PyUnicode_Check(py_key) || !PyUnicode_Check(py_val)) {
PyErr_SetString(PyExc_ValueError, "Invalid advanced codec options");
return 1;
}
const char *key = PyUnicode_AsUTF8(py_key);
const char *val = PyUnicode_AsUTF8(py_val);
if (key == NULL || val == NULL) {
PyErr_SetString(PyExc_ValueError, "Invalid advanced codec options");
return 1;
}
avifResult result = avifEncoderSetCodecSpecificOption(encoder, key, val);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Setting advanced codec options failed: %s",
avifResultToString(result)
);
return 1;
}
}
return 0;
}
// Encoder functions
PyObject *
AvifEncoderNew(PyObject *self_, PyObject *args) {
unsigned int width, height;
AvifEncoderObject *self = NULL;
avifEncoder *encoder = NULL;
char *subsampling;
int quality;
int speed;
int exif_orientation;
int max_threads;
Py_buffer icc_buffer;
Py_buffer exif_buffer;
Py_buffer xmp_buffer;
int alpha_premultiplied;
int autotiling;
int tile_rows_log2;
int tile_cols_log2;
char *codec;
char *range;
PyObject *advanced;
int error = 0;
if (!PyArg_ParseTuple(
args,
"(II)siiissiippy*y*iy*O",
&width,
&height,
&subsampling,
&quality,
&speed,
&max_threads,
&codec,
&range,
&tile_rows_log2,
&tile_cols_log2,
&alpha_premultiplied,
&autotiling,
&icc_buffer,
&exif_buffer,
&exif_orientation,
&xmp_buffer,
&advanced
)) {
return NULL;
}
// Create a new animation encoder and picture frame
avifImage *image = avifImageCreateEmpty();
if (image == NULL) {
PyErr_SetString(PyExc_ValueError, "Image creation failed");
error = 1;
goto end;
}
// 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");
error = 1;
goto end;
}
if (strcmp(subsampling, "4:0:0") == 0) {
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
} else if (strcmp(subsampling, "4:2:0") == 0) {
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV420;
} else if (strcmp(subsampling, "4:2:2") == 0) {
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV422;
} else if (strcmp(subsampling, "4:4:4") == 0) {
image->yuvFormat = AVIF_PIXEL_FORMAT_YUV444;
} else {
PyErr_Format(PyExc_ValueError, "Invalid subsampling: %s", subsampling);
error = 1;
goto end;
}
// Validate canvas dimensions
if (width == 0 || height == 0) {
PyErr_SetString(PyExc_ValueError, "invalid canvas dimensions");
error = 1;
goto end;
}
image->width = width;
image->height = height;
image->depth = 8;
image->alphaPremultiplied = alpha_premultiplied ? AVIF_TRUE : AVIF_FALSE;
encoder = avifEncoderCreate();
if (!encoder) {
PyErr_SetString(PyExc_MemoryError, "Can't allocate encoder");
error = 1;
goto end;
}
int is_aom_encode = strcmp(codec, "aom") == 0 ||
(strcmp(codec, "auto") == 0 &&
_codec_available("aom", AVIF_CODEC_FLAG_CAN_ENCODE));
encoder->maxThreads = is_aom_encode && max_threads > 64 ? 64 : max_threads;
encoder->quality = quality;
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->autoTiling = autotiling ? AVIF_TRUE : AVIF_FALSE;
if (!autotiling) {
encoder->tileRowsLog2 = normalize_tiles_log2(tile_rows_log2);
encoder->tileColsLog2 = normalize_tiles_log2(tile_cols_log2);
}
if (advanced != Py_None && _add_codec_specific_options(encoder, advanced)) {
error = 1;
goto end;
}
self = PyObject_New(AvifEncoderObject, &AvifEncoder_Type);
if (!self) {
PyErr_SetString(PyExc_RuntimeError, "could not create encoder object");
error = 1;
goto end;
}
self->first_frame = 1;
avifResult result;
if (icc_buffer.len) {
result = avifImageSetProfileICC(image, icc_buffer.buf, icc_buffer.len);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Setting ICC profile failed: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
// colorPrimaries and transferCharacteristics are ignored when an ICC
// profile is present, so set them to UNSPECIFIED.
image->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
} else {
image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
}
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
if (exif_buffer.len) {
result = avifImageSetMetadataExif(image, exif_buffer.buf, exif_buffer.len);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Setting EXIF data failed: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
}
if (xmp_buffer.len) {
result = avifImageSetMetadataXMP(image, xmp_buffer.buf, xmp_buffer.len);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Setting XMP data failed: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
}
if (exif_orientation > 1) {
exif_orientation_to_irot_imir(image, exif_orientation);
}
self->image = image;
self->encoder = encoder;
end:
PyBuffer_Release(&icc_buffer);
PyBuffer_Release(&exif_buffer);
PyBuffer_Release(&xmp_buffer);
if (error) {
if (image) {
avifImageDestroy(image);
}
if (encoder) {
avifEncoderDestroy(encoder);
}
if (self) {
PyObject_Del(self);
}
return NULL;
}
return (PyObject *)self;
}
PyObject *
_encoder_dealloc(AvifEncoderObject *self) {
if (self->encoder) {
avifEncoderDestroy(self->encoder);
}
if (self->image) {
avifImageDestroy(self->image);
}
Py_RETURN_NONE;
}
PyObject *
_encoder_add(AvifEncoderObject *self, PyObject *args) {
uint8_t *rgb_bytes;
Py_ssize_t size;
unsigned int duration;
unsigned int width;
unsigned int height;
char *mode;
unsigned int is_single_frame;
int error = 0;
avifRGBImage rgb;
avifResult result;
avifEncoder *encoder = self->encoder;
avifImage *image = self->image;
avifImage *frame = NULL;
if (!PyArg_ParseTuple(
args,
"y#I(II)sp",
(char **)&rgb_bytes,
&size,
&duration,
&width,
&height,
&mode,
&is_single_frame
)) {
return NULL;
}
if (image->width != width || image->height != height) {
PyErr_Format(
PyExc_ValueError,
"Image sequence dimensions mismatch, %ux%u != %ux%u",
image->width,
image->height,
width,
height
);
return NULL;
}
if (self->first_frame) {
// If we don't have an image populated with yuv planes, this is the first frame
frame = image;
} else {
frame = avifImageCreateEmpty();
if (image == NULL) {
PyErr_SetString(PyExc_ValueError, "Image creation failed");
return NULL;
}
frame->width = width;
frame->height = height;
frame->colorPrimaries = image->colorPrimaries;
frame->transferCharacteristics = image->transferCharacteristics;
frame->matrixCoefficients = image->matrixCoefficients;
frame->yuvRange = image->yuvRange;
frame->yuvFormat = image->yuvFormat;
frame->depth = image->depth;
frame->alphaPremultiplied = image->alphaPremultiplied;
}
avifRGBImageSetDefaults(&rgb, frame);
if (strcmp(mode, "RGBA") == 0) {
rgb.format = AVIF_RGB_FORMAT_RGBA;
} else {
rgb.format = AVIF_RGB_FORMAT_RGB;
}
result = avifRGBImageAllocatePixels(&rgb);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Pixel allocation failed: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
if (rgb.rowBytes * rgb.height != size) {
PyErr_Format(
PyExc_RuntimeError,
"rgb data has incorrect size: %u * %u (%u) != %u",
rgb.rowBytes,
rgb.height,
rgb.rowBytes * rgb.height,
size
);
error = 1;
goto end;
}
// rgb.pixels is safe for writes
memcpy(rgb.pixels, rgb_bytes, size);
Py_BEGIN_ALLOW_THREADS;
result = avifImageRGBToYUV(frame, &rgb);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Conversion to YUV failed: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
uint32_t addImageFlags =
is_single_frame ? AVIF_ADD_IMAGE_FLAG_SINGLE : AVIF_ADD_IMAGE_FLAG_NONE;
Py_BEGIN_ALLOW_THREADS;
result = avifEncoderAddImage(encoder, frame, duration, addImageFlags);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Failed to encode image: %s",
avifResultToString(result)
);
error = 1;
goto end;
}
end:
avifRGBImageFreePixels(&rgb);
if (!self->first_frame) {
avifImageDestroy(frame);
}
if (error) {
return NULL;
}
self->first_frame = 0;
Py_RETURN_NONE;
}
PyObject *
_encoder_finish(AvifEncoderObject *self) {
avifEncoder *encoder = self->encoder;
avifRWData raw = AVIF_DATA_EMPTY;
avifResult result;
PyObject *ret = NULL;
Py_BEGIN_ALLOW_THREADS;
result = avifEncoderFinish(encoder, &raw);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Failed to finish encoding: %s",
avifResultToString(result)
);
avifRWDataFree(&raw);
return NULL;
}
ret = PyBytes_FromStringAndSize((char *)raw.data, raw.size);
avifRWDataFree(&raw);
return ret;
}
// Decoder functions
PyObject *
AvifDecoderNew(PyObject *self_, PyObject *args) {
Py_buffer buffer;
AvifDecoderObject *self = NULL;
avifDecoder *decoder;
char *codec_str;
avifCodecChoice codec;
int max_threads;
avifResult result;
if (!PyArg_ParseTuple(args, "y*si", &buffer, &codec_str, &max_threads)) {
return NULL;
}
if (strcmp(codec_str, "auto") == 0) {
codec = AVIF_CODEC_CHOICE_AUTO;
} else {
codec = avifCodecChoiceFromName(codec_str);
}
self = PyObject_New(AvifDecoderObject, &AvifDecoder_Type);
if (!self) {
PyErr_SetString(PyExc_RuntimeError, "could not create decoder object");
PyBuffer_Release(&buffer);
return NULL;
}
decoder = avifDecoderCreate();
if (!decoder) {
PyErr_SetString(PyExc_MemoryError, "Can't allocate decoder");
PyBuffer_Release(&buffer);
PyObject_Del(self);
return NULL;
}
decoder->maxThreads = max_threads;
// Turn off libavif's 'clap' (clean aperture) property validation.
decoder->strictFlags &= ~AVIF_STRICT_CLAP_VALID;
// 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
// AV1 image items.
decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED;
decoder->codecChoice = codec;
result = avifDecoderSetIOMemory(decoder, buffer.buf, buffer.len);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Setting IO memory failed: %s",
avifResultToString(result)
);
avifDecoderDestroy(decoder);
PyBuffer_Release(&buffer);
PyObject_Del(self);
return NULL;
}
result = avifDecoderParse(decoder);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Failed to decode image: %s",
avifResultToString(result)
);
avifDecoderDestroy(decoder);
PyBuffer_Release(&buffer);
PyObject_Del(self);
return NULL;
}
self->decoder = decoder;
self->buffer = buffer;
return (PyObject *)self;
}
PyObject *
_decoder_dealloc(AvifDecoderObject *self) {
if (self->decoder) {
avifDecoderDestroy(self->decoder);
}
PyBuffer_Release(&self->buffer);
Py_RETURN_NONE;
}
PyObject *
_decoder_get_info(AvifDecoderObject *self) {
avifDecoder *decoder = self->decoder;
avifImage *image = decoder->image;
PyObject *icc = NULL;
PyObject *exif = NULL;
PyObject *xmp = NULL;
PyObject *ret = NULL;
if (image->xmp.size) {
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
}
if (image->exif.size) {
exif =
PyBytes_FromStringAndSize((const char *)image->exif.data, image->exif.size);
}
if (image->icc.size) {
icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size);
}
ret = Py_BuildValue(
"(II)IsSSIS",
image->width,
image->height,
decoder->imageCount,
decoder->alphaPresent ? "RGBA" : "RGB",
NULL == icc ? Py_None : icc,
NULL == exif ? Py_None : exif,
irot_imir_to_exif_orientation(image),
NULL == xmp ? Py_None : xmp
);
Py_XDECREF(xmp);
Py_XDECREF(exif);
Py_XDECREF(icc);
return ret;
}
PyObject *
_decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
PyObject *bytes;
PyObject *ret;
Py_ssize_t size;
avifResult result;
avifRGBImage rgb;
avifDecoder *decoder;
avifImage *image;
uint32_t frame_index;
decoder = self->decoder;
if (!PyArg_ParseTuple(args, "I", &frame_index)) {
return NULL;
}
result = avifDecoderNthImage(decoder, frame_index);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Failed to decode frame %u: %s",
frame_index,
avifResultToString(result)
);
return NULL;
}
image = decoder->image;
avifRGBImageSetDefaults(&rgb, image);
rgb.depth = 8;
rgb.format = decoder->alphaPresent ? AVIF_RGB_FORMAT_RGBA : AVIF_RGB_FORMAT_RGB;
result = avifRGBImageAllocatePixels(&rgb);
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Pixel allocation failed: %s",
avifResultToString(result)
);
return NULL;
}
Py_BEGIN_ALLOW_THREADS;
result = avifImageYUVToRGB(image, &rgb);
Py_END_ALLOW_THREADS;
if (result != AVIF_RESULT_OK) {
PyErr_Format(
exc_type_for_avif_result(result),
"Conversion from YUV failed: %s",
avifResultToString(result)
);
avifRGBImageFreePixels(&rgb);
return NULL;
}
if (rgb.height > PY_SSIZE_T_MAX / rgb.rowBytes) {
PyErr_SetString(PyExc_MemoryError, "Integer overflow in pixel size");
return NULL;
}
size = rgb.rowBytes * rgb.height;
bytes = PyBytes_FromStringAndSize((char *)rgb.pixels, size);
avifRGBImageFreePixels(&rgb);
ret = Py_BuildValue(
"SKKK",
bytes,
decoder->timescale,
decoder->imageTiming.ptsInTimescales,
decoder->imageTiming.durationInTimescales
);
Py_DECREF(bytes);
return ret;
}
/* -------------------------------------------------------------------- */
/* Type Definitions */
/* -------------------------------------------------------------------- */
// AvifEncoder methods
static struct PyMethodDef _encoder_methods[] = {
{"add", (PyCFunction)_encoder_add, METH_VARARGS},
{"finish", (PyCFunction)_encoder_finish, METH_NOARGS},
{NULL, NULL} /* sentinel */
};
// AvifEncoder type definition
static PyTypeObject AvifEncoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "AvifEncoder",
.tp_basicsize = sizeof(AvifEncoderObject),
.tp_dealloc = (destructor)_encoder_dealloc,
.tp_methods = _encoder_methods,
};
// AvifDecoder methods
static struct PyMethodDef _decoder_methods[] = {
{"get_info", (PyCFunction)_decoder_get_info, METH_NOARGS},
{"get_frame", (PyCFunction)_decoder_get_frame, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
// AvifDecoder type definition
static PyTypeObject AvifDecoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "AvifDecoder",
.tp_basicsize = sizeof(AvifDecoderObject),
.tp_dealloc = (destructor)_decoder_dealloc,
.tp_methods = _decoder_methods,
};
/* -------------------------------------------------------------------- */
/* Module Setup */
/* -------------------------------------------------------------------- */
static PyMethodDef avifMethods[] = {
{"AvifDecoder", AvifDecoderNew, METH_VARARGS},
{"AvifEncoder", AvifEncoderNew, METH_VARARGS},
{"decoder_codec_available", _decoder_codec_available, METH_VARARGS},
{"encoder_codec_available", _encoder_codec_available, METH_VARARGS},
{"codec_versions", _codec_versions, METH_NOARGS},
{NULL, NULL}
};
static int
setup_module(PyObject *m) {
if (PyType_Ready(&AvifDecoder_Type) < 0 || PyType_Ready(&AvifEncoder_Type) < 0) {
return -1;
}
PyObject *d = PyModule_GetDict(m);
PyObject *v = PyUnicode_FromString(avifVersion());
PyDict_SetItemString(d, "libavif_version", v ? v : Py_None);
Py_XDECREF(v);
return 0;
}
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC
PyInit__avif(void) {
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_avif",
.m_methods = avifMethods,
.m_slots = slots
};
return PyModuleDef_Init(&module_def);
}