/src/mpv/video/image_writer.c
Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdio.h> |
19 | | #include <stdlib.h> |
20 | | #include <string.h> |
21 | | #include <inttypes.h> |
22 | | |
23 | | #include <libavcodec/avcodec.h> |
24 | | #include <libavformat/avformat.h> |
25 | | #include <libavutil/mem.h> |
26 | | #include <libavutil/opt.h> |
27 | | #include <libavutil/pixdesc.h> |
28 | | #include <libplacebo/utils/libav.h> |
29 | | |
30 | | #include "common/msg.h" |
31 | | #include "config.h" |
32 | | |
33 | | #if HAVE_JPEG |
34 | | #include <setjmp.h> |
35 | | #include <jpeglib.h> |
36 | | #endif |
37 | | |
38 | | #include "osdep/io.h" |
39 | | #include "misc/path_utils.h" |
40 | | |
41 | | #include "common/av_common.h" |
42 | | #include "common/msg.h" |
43 | | #include "image_writer.h" |
44 | | #include "mpv_talloc.h" |
45 | | #include "misc/lavc_compat.h" |
46 | | #include "video/fmt-conversion.h" |
47 | | #include "video/img_format.h" |
48 | | #include "video/mp_image.h" |
49 | | #include "video/sws_utils.h" |
50 | | |
51 | | #include "options/m_option.h" |
52 | | |
53 | | const struct image_writer_opts image_writer_opts_defaults = { |
54 | | .format = AV_CODEC_ID_MJPEG, |
55 | | .high_bit_depth = true, |
56 | | .png_compression = 7, |
57 | | .png_filter = 5, |
58 | | .jpeg_quality = 90, |
59 | | .jpeg_source_chroma = true, |
60 | | .webp_quality = 75, |
61 | | .webp_compression = 4, |
62 | | .jxl_distance = 1.0, |
63 | | .jxl_effort = 4, |
64 | | .avif_encoder = "libaom-av1", |
65 | | .avif_opts = (char*[]){ |
66 | | "usage", "allintra", |
67 | | "crf", "0", |
68 | | "cpu-used", "8", |
69 | | NULL |
70 | | }, |
71 | | .tag_csp = true, |
72 | | }; |
73 | | |
74 | | const struct m_opt_choice_alternatives mp_image_writer_formats[] = { |
75 | | {"jpg", AV_CODEC_ID_MJPEG}, |
76 | | {"jpeg", AV_CODEC_ID_MJPEG}, |
77 | | {"png", AV_CODEC_ID_PNG}, |
78 | | {"webp", AV_CODEC_ID_WEBP}, |
79 | | {"jxl", AV_CODEC_ID_JPEGXL}, |
80 | | {"avif", AV_CODEC_ID_AV1}, |
81 | | {0} |
82 | | }; |
83 | | |
84 | | #define OPT_BASE_STRUCT struct image_writer_opts |
85 | | |
86 | | const struct m_option image_writer_opts[] = { |
87 | | {"format", OPT_CHOICE_C(format, mp_image_writer_formats)}, |
88 | | {"jpeg-quality", OPT_INT(jpeg_quality), M_RANGE(0, 100)}, |
89 | | {"jpeg-source-chroma", OPT_BOOL(jpeg_source_chroma)}, |
90 | | {"png-compression", OPT_INT(png_compression), M_RANGE(0, 9)}, |
91 | | {"png-filter", OPT_INT(png_filter), M_RANGE(0, 5)}, |
92 | | {"webp-lossless", OPT_BOOL(webp_lossless)}, |
93 | | {"webp-quality", OPT_INT(webp_quality), M_RANGE(0, 100)}, |
94 | | {"webp-compression", OPT_INT(webp_compression), M_RANGE(0, 6)}, |
95 | | {"jxl-distance", OPT_DOUBLE(jxl_distance), M_RANGE(0.0, 15.0)}, |
96 | | {"jxl-effort", OPT_INT(jxl_effort), M_RANGE(1, 9)}, |
97 | | {"avif-encoder", OPT_STRING(avif_encoder)}, |
98 | | {"avif-opts", OPT_KEYVALUELIST(avif_opts)}, |
99 | | {"avif-pixfmt", OPT_STRING(avif_pixfmt)}, |
100 | | {"high-bit-depth", OPT_BOOL(high_bit_depth)}, |
101 | | {"tag-colorspace", OPT_BOOL(tag_csp)}, |
102 | | {0}, |
103 | | }; |
104 | | |
105 | | struct image_writer_ctx { |
106 | | struct mp_log *log; |
107 | | const struct image_writer_opts *opts; |
108 | | struct mp_imgfmt_desc original_format; |
109 | | }; |
110 | | |
111 | | static enum AVPixelFormat replace_j_format(enum AVPixelFormat fmt) |
112 | 0 | { |
113 | 0 | switch (fmt) { |
114 | 0 | case AV_PIX_FMT_YUV420P: return AV_PIX_FMT_YUVJ420P; |
115 | 0 | case AV_PIX_FMT_YUV422P: return AV_PIX_FMT_YUVJ422P; |
116 | 0 | case AV_PIX_FMT_YUV444P: return AV_PIX_FMT_YUVJ444P; |
117 | 0 | } |
118 | 0 | return fmt; |
119 | 0 | } |
120 | | |
121 | | static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx, |
122 | | mp_image_t *image, bool tag_csp, |
123 | | struct mp_log *log) |
124 | 0 | { |
125 | 0 | for (int n = 0; n < 4; n++) { |
126 | 0 | pic->data[n] = image->planes[n]; |
127 | 0 | pic->linesize[n] = image->stride[n]; |
128 | 0 | } |
129 | 0 | pic->format = avctx->pix_fmt; |
130 | 0 | pic->width = avctx->width; |
131 | 0 | pic->height = avctx->height; |
132 | 0 | pl_avframe_set_repr(pic, image->params.repr); |
133 | 0 | avctx->colorspace = pic->colorspace; |
134 | 0 | avctx->color_range = pic->color_range; |
135 | |
|
136 | 0 | if (!tag_csp) |
137 | 0 | return; |
138 | 0 | pl_avframe_set_color(pic, image->params.color); |
139 | 0 | avctx->color_primaries = pic->color_primaries; |
140 | 0 | avctx->color_trc = pic->color_trc; |
141 | 0 | avctx->chroma_sample_location = pic->chroma_location = |
142 | 0 | pl_chroma_to_av(image->params.chroma_location); |
143 | |
|
144 | 0 | mp_dbg(log, "mapped color params:\n" |
145 | 0 | " trc = %s\n" |
146 | 0 | " primaries = %s\n" |
147 | 0 | " range = %s\n" |
148 | 0 | " colorspace = %s\n" |
149 | 0 | " chroma_location = %s\n", |
150 | 0 | av_color_transfer_name(avctx->color_trc), |
151 | 0 | av_color_primaries_name(avctx->color_primaries), |
152 | 0 | av_color_range_name(avctx->color_range), |
153 | 0 | av_color_space_name(avctx->colorspace), |
154 | 0 | av_chroma_location_name(avctx->chroma_sample_location) |
155 | 0 | ); |
156 | 0 | } |
157 | | |
158 | | static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) |
159 | 21 | { |
160 | 21 | bool success = false; |
161 | 21 | AVFrame *pic = NULL; |
162 | 21 | AVPacket *pkt = NULL; |
163 | | |
164 | 21 | const AVCodec *codec; |
165 | 21 | if (ctx->opts->format == AV_CODEC_ID_WEBP) { |
166 | 0 | codec = avcodec_find_encoder_by_name("libwebp"); // non-animated encoder |
167 | 21 | } else { |
168 | 21 | codec = avcodec_find_encoder(ctx->opts->format); |
169 | 21 | } |
170 | | |
171 | 21 | AVCodecContext *avctx = NULL; |
172 | 21 | if (!codec) |
173 | 21 | goto print_open_fail; |
174 | 0 | avctx = avcodec_alloc_context3(codec); |
175 | 0 | if (!avctx) |
176 | 0 | goto print_open_fail; |
177 | | |
178 | 0 | MP_VERBOSE(ctx, "Using encoder %s\n", codec->name); |
179 | |
|
180 | 0 | avctx->time_base = AV_TIME_BASE_Q; |
181 | 0 | avctx->width = image->w; |
182 | 0 | avctx->height = image->h; |
183 | 0 | avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt); |
184 | | |
185 | | /* |
186 | | * tagging avctx->bits_per_raw_sample indicates the number of significant |
187 | | * bits. For example, if the original video was 10-bit, and the GPU buffer is |
188 | | * 16-bit, this tells lavc that only 10 bits are significant. lavc encoders may |
189 | | * ignore this value, but some codecs can make use of it (for example, PNG's |
190 | | * sBIT chunk or JXL's bit depth header) |
191 | | * |
192 | | * This only works when screenshot-sw=yes is set. With hardware screenshots, |
193 | | * the "original" is whatever is in the GPU buffer, which is likely to be at |
194 | | * full bit depth already. |
195 | | */ |
196 | 0 | if (image->params.repr.bits.color_depth != image->params.repr.bits.sample_depth) { |
197 | 0 | MP_DBG(ctx, "tagging bits_per_raw_sample=%d\n", image->params.repr.bits.color_depth); |
198 | 0 | avctx->bits_per_raw_sample = image->params.repr.bits.color_depth; |
199 | 0 | } |
200 | |
|
201 | 0 | if (codec->id == AV_CODEC_ID_MJPEG) { |
202 | | // Annoying deprecated garbage for the jpg encoder. |
203 | 0 | if (image->params.repr.levels == PL_COLOR_LEVELS_FULL) |
204 | 0 | avctx->pix_fmt = replace_j_format(avctx->pix_fmt); |
205 | 0 | } |
206 | 0 | if (avctx->pix_fmt == AV_PIX_FMT_NONE) { |
207 | 0 | MP_ERR(ctx, "Image format %s not supported by lavc.\n", |
208 | 0 | mp_imgfmt_to_name(image->imgfmt)); |
209 | 0 | goto error_exit; |
210 | 0 | } |
211 | | |
212 | 0 | if (codec->id == AV_CODEC_ID_MJPEG) { |
213 | 0 | avctx->flags |= AV_CODEC_FLAG_QSCALE; |
214 | | // jpeg_quality is set below |
215 | 0 | } else if (codec->id == AV_CODEC_ID_PNG) { |
216 | 0 | avctx->compression_level = ctx->opts->png_compression; |
217 | 0 | av_opt_set_int(avctx, "pred", ctx->opts->png_filter, |
218 | 0 | AV_OPT_SEARCH_CHILDREN); |
219 | 0 | } else if (codec->id == AV_CODEC_ID_WEBP) { |
220 | 0 | avctx->compression_level = ctx->opts->webp_compression; |
221 | 0 | av_opt_set_int(avctx, "lossless", ctx->opts->webp_lossless, |
222 | 0 | AV_OPT_SEARCH_CHILDREN); |
223 | 0 | av_opt_set_int(avctx, "quality", ctx->opts->webp_quality, |
224 | 0 | AV_OPT_SEARCH_CHILDREN); |
225 | 0 | } else if (codec->id == AV_CODEC_ID_JPEGXL) { |
226 | 0 | av_opt_set_double(avctx, "distance", ctx->opts->jxl_distance, |
227 | 0 | AV_OPT_SEARCH_CHILDREN); |
228 | 0 | av_opt_set_int(avctx, "effort", ctx->opts->jxl_effort, |
229 | 0 | AV_OPT_SEARCH_CHILDREN); |
230 | 0 | } |
231 | |
|
232 | 0 | if (avcodec_open2(avctx, codec, NULL) < 0) { |
233 | 21 | print_open_fail: |
234 | 21 | MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n"); |
235 | 21 | goto error_exit; |
236 | 0 | } |
237 | | |
238 | 0 | pic = av_frame_alloc(); |
239 | 0 | if (!pic) |
240 | 0 | goto error_exit; |
241 | 0 | prepare_avframe(pic, avctx, image, ctx->opts->tag_csp, ctx->log); |
242 | 0 | if (codec->id == AV_CODEC_ID_MJPEG) { |
243 | 0 | int qscale = 1 + (100 - ctx->opts->jpeg_quality) * 30 / 100; |
244 | 0 | pic->quality = qscale * FF_QP2LAMBDA; |
245 | 0 | } |
246 | |
|
247 | 0 | int ret = avcodec_send_frame(avctx, pic); |
248 | 0 | if (ret < 0) |
249 | 0 | goto error_exit; |
250 | 0 | ret = avcodec_send_frame(avctx, NULL); // send EOF |
251 | 0 | if (ret < 0) |
252 | 0 | goto error_exit; |
253 | 0 | pkt = av_packet_alloc(); |
254 | 0 | if (!pkt) |
255 | 0 | goto error_exit; |
256 | 0 | ret = avcodec_receive_packet(avctx, pkt); |
257 | 0 | if (ret < 0) |
258 | 0 | goto error_exit; |
259 | | |
260 | 0 | success = fwrite(pkt->data, pkt->size, 1, fp) == 1; |
261 | |
|
262 | 21 | error_exit: |
263 | 21 | avcodec_free_context(&avctx); |
264 | 21 | av_frame_free(&pic); |
265 | 21 | av_packet_free(&pkt); |
266 | 21 | return success; |
267 | 0 | } |
268 | | |
269 | | #if HAVE_JPEG |
270 | | |
271 | | static void write_jpeg_error_exit(j_common_ptr cinfo) |
272 | | { |
273 | | // NOTE: do not write error message, too much effort to connect the libjpeg |
274 | | // log callbacks with mplayer's log function mp_msp() |
275 | | |
276 | | // Return control to the setjmp point |
277 | | longjmp(*(jmp_buf*)cinfo->client_data, 1); |
278 | | } |
279 | | |
280 | | static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) |
281 | | { |
282 | | struct jpeg_compress_struct cinfo; |
283 | | struct jpeg_error_mgr jerr; |
284 | | |
285 | | cinfo.err = jpeg_std_error(&jerr); |
286 | | jerr.error_exit = write_jpeg_error_exit; |
287 | | |
288 | | jmp_buf error_return_jmpbuf; |
289 | | cinfo.client_data = &error_return_jmpbuf; |
290 | | if (setjmp(cinfo.client_data)) { |
291 | | jpeg_destroy_compress(&cinfo); |
292 | | return false; |
293 | | } |
294 | | |
295 | | jpeg_create_compress(&cinfo); |
296 | | jpeg_stdio_dest(&cinfo, fp); |
297 | | |
298 | | cinfo.image_width = image->w; |
299 | | cinfo.image_height = image->h; |
300 | | cinfo.input_components = 3; |
301 | | cinfo.in_color_space = JCS_RGB; |
302 | | |
303 | | cinfo.write_JFIF_header = TRUE; |
304 | | cinfo.JFIF_major_version = 1; |
305 | | cinfo.JFIF_minor_version = 2; |
306 | | |
307 | | jpeg_set_defaults(&cinfo); |
308 | | jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, 0); |
309 | | |
310 | | if (ctx->opts->jpeg_source_chroma) { |
311 | | cinfo.comp_info[0].h_samp_factor = 1 << ctx->original_format.chroma_xs; |
312 | | cinfo.comp_info[0].v_samp_factor = 1 << ctx->original_format.chroma_ys; |
313 | | } |
314 | | |
315 | | jpeg_start_compress(&cinfo, TRUE); |
316 | | |
317 | | while (cinfo.next_scanline < cinfo.image_height) { |
318 | | JSAMPROW row_pointer[1]; |
319 | | row_pointer[0] = image->planes[0] + |
320 | | (ptrdiff_t)cinfo.next_scanline * image->stride[0]; |
321 | | jpeg_write_scanlines(&cinfo, row_pointer, 1); |
322 | | } |
323 | | |
324 | | jpeg_finish_compress(&cinfo); |
325 | | |
326 | | jpeg_destroy_compress(&cinfo); |
327 | | |
328 | | return true; |
329 | | } |
330 | | |
331 | | #endif |
332 | | |
333 | | static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data, |
334 | | size_t size) |
335 | 0 | { |
336 | 0 | if (!mp_msg_test(ctx->log, MSGL_DEBUG)) |
337 | 0 | return; |
338 | 0 | char dbgbuff[129]; |
339 | 0 | if (size) |
340 | 0 | MP_DBG(ctx, "write_avif() packet side data:\n"); |
341 | 0 | for (int i = 0; i < size; i++) { |
342 | 0 | AVPacketSideData *sd = &data[i]; |
343 | 0 | for (int k = 0; k < MPMIN(sd->size, 64); k++) |
344 | 0 | snprintf(dbgbuff + k*2, 3, "%02x", (int)sd->data[k]); |
345 | 0 | MP_DBG(ctx, " [%d] = {[%s], '%s'}\n", |
346 | 0 | i, av_packet_side_data_name(sd->type), dbgbuff); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) |
351 | 0 | { |
352 | 0 | const AVCodec *codec = NULL; |
353 | 0 | const AVOutputFormat *ofmt = NULL; |
354 | 0 | AVCodecContext *avctx = NULL; |
355 | 0 | AVIOContext *avioctx = NULL; |
356 | 0 | AVFormatContext *fmtctx = NULL; |
357 | 0 | AVStream *stream = NULL; |
358 | 0 | AVFrame *pic = NULL; |
359 | 0 | AVPacket *pkt = NULL; |
360 | 0 | int ret; |
361 | 0 | bool success = false; |
362 | |
|
363 | 0 | codec = avcodec_find_encoder_by_name(ctx->opts->avif_encoder); |
364 | 0 | if (!codec) { |
365 | 0 | MP_ERR(ctx, "Could not find encoder '%s', for saving images\n", |
366 | 0 | ctx->opts->avif_encoder); |
367 | 0 | goto free_data; |
368 | 0 | } |
369 | | |
370 | 0 | ofmt = av_guess_format("avif", NULL, NULL); |
371 | 0 | if (!ofmt) { |
372 | 0 | MP_ERR(ctx, "Could not guess output format 'avif'\n"); |
373 | 0 | goto free_data; |
374 | 0 | } |
375 | | |
376 | 0 | avctx = avcodec_alloc_context3(codec); |
377 | 0 | if (!avctx) { |
378 | 0 | MP_ERR(ctx, "Failed to allocate AVContext.\n"); |
379 | 0 | goto free_data; |
380 | 0 | } |
381 | | |
382 | 0 | avctx->width = image->w; |
383 | 0 | avctx->height = image->h; |
384 | 0 | avctx->time_base = (AVRational){1, 30}; |
385 | 0 | avctx->pkt_timebase = (AVRational){1, 30}; |
386 | 0 | avctx->codec_type = AVMEDIA_TYPE_VIDEO; |
387 | 0 | avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt); |
388 | 0 | if (avctx->pix_fmt == AV_PIX_FMT_NONE) { |
389 | 0 | MP_ERR(ctx, "Image format %s not supported by lavc.\n", |
390 | 0 | mp_imgfmt_to_name(image->imgfmt)); |
391 | 0 | goto free_data; |
392 | 0 | } |
393 | | |
394 | 0 | av_opt_set_int(avctx, "still-picture", 1, AV_OPT_SEARCH_CHILDREN); |
395 | |
|
396 | 0 | AVDictionary *avd = NULL; |
397 | 0 | mp_set_avdict(&avd, ctx->opts->avif_opts); |
398 | 0 | av_opt_set_dict2(avctx, &avd, AV_OPT_SEARCH_CHILDREN); |
399 | 0 | av_dict_free(&avd); |
400 | |
|
401 | 0 | pic = av_frame_alloc(); |
402 | 0 | if (!pic) { |
403 | 0 | MP_ERR(ctx, "Could not allocate AVFrame\n"); |
404 | 0 | goto free_data; |
405 | 0 | } |
406 | | |
407 | 0 | prepare_avframe(pic, avctx, image, ctx->opts->tag_csp, ctx->log); |
408 | | // Not setting this flag caused ffmpeg to output avif that was not passing |
409 | | // standard checks but ffmpeg would still read and not complain... |
410 | 0 | avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; |
411 | |
|
412 | 0 | ret = avcodec_open2(avctx, codec, NULL); |
413 | 0 | if (ret < 0) { |
414 | 0 | MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n"); |
415 | 0 | goto free_data; |
416 | 0 | } |
417 | | |
418 | 0 | avio_open_dyn_buf(&avioctx); |
419 | 0 | MP_HANDLE_OOM(avioctx); |
420 | | |
421 | 0 | fmtctx = avformat_alloc_context(); |
422 | 0 | if (!fmtctx) { |
423 | 0 | MP_ERR(ctx, "Could not allocate format context\n"); |
424 | 0 | goto free_data; |
425 | 0 | } |
426 | 0 | fmtctx->pb = avioctx; |
427 | 0 | fmtctx->oformat = ofmt; |
428 | |
|
429 | 0 | stream = avformat_new_stream(fmtctx, codec); |
430 | 0 | if (!stream) { |
431 | 0 | MP_ERR(ctx, "Could not allocate stream\n"); |
432 | 0 | goto free_data; |
433 | 0 | } |
434 | | |
435 | 0 | ret = avcodec_parameters_from_context(stream->codecpar, avctx); |
436 | 0 | if (ret < 0) { |
437 | 0 | MP_ERR(ctx, "Could not copy parameters from context\n"); |
438 | 0 | goto free_data; |
439 | 0 | } |
440 | | |
441 | 0 | ret = avformat_init_output(fmtctx, NULL); |
442 | 0 | if (ret < 0) { |
443 | 0 | MP_ERR(ctx, "Could not initialize output\n"); |
444 | 0 | goto free_data; |
445 | 0 | } |
446 | | |
447 | 0 | ret = avformat_write_header(fmtctx, NULL); |
448 | 0 | if (ret < 0) { |
449 | 0 | MP_ERR(ctx, "Could not write format header\n"); |
450 | 0 | goto free_data; |
451 | 0 | } |
452 | | |
453 | 0 | pkt = av_packet_alloc(); |
454 | 0 | if (!pkt) { |
455 | 0 | MP_ERR(ctx, "Could not allocate packet\n"); |
456 | 0 | goto free_data; |
457 | 0 | } |
458 | | |
459 | 0 | ret = avcodec_send_frame(avctx, pic); |
460 | 0 | if (ret < 0) { |
461 | 0 | MP_ERR(ctx, "Error sending frame\n"); |
462 | 0 | goto free_data; |
463 | 0 | } |
464 | 0 | ret = avcodec_send_frame(avctx, NULL); // send EOF |
465 | 0 | if (ret < 0) |
466 | 0 | goto free_data; |
467 | | |
468 | 0 | int pts = 0; |
469 | 0 | log_side_data(ctx, avctx->coded_side_data, avctx->nb_coded_side_data); |
470 | 0 | while (ret >= 0) { |
471 | 0 | ret = avcodec_receive_packet(avctx, pkt); |
472 | 0 | if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) |
473 | 0 | break; |
474 | 0 | if (ret < 0) { |
475 | 0 | MP_ERR(ctx, "Error receiving packet\n"); |
476 | 0 | goto free_data; |
477 | 0 | } |
478 | 0 | pkt->dts = pkt->pts = ++pts; |
479 | 0 | pkt->stream_index = stream->index; |
480 | 0 | log_side_data(ctx, pkt->side_data, pkt->side_data_elems); |
481 | |
|
482 | 0 | ret = av_write_frame(fmtctx, pkt); |
483 | 0 | if (ret < 0) { |
484 | 0 | MP_ERR(ctx, "Error writing frame\n"); |
485 | 0 | goto free_data; |
486 | 0 | } |
487 | 0 | av_packet_unref(pkt); |
488 | 0 | } |
489 | | |
490 | 0 | ret = av_write_trailer(fmtctx); |
491 | 0 | if (ret < 0) { |
492 | 0 | MP_ERR(ctx, "Could not write trailer\n"); |
493 | 0 | goto free_data; |
494 | 0 | } |
495 | 0 | MP_DBG(ctx, "write_avif(): avio_size() = %"PRIi64"\n", avio_size(avioctx)); |
496 | |
|
497 | 0 | uint8_t *buf = NULL; |
498 | 0 | int written_size = avio_close_dyn_buf(avioctx, &buf); |
499 | 0 | success = fwrite(buf, written_size, 1, fp) == 1; |
500 | 0 | av_freep(&buf); |
501 | |
|
502 | 0 | free_data: |
503 | 0 | avformat_free_context(fmtctx); |
504 | 0 | avcodec_free_context(&avctx); |
505 | 0 | av_packet_free(&pkt); |
506 | 0 | av_frame_free(&pic); |
507 | |
|
508 | 0 | return success; |
509 | 0 | } |
510 | | |
511 | | static int get_encoder_format(const AVCodec *codec, int srcfmt, bool highdepth) |
512 | 0 | { |
513 | 0 | const enum AVPixelFormat *pix_fmts; |
514 | 0 | int ret = mp_avcodec_get_supported_config(NULL, codec, |
515 | 0 | AV_CODEC_CONFIG_PIX_FORMAT, |
516 | 0 | (const void **)&pix_fmts); |
517 | 0 | int current = 0; |
518 | 0 | for (int n = 0; ret >= 0 && pix_fmts && pix_fmts[n] != AV_PIX_FMT_NONE; n++) { |
519 | 0 | int fmt = pixfmt2imgfmt(pix_fmts[n]); |
520 | 0 | if (!fmt) |
521 | 0 | continue; |
522 | 0 | if (!highdepth) { |
523 | | // Ignore formats larger than 8 bit per pixel. (Or which are unknown.) |
524 | 0 | struct mp_regular_imgfmt rdesc; |
525 | 0 | if (!mp_get_regular_imgfmt(&rdesc, fmt)) { |
526 | 0 | int ofmt = mp_find_other_endian(fmt); |
527 | 0 | if (!mp_get_regular_imgfmt(&rdesc, ofmt)) |
528 | 0 | continue; |
529 | 0 | } |
530 | 0 | if (rdesc.component_size > 1) |
531 | 0 | continue; |
532 | 0 | } |
533 | 0 | current = current ? mp_imgfmt_select_best(current, fmt, srcfmt) : fmt; |
534 | 0 | } |
535 | 0 | return current; |
536 | 0 | } |
537 | | |
538 | | static int get_target_format(struct image_writer_ctx *ctx) |
539 | 21 | { |
540 | 21 | const AVCodec *codec = avcodec_find_encoder(ctx->opts->format); |
541 | 21 | if (!codec) |
542 | 21 | goto unknown; |
543 | | |
544 | 0 | int srcfmt = ctx->original_format.id; |
545 | |
|
546 | 0 | int target = get_encoder_format(codec, srcfmt, ctx->opts->high_bit_depth); |
547 | 0 | if (!target) { |
548 | 0 | mp_dbg(ctx->log, "Falling back to high-depth format.\n"); |
549 | 0 | target = get_encoder_format(codec, srcfmt, true); |
550 | 0 | } |
551 | |
|
552 | 0 | if (!target) |
553 | 0 | goto unknown; |
554 | | |
555 | 0 | return target; |
556 | | |
557 | 21 | unknown: |
558 | 21 | return IMGFMT_RGB0; |
559 | 0 | } |
560 | | |
561 | | const char *image_writer_file_ext(const struct image_writer_opts *opts) |
562 | 21 | { |
563 | 21 | struct image_writer_opts defs = image_writer_opts_defaults; |
564 | | |
565 | 21 | if (!opts) |
566 | 0 | opts = &defs; |
567 | | |
568 | 21 | return m_opt_choice_str(mp_image_writer_formats, opts->format); |
569 | 21 | } |
570 | | |
571 | | bool image_writer_high_depth(const struct image_writer_opts *opts) |
572 | 0 | { |
573 | 0 | return opts->format == AV_CODEC_ID_PNG |
574 | 0 | || opts->format == AV_CODEC_ID_JPEGXL |
575 | 0 | || opts->format == AV_CODEC_ID_AV1; |
576 | 0 | } |
577 | | |
578 | | bool image_writer_flexible_csp(const struct image_writer_opts *opts) |
579 | 21 | { |
580 | 21 | if (!opts->tag_csp) |
581 | 0 | return false; |
582 | 21 | return opts->format == AV_CODEC_ID_JPEGXL |
583 | 21 | || opts->format == AV_CODEC_ID_AV1 |
584 | 21 | || opts->format == AV_CODEC_ID_PNG; |
585 | 21 | } |
586 | | |
587 | | int image_writer_format_from_ext(const char *ext) |
588 | 0 | { |
589 | 0 | for (int n = 0; mp_image_writer_formats[n].name; n++) { |
590 | 0 | if (ext && strcmp(mp_image_writer_formats[n].name, ext) == 0) |
591 | 0 | return mp_image_writer_formats[n].value; |
592 | 0 | } |
593 | 0 | return 0; |
594 | 0 | } |
595 | | |
596 | | static struct mp_image *convert_image(struct mp_image *image, int destfmt, |
597 | | enum pl_color_levels yuv_levels, |
598 | | const struct image_writer_opts *opts, |
599 | | struct mpv_global *global, |
600 | | struct mp_log *log) |
601 | 21 | { |
602 | 21 | int d_w, d_h; |
603 | 21 | mp_image_params_get_dsize(&image->params, &d_w, &d_h); |
604 | | |
605 | 21 | struct mp_image_params p = { |
606 | 21 | .imgfmt = destfmt, |
607 | 21 | .w = d_w, |
608 | 21 | .h = d_h, |
609 | 21 | .p_w = 1, |
610 | 21 | .p_h = 1, |
611 | 21 | .color = image->params.color, |
612 | 21 | .repr = image->params.repr, |
613 | 21 | .chroma_location = image->params.chroma_location, |
614 | 21 | .crop = {0, 0, d_w, d_h}, |
615 | 21 | }; |
616 | 21 | mp_image_params_guess_csp(&p); |
617 | | |
618 | 21 | if (!image_writer_flexible_csp(opts)) { |
619 | | // If our format can't tag csps, set something sane |
620 | 21 | p.color.primaries = PL_COLOR_PRIM_BT_709; |
621 | 21 | p.color.transfer = PL_COLOR_TRC_UNKNOWN; |
622 | 21 | p.light = MP_CSP_LIGHT_DISPLAY; |
623 | 21 | p.color.hdr = (struct pl_hdr_metadata){0}; |
624 | 21 | if (p.repr.sys != PL_COLOR_SYSTEM_RGB) { |
625 | 0 | p.repr.levels = yuv_levels; |
626 | 0 | p.repr.sys = PL_COLOR_SYSTEM_BT_601; |
627 | 0 | p.chroma_location = PL_CHROMA_CENTER; |
628 | 0 | } |
629 | 21 | mp_image_params_guess_csp(&p); |
630 | 21 | } |
631 | | |
632 | 21 | if (mp_image_params_equal(&p, &image->params)) |
633 | 0 | return mp_image_new_ref(image); |
634 | | |
635 | 21 | mp_verbose(log, "converted: %s\n", mp_image_params_to_str(&p)); |
636 | | |
637 | 21 | struct mp_image *src = image; |
638 | 21 | if (mp_image_crop_valid(&src->params) && |
639 | 21 | (mp_rect_w(src->params.crop) != src->w || |
640 | 21 | mp_rect_h(src->params.crop) != src->h)) |
641 | 0 | { |
642 | 0 | src = mp_image_new_ref(src); |
643 | 0 | if (!src) { |
644 | 0 | mp_err(log, "mp_image_new_ref failed!\n"); |
645 | 0 | return NULL; |
646 | 0 | } |
647 | 0 | mp_image_crop_rc(src, src->params.crop); |
648 | 0 | } |
649 | | |
650 | 21 | struct mp_image *dst = mp_image_alloc(p.imgfmt, p.w, p.h); |
651 | 21 | if (!dst) { |
652 | 0 | mp_err(log, "Out of memory.\n"); |
653 | 0 | return NULL; |
654 | 0 | } |
655 | 21 | mp_image_copy_attributes(dst, src); |
656 | | |
657 | 21 | dst->params = p; |
658 | | |
659 | 21 | struct mp_sws_context *sws = mp_sws_alloc(NULL); |
660 | 21 | sws->log = log; |
661 | 21 | if (global) |
662 | 21 | mp_sws_enable_cmdline_opts(sws, global); |
663 | 21 | bool ok = mp_sws_scale(sws, dst, src) >= 0; |
664 | 21 | talloc_free(sws); |
665 | | |
666 | 21 | if (src != image) |
667 | 0 | talloc_free(src); |
668 | | |
669 | 21 | if (!ok) { |
670 | 0 | mp_err(log, "Error when converting image.\n"); |
671 | 0 | talloc_free(dst); |
672 | 0 | return NULL; |
673 | 0 | } |
674 | | |
675 | 21 | return dst; |
676 | 21 | } |
677 | | |
678 | | bool write_image(struct mp_image *image, const struct image_writer_opts *opts, |
679 | | const char *filename, struct mpv_global *global, |
680 | | struct mp_log *log, bool overwrite) |
681 | 21 | { |
682 | 21 | struct image_writer_opts defs = image_writer_opts_defaults; |
683 | 21 | if (!opts) |
684 | 0 | opts = &defs; |
685 | | |
686 | 21 | mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params)); |
687 | | |
688 | 21 | struct image_writer_ctx ctx = { log, opts, image->fmt }; |
689 | 21 | bool (*write)(struct image_writer_ctx *, mp_image_t *, FILE *) = write_lavc; |
690 | 21 | int destfmt = 0; |
691 | | |
692 | | #if HAVE_JPEG |
693 | | if (opts->format == AV_CODEC_ID_MJPEG) { |
694 | | write = write_jpeg; |
695 | | destfmt = IMGFMT_RGB24; |
696 | | } |
697 | | #endif |
698 | 21 | if (opts->format == AV_CODEC_ID_AV1) { |
699 | 0 | write = write_avif; |
700 | 0 | if (opts->avif_pixfmt && opts->avif_pixfmt[0]) |
701 | 0 | destfmt = mp_imgfmt_from_name(bstr0(opts->avif_pixfmt)); |
702 | 21 | } else if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) { |
703 | | // For lossy images, libwebp has its own RGB->YUV conversion. |
704 | | // We don't want that, so force YUV/YUVA here. |
705 | 0 | int alpha = image->fmt.flags & MP_IMGFLAG_ALPHA; |
706 | 0 | destfmt = alpha ? pixfmt2imgfmt(AV_PIX_FMT_YUVA420P) : IMGFMT_420P; |
707 | 0 | } |
708 | | |
709 | 21 | if (!destfmt) |
710 | 21 | destfmt = get_target_format(&ctx); |
711 | | |
712 | 21 | enum pl_color_levels levels; // Ignored if destfmt is a RGB format |
713 | 21 | if (opts->format == AV_CODEC_ID_WEBP) { |
714 | 0 | levels = PL_COLOR_LEVELS_LIMITED; |
715 | 21 | } else { |
716 | 21 | levels = PL_COLOR_LEVELS_FULL; |
717 | 21 | } |
718 | | |
719 | 21 | struct mp_image *dst = convert_image(image, destfmt, levels, opts, global, log); |
720 | 21 | if (!dst) |
721 | 0 | return false; |
722 | | |
723 | 21 | bool success = false; |
724 | 21 | FILE *fp = fopen(filename, overwrite ? "wb" : "wbx"); |
725 | 21 | if (!fp) { |
726 | 0 | mp_err(log, "Error creating '%s' for writing: %s!\n", |
727 | 0 | filename, mp_strerror(errno)); |
728 | 0 | goto done; |
729 | 0 | } |
730 | | |
731 | 21 | success = write(&ctx, dst, fp); |
732 | 21 | if (fclose(fp) || !success) { |
733 | 21 | mp_err(log, "Error writing file '%s'!\n", filename); |
734 | 21 | unlink(filename); |
735 | 21 | } |
736 | | |
737 | 21 | done: |
738 | 21 | talloc_free(dst); |
739 | 21 | return success; |
740 | 21 | } |
741 | | |
742 | | void dump_png(struct mp_image *image, const char *filename, struct mp_log *log) |
743 | 0 | { |
744 | 0 | struct image_writer_opts opts = image_writer_opts_defaults; |
745 | 0 | opts.format = AV_CODEC_ID_PNG; |
746 | 0 | write_image(image, &opts, filename, NULL, log, true); |
747 | 0 | } |