mirror of https://github.com/mpv-player/mpv
video/image_writer: add avif screenshot support
Notes: - converts the (image) write() api to filenames, because using avio with FILE* is a pain. - adds more debug logs for screenshots. build: rename av1 dependency to avif_muxer wscript: unify lavf dependency with meson
This commit is contained in:
parent
0bfafd2451
commit
de7f4fb1ee
|
@ -4317,6 +4317,7 @@ Screenshot
|
||||||
:jpeg: JPEG (alias for jpg)
|
:jpeg: JPEG (alias for jpg)
|
||||||
:webp: WebP
|
:webp: WebP
|
||||||
:jxl: JPEG XL
|
:jxl: JPEG XL
|
||||||
|
:avif: AVIF
|
||||||
|
|
||||||
``--screenshot-tag-colorspace=<yes|no>``
|
``--screenshot-tag-colorspace=<yes|no>``
|
||||||
Tag screenshots with the appropriate colorspace.
|
Tag screenshots with the appropriate colorspace.
|
||||||
|
@ -4472,6 +4473,33 @@ Screenshot
|
||||||
Set the JPEG XL compression effort. Higher effort (usually) means better
|
Set the JPEG XL compression effort. Higher effort (usually) means better
|
||||||
compression, but takes more CPU time. The default is 4.
|
compression, but takes more CPU time. The default is 4.
|
||||||
|
|
||||||
|
``--screenshot-avif-encoder=<encoder>``
|
||||||
|
Specify the AV1 encoder to be used by libavcodec for encoding avif
|
||||||
|
screenshots.
|
||||||
|
|
||||||
|
Default: ``libaom-av1``
|
||||||
|
|
||||||
|
``--screenshot-avif-pixfmt=<format>``
|
||||||
|
Specify the pixel format to the libavcodec encoder.
|
||||||
|
|
||||||
|
Default: ``yuv420p``
|
||||||
|
|
||||||
|
``--screenshot-avif-opts=key1=value1,key2=value2,...``
|
||||||
|
Specifies libavcodec options for selected encoder. For more information,
|
||||||
|
consult the FFmpeg documentation.
|
||||||
|
|
||||||
|
Default: ``usage=allintra,crf=32,cpu-used=8,tune=ssim``
|
||||||
|
|
||||||
|
Note: the default is only guaranteed to work with the libaom-av1 encoder.
|
||||||
|
Above options may not be valid and or optimal for other encoders.
|
||||||
|
|
||||||
|
This is a key/value list option. See `List Options`_ for details.
|
||||||
|
|
||||||
|
.. admonition:: Example
|
||||||
|
|
||||||
|
"``--screenshot-avif-opts=crf=32,aq-mode=complexity``"
|
||||||
|
sets the crf to 32 and quantization (aq-mode) to complexity based.
|
||||||
|
|
||||||
``--screenshot-sw=<yes|no>``
|
``--screenshot-sw=<yes|no>``
|
||||||
Whether to use software rendering for screenshots (default: no).
|
Whether to use software rendering for screenshots (default: no).
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ features = {
|
||||||
'ffmpeg': true,
|
'ffmpeg': true,
|
||||||
'gpl': get_option('gpl'),
|
'gpl': get_option('gpl'),
|
||||||
'jpegxl': libavformat.version().version_compare('>= 59.27.100'),
|
'jpegxl': libavformat.version().version_compare('>= 59.27.100'),
|
||||||
|
'avif_muxer': libavformat.version().version_compare('>= 59.24.100'),
|
||||||
'libass': true,
|
'libass': true,
|
||||||
'threads': true,
|
'threads': true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,12 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
#include <libavutil/mem.h>
|
#include <libavutil/mem.h>
|
||||||
#include <libavutil/opt.h>
|
#include <libavutil/opt.h>
|
||||||
|
#include <libavutil/pixdesc.h>
|
||||||
|
|
||||||
|
#include "common/msg.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#if HAVE_JPEG
|
#if HAVE_JPEG
|
||||||
|
@ -32,11 +35,13 @@
|
||||||
|
|
||||||
#include "osdep/io.h"
|
#include "osdep/io.h"
|
||||||
|
|
||||||
|
#include "common/av_common.h"
|
||||||
|
#include "common/msg.h"
|
||||||
#include "image_writer.h"
|
#include "image_writer.h"
|
||||||
#include "mpv_talloc.h"
|
#include "mpv_talloc.h"
|
||||||
|
#include "video/fmt-conversion.h"
|
||||||
#include "video/img_format.h"
|
#include "video/img_format.h"
|
||||||
#include "video/mp_image.h"
|
#include "video/mp_image.h"
|
||||||
#include "video/fmt-conversion.h"
|
|
||||||
#include "video/sws_utils.h"
|
#include "video/sws_utils.h"
|
||||||
|
|
||||||
#include "options/m_option.h"
|
#include "options/m_option.h"
|
||||||
|
@ -52,6 +57,15 @@ const struct image_writer_opts image_writer_opts_defaults = {
|
||||||
.webp_compression = 4,
|
.webp_compression = 4,
|
||||||
.jxl_distance = 1.0,
|
.jxl_distance = 1.0,
|
||||||
.jxl_effort = 4,
|
.jxl_effort = 4,
|
||||||
|
.avif_encoder = "libaom-av1",
|
||||||
|
.avif_pixfmt = "yuv420p",
|
||||||
|
.avif_opts = (char*[]){
|
||||||
|
"usage", "allintra",
|
||||||
|
"crf", "32",
|
||||||
|
"cpu-used", "8",
|
||||||
|
"tune", "ssim",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
.tag_csp = true,
|
.tag_csp = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,6 +76,9 @@ const struct m_opt_choice_alternatives mp_image_writer_formats[] = {
|
||||||
{"webp", AV_CODEC_ID_WEBP},
|
{"webp", AV_CODEC_ID_WEBP},
|
||||||
#if HAVE_JPEGXL
|
#if HAVE_JPEGXL
|
||||||
{"jxl", AV_CODEC_ID_JPEGXL},
|
{"jxl", AV_CODEC_ID_JPEGXL},
|
||||||
|
#endif
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
{"avif", AV_CODEC_ID_AV1},
|
||||||
#endif
|
#endif
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
@ -80,6 +97,11 @@ const struct m_option image_writer_opts[] = {
|
||||||
#if HAVE_JPEGXL
|
#if HAVE_JPEGXL
|
||||||
{"jxl-distance", OPT_DOUBLE(jxl_distance), M_RANGE(0.0, 15.0)},
|
{"jxl-distance", OPT_DOUBLE(jxl_distance), M_RANGE(0.0, 15.0)},
|
||||||
{"jxl-effort", OPT_INT(jxl_effort), M_RANGE(1, 9)},
|
{"jxl-effort", OPT_INT(jxl_effort), M_RANGE(1, 9)},
|
||||||
|
#endif
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
{"avif-encoder", OPT_STRING(avif_encoder)},
|
||||||
|
{"avif-opts", OPT_KEYVALUELIST(avif_opts)},
|
||||||
|
{"avif-pixfmt", OPT_STRING(avif_pixfmt)},
|
||||||
#endif
|
#endif
|
||||||
{"high-bit-depth", OPT_BOOL(high_bit_depth)},
|
{"high-bit-depth", OPT_BOOL(high_bit_depth)},
|
||||||
{"tag-colorspace", OPT_BOOL(tag_csp)},
|
{"tag-colorspace", OPT_BOOL(tag_csp)},
|
||||||
|
@ -102,8 +124,15 @@ static enum AVPixelFormat replace_j_format(enum AVPixelFormat fmt)
|
||||||
return fmt;
|
return fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
|
|
||||||
|
static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const char *filename)
|
||||||
{
|
{
|
||||||
|
FILE* fp = fopen(filename, "wb");
|
||||||
|
if (!fp) {
|
||||||
|
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
AVFrame *pic = NULL;
|
AVFrame *pic = NULL;
|
||||||
AVPacket *pkt = NULL;
|
AVPacket *pkt = NULL;
|
||||||
|
@ -208,7 +237,7 @@ error_exit:
|
||||||
avcodec_free_context(&avctx);
|
avcodec_free_context(&avctx);
|
||||||
av_frame_free(&pic);
|
av_frame_free(&pic);
|
||||||
av_packet_free(&pkt);
|
av_packet_free(&pkt);
|
||||||
return success;
|
return !fclose(fp) && success;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAVE_JPEG
|
#if HAVE_JPEG
|
||||||
|
@ -222,8 +251,15 @@ static void write_jpeg_error_exit(j_common_ptr cinfo)
|
||||||
longjmp(*(jmp_buf*)cinfo->client_data, 1);
|
longjmp(*(jmp_buf*)cinfo->client_data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
|
static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
|
const char *filename)
|
||||||
{
|
{
|
||||||
|
FILE *fp = fopen(filename, "wb");
|
||||||
|
if (!fp) {
|
||||||
|
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
struct jpeg_compress_struct cinfo;
|
struct jpeg_compress_struct cinfo;
|
||||||
struct jpeg_error_mgr jerr;
|
struct jpeg_error_mgr jerr;
|
||||||
|
|
||||||
|
@ -270,7 +306,242 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
|
||||||
|
|
||||||
jpeg_destroy_compress(&cinfo);
|
jpeg_destroy_compress(&cinfo);
|
||||||
|
|
||||||
return true;
|
return !fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
|
||||||
|
static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
if (!mp_msg_test(ctx->log, MSGL_DEBUG))
|
||||||
|
return;
|
||||||
|
char dbgbuff[129];
|
||||||
|
if (size)
|
||||||
|
MP_DBG(ctx, "write_avif() packet side data:\n");
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
AVPacketSideData *sd = &data[i];
|
||||||
|
int k = 0;
|
||||||
|
for (; k < MPMIN(sd->size, 64); k++)
|
||||||
|
sprintf(dbgbuff + k*2, "%02x", sd->data[k]);
|
||||||
|
dbgbuff[k] = '\0';
|
||||||
|
MP_DBG(ctx, " [%d] = {[%s], '%s'}\n",
|
||||||
|
i, av_packet_side_data_name(sd->type), dbgbuff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
|
const char *filename)
|
||||||
|
{
|
||||||
|
const AVCodec *codec = NULL;
|
||||||
|
const AVOutputFormat *ofmt = NULL;
|
||||||
|
AVCodecContext *avctx = NULL;
|
||||||
|
AVIOContext *avioctx = NULL;
|
||||||
|
AVFormatContext *fmtctx = NULL;
|
||||||
|
AVStream *stream = NULL;
|
||||||
|
AVFrame *pic = NULL;
|
||||||
|
AVPacket *pkt = NULL;
|
||||||
|
int ret;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
codec = avcodec_find_encoder_by_name(ctx->opts->avif_encoder);
|
||||||
|
if (!codec) {
|
||||||
|
MP_ERR(ctx, "Could not find encoder '%s', for saving images\n",
|
||||||
|
ctx->opts->avif_encoder);
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ofmt = av_guess_format("avif", NULL, NULL);
|
||||||
|
if (!ofmt) {
|
||||||
|
MP_ERR(ctx, "Could not guess output format 'avif'\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
avctx = avcodec_alloc_context3(codec);
|
||||||
|
if (!avctx) {
|
||||||
|
MP_ERR(ctx, "Failed to allocate AVContext.\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
avctx->width = image->w;
|
||||||
|
avctx->height = image->h;
|
||||||
|
avctx->time_base = (AVRational){1, 30};
|
||||||
|
avctx->pkt_timebase = (AVRational){1, 30};
|
||||||
|
avctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt);
|
||||||
|
|
||||||
|
av_opt_set_int(avctx, "still-image", 1, AV_OPT_SEARCH_CHILDREN);
|
||||||
|
|
||||||
|
AVDictionary *avd = NULL;
|
||||||
|
mp_set_avdict(&avd, ctx->opts->avif_opts);
|
||||||
|
av_opt_set_dict2(avctx, &avd, AV_OPT_SEARCH_CHILDREN);
|
||||||
|
av_dict_free(&avd);
|
||||||
|
|
||||||
|
pic = av_frame_alloc();
|
||||||
|
if (!pic) {
|
||||||
|
MP_ERR(ctx, "Could not allocate AVFrame\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int n = 0; n < 4; n++) {
|
||||||
|
pic->data[n] = image->planes[n];
|
||||||
|
pic->linesize[n] = image->stride[n];
|
||||||
|
}
|
||||||
|
pic->format = avctx->pix_fmt;
|
||||||
|
pic->width = avctx->width;
|
||||||
|
pic->height = avctx->height;
|
||||||
|
// Not setting this flag caused ffmpeg to output avif that was not passing
|
||||||
|
// standard checks but ffmpeg would still read and not complain...
|
||||||
|
avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||||
|
avctx->color_range = pic->color_range =
|
||||||
|
mp_csp_levels_to_avcol_range(image->params.color.levels);
|
||||||
|
avctx->color_primaries = pic->color_primaries =
|
||||||
|
mp_csp_prim_to_avcol_pri(image->params.color.primaries);
|
||||||
|
avctx->color_trc = pic->color_trc =
|
||||||
|
mp_csp_trc_to_avcol_trc(image->params.color.gamma);
|
||||||
|
avctx->colorspace = pic->colorspace =
|
||||||
|
mp_csp_to_avcol_spc(image->params.color.space);
|
||||||
|
avctx->chroma_sample_location = pic->chroma_location =
|
||||||
|
mp_chroma_location_to_av(image->params.chroma_location);
|
||||||
|
|
||||||
|
ret = avcodec_open2(avctx, codec, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not open file '%s' for saving images\n", filename);
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmtctx = avformat_alloc_context();
|
||||||
|
if (!fmtctx) {
|
||||||
|
MP_ERR(ctx, "Could not allocate format context\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
fmtctx->pb = avioctx;
|
||||||
|
fmtctx->oformat = ofmt;
|
||||||
|
|
||||||
|
stream = avformat_new_stream(fmtctx, codec);
|
||||||
|
if (!stream) {
|
||||||
|
MP_ERR(ctx, "Could not allocate stream\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avcodec_parameters_from_context(stream->codecpar, avctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not copy parameters from context\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
MP_DBG(ctx, "write_avif() Codec Context:\n"
|
||||||
|
" color_trc = %s\n"
|
||||||
|
" color_primaries = %s\n"
|
||||||
|
" color_range = %s\n"
|
||||||
|
" colorspace = %s\n"
|
||||||
|
" chroma_sample_location = %s\n",
|
||||||
|
av_color_transfer_name(avctx->color_trc),
|
||||||
|
av_color_primaries_name(avctx->color_primaries),
|
||||||
|
av_color_range_name(avctx->color_range),
|
||||||
|
av_color_space_name(avctx->colorspace),
|
||||||
|
av_chroma_location_name(avctx->chroma_sample_location)
|
||||||
|
);
|
||||||
|
|
||||||
|
ret = avformat_init_output(fmtctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not initialize output\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avformat_write_header(fmtctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not write format header\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt = av_packet_alloc();
|
||||||
|
if (!pkt) {
|
||||||
|
MP_ERR(ctx, "Could not allocate packet\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avcodec_send_frame(avctx, pic);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Error sending frame\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pts = 0;
|
||||||
|
log_side_data(ctx, avctx->coded_side_data, avctx->nb_coded_side_data);
|
||||||
|
while (ret >= 0) {
|
||||||
|
ret = avcodec_receive_packet(avctx, pkt);
|
||||||
|
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||||
|
break;
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Error receiving packet\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
pkt->pts = ++pts;
|
||||||
|
pkt->dts = pts;
|
||||||
|
pkt->stream_index = stream->index;
|
||||||
|
log_side_data(ctx, pkt->side_data, pkt->side_data_elems);
|
||||||
|
|
||||||
|
ret = av_write_frame(fmtctx, pkt);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Error writing frame\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
av_packet_unref(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = avcodec_send_frame(avctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Error sending flushing frame\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ret >= 0) {
|
||||||
|
ret = avcodec_receive_packet(avctx, pkt);
|
||||||
|
if (ret == AVERROR_EOF)
|
||||||
|
break;
|
||||||
|
if (ret != 0) {
|
||||||
|
MP_ERR(ctx, "Error receiving packet\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
pkt->pts = ++pts;
|
||||||
|
pkt->dts = pts;
|
||||||
|
pkt->stream_index = stream->index;
|
||||||
|
log_side_data(ctx, pkt->side_data, pkt->side_data_elems);
|
||||||
|
|
||||||
|
ret = av_write_frame(fmtctx, pkt);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Error writing frame\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
av_packet_unref(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = av_write_trailer(fmtctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
MP_ERR(ctx, "Could not write trailer\n");
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
MP_DBG(ctx, "write_avif(): avio_size() = %ld\n", avio_size(avioctx));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
free_data:
|
||||||
|
success = !avio_closep(&avioctx) && success;
|
||||||
|
avformat_free_context(fmtctx);
|
||||||
|
avcodec_free_context(&avctx);
|
||||||
|
av_packet_free(&pkt);
|
||||||
|
av_frame_free(&pic);
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -335,6 +606,9 @@ bool image_writer_high_depth(const struct image_writer_opts *opts)
|
||||||
return opts->format == AV_CODEC_ID_PNG
|
return opts->format == AV_CODEC_ID_PNG
|
||||||
#if HAVE_JPEGXL
|
#if HAVE_JPEGXL
|
||||||
|| opts->format == AV_CODEC_ID_JPEGXL
|
|| opts->format == AV_CODEC_ID_JPEGXL
|
||||||
|
#endif
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
|| opts->format == AV_CODEC_ID_AV1
|
||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -345,6 +619,9 @@ bool image_writer_flexible_csp(const struct image_writer_opts *opts)
|
||||||
#if HAVE_JPEGXL
|
#if HAVE_JPEGXL
|
||||||
|| opts->format == AV_CODEC_ID_JPEGXL
|
|| opts->format == AV_CODEC_ID_JPEGXL
|
||||||
#endif
|
#endif
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
|| opts->format == AV_CODEC_ID_AV1
|
||||||
|
#endif
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 58, 100)
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 58, 100)
|
||||||
// This version added support for cICP tag writing
|
// This version added support for cICP tag writing
|
||||||
|| opts->format == AV_CODEC_ID_PNG
|
|| opts->format == AV_CODEC_ID_PNG
|
||||||
|
@ -423,7 +700,7 @@ static struct mp_image *convert_image(struct mp_image *image, int destfmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
const char *filename, struct mpv_global *global,
|
const char *filename, struct mpv_global *global,
|
||||||
struct mp_log *log)
|
struct mp_log *log)
|
||||||
{
|
{
|
||||||
struct image_writer_opts defs = image_writer_opts_defaults;
|
struct image_writer_opts defs = image_writer_opts_defaults;
|
||||||
|
@ -431,7 +708,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
opts = &defs;
|
opts = &defs;
|
||||||
|
|
||||||
struct image_writer_ctx ctx = { log, opts, image->fmt };
|
struct image_writer_ctx ctx = { log, opts, image->fmt };
|
||||||
bool (*write)(struct image_writer_ctx *, mp_image_t *, FILE *) = write_lavc;
|
bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *) = write_lavc;
|
||||||
int destfmt = 0;
|
int destfmt = 0;
|
||||||
|
|
||||||
#if HAVE_JPEG
|
#if HAVE_JPEG
|
||||||
|
@ -439,6 +716,12 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
write = write_jpeg;
|
write = write_jpeg;
|
||||||
destfmt = IMGFMT_RGB24;
|
destfmt = IMGFMT_RGB24;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if HAVE_AVIF_MUXER
|
||||||
|
if (opts->format == AV_CODEC_ID_AV1) {
|
||||||
|
write = write_avif;
|
||||||
|
destfmt = mp_imgfmt_from_name(bstr0(opts->avif_pixfmt));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) {
|
if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) {
|
||||||
// For lossy images, libwebp has its own RGB->YUV conversion.
|
// For lossy images, libwebp has its own RGB->YUV conversion.
|
||||||
|
@ -461,16 +744,9 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
if (!dst)
|
if (!dst)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "wb");
|
bool success = write(&ctx, dst, filename);
|
||||||
bool success = false;
|
if (!success)
|
||||||
if (fp == NULL) {
|
mp_err(log, "Error writing file '%s'!\n", filename);
|
||||||
mp_err(log, "Error opening '%s' for writing!\n", filename);
|
|
||||||
} else {
|
|
||||||
success = write(&ctx, dst, fp);
|
|
||||||
success = !fclose(fp) && success;
|
|
||||||
if (!success)
|
|
||||||
mp_err(log, "Error writing file '%s'!\n", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
talloc_free(dst);
|
talloc_free(dst);
|
||||||
return success;
|
return success;
|
||||||
|
|
|
@ -32,6 +32,9 @@ struct image_writer_opts {
|
||||||
int webp_compression;
|
int webp_compression;
|
||||||
double jxl_distance;
|
double jxl_distance;
|
||||||
int jxl_effort;
|
int jxl_effort;
|
||||||
|
char *avif_encoder;
|
||||||
|
char *avif_pixfmt;
|
||||||
|
char **avif_opts;
|
||||||
bool tag_csp;
|
bool tag_csp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
4
wscript
4
wscript
|
@ -411,6 +411,10 @@ iconv support use --disable-iconv.",
|
||||||
'name': 'jpegxl',
|
'name': 'jpegxl',
|
||||||
'desc': 'JPEG XL support via libavcodec',
|
'desc': 'JPEG XL support via libavcodec',
|
||||||
'func': check_pkg_config('libavcodec >= 59.27.100'),
|
'func': check_pkg_config('libavcodec >= 59.27.100'),
|
||||||
|
}, {
|
||||||
|
'name': 'avif_muxer',
|
||||||
|
'desc': 'avif support via libavcodec',
|
||||||
|
'func': check_pkg_config('libavformat >= 59.24.100'),
|
||||||
}, {
|
}, {
|
||||||
'name': 'rubberband-3',
|
'name': 'rubberband-3',
|
||||||
'desc': 'new engine support for librubberband',
|
'desc': 'new engine support for librubberband',
|
||||||
|
|
Loading…
Reference in New Issue