This filter is designed to parse embedded ICC profiles and attempt extracting colorspace tags from them, updating the AVFrame metadata accordingly. This is intentionally made a separate filter, rather than being part of libavcodec itself, so that it's an opt-in behavior for the time being. This also gives the user more flexibility to e.g. first attach an ICC profile and then also set the colorspace tags from it. This makes #9673 possible, though not automatic.
Showing the changes to libavfilter (3 of 5 files). Show all
Unified · Split
| @@ -322,6 +322,7 @@ OBJS-$(CONFIG_HWMAP_FILTER) += vf_hwmap.o · 321 unmodified lines | |||
| 322 | OBJS-$(CONFIG_HWUPLOAD_CUDA_FILTER) += vf_hwupload_cuda.o | 322 | OBJS-$(CONFIG_HWUPLOAD_CUDA_FILTER) += vf_hwupload_cuda.o |
| 323 | OBJS-$(CONFIG_HWUPLOAD_FILTER) += vf_hwupload.o | 323 | OBJS-$(CONFIG_HWUPLOAD_FILTER) += vf_hwupload.o |
| 324 | OBJS-$(CONFIG_HYSTERESIS_FILTER) += vf_hysteresis.o framesync.o | 324 | OBJS-$(CONFIG_HYSTERESIS_FILTER) += vf_hysteresis.o framesync.o |
| 325 | OBJS-$(CONFIG_ICCDETECT_FILTER) += vf_iccdetect.o fflcms2.o colorspace.o | ||
| 325 | OBJS-$(CONFIG_ICCGEN_FILTER) += vf_iccgen.o fflcms2.o colorspace.o | 326 | OBJS-$(CONFIG_ICCGEN_FILTER) += vf_iccgen.o fflcms2.o colorspace.o |
| 326 | OBJS-$(CONFIG_IDENTITY_FILTER) += vf_identity.o | 327 | OBJS-$(CONFIG_IDENTITY_FILTER) += vf_identity.o |
| 327 | OBJS-$(CONFIG_IDET_FILTER) += vf_idet.o | 328 | OBJS-$(CONFIG_IDET_FILTER) += vf_idet.o |
| @@ -305,6 +305,7 @@ extern const AVFilter ff_vf_hwmap; · 304 unmodified lines | |||
| 305 | extern const AVFilter ff_vf_hwupload; | 305 | extern const AVFilter ff_vf_hwupload; |
| 306 | extern const AVFilter ff_vf_hwupload_cuda; | 306 | extern const AVFilter ff_vf_hwupload_cuda; |
| 307 | extern const AVFilter ff_vf_hysteresis; | 307 | extern const AVFilter ff_vf_hysteresis; |
| 308 | extern const AVFilter ff_vf_iccdetect; | ||
| 308 | extern const AVFilter ff_vf_iccgen; | 309 | extern const AVFilter ff_vf_iccgen; |
| 309 | extern const AVFilter ff_vf_identity; | 310 | extern const AVFilter ff_vf_identity; |
| 310 | extern const AVFilter ff_vf_idet; | 311 | extern const AVFilter ff_vf_idet; |
| @@ -0,0 +1,142 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2022 Niklas Haas | ||
| 3 | * This file is part of FFmpeg. | ||
| 4 | * | ||
| 5 | * FFmpeg is free software; you can redistribute it and/or | ||
| 6 | * modify it under the terms of the GNU Lesser General Public | ||
| 7 | * License as published by the Free Software Foundation; either | ||
| 8 | * version 2.1 of the License, or (at your option) any later version. | ||
| 9 | * | ||
| 10 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 13 | * Lesser General Public License for more details. | ||
| 14 | * | ||
| 15 | * You should have received a copy of the GNU Lesser General Public | ||
| 16 | * License along with FFmpeg; if not, write to the Free Software | ||
| 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 18 | */ | ||
| 19 | |||
| 20 | /** | ||
| 21 | * @file | ||
| 22 | * filter for generating ICC profiles | ||
| 23 | */ | ||
| 24 | |||
| 25 | #include <lcms2.h> | ||
| 26 | |||
| 27 | #include "libavutil/opt.h" | ||
| 28 | #include "libavutil/pixdesc.h" | ||
| 29 | |||
| 30 | #include "avfilter.h" | ||
| 31 | #include "fflcms2.h" | ||
| 32 | #include "internal.h" | ||
| 33 | |||
| 34 | typedef struct IccDetectContext { | ||
| 35 | const AVClass *class; | ||
| 36 | FFIccContext icc; | ||
| 37 | int force; | ||
| 38 | /* (cached) detected ICC profile values */ | ||
| 39 | AVBufferRef *profile; | ||
| 40 | enum AVColorPrimaries profile_prim; | ||
| 41 | enum AVColorTransferCharacteristic profile_trc; | ||
| 42 | } IccDetectContext; | ||
| 43 | |||
| 44 | #define OFFSET(x) offsetof(IccDetectContext, x) | ||
| 45 | #define VF AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | ||
| 46 | |||
| 47 | static const AVOption iccdetect_options[] = { | ||
| 48 | { "force", "overwrite existing tags", OFFSET(force), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1, VF }, | ||
| 49 | { NULL } | ||
| 50 | }; | ||
| 51 | |||
| 52 | AVFILTER_DEFINE_CLASS(iccdetect); | ||
| 53 | |||
| 54 | static av_cold void iccdetect_uninit(AVFilterContext *avctx) | ||
| 55 | { | ||
| 56 | IccDetectContext *s = avctx->priv; | ||
| 57 | av_buffer_unref(&s->profile); | ||
| 58 | ff_icc_context_uninit(&s->icc); | ||
| 59 | } | ||
| 60 | |||
| 61 | static av_cold int iccdetect_init(AVFilterContext *avctx) | ||
| 62 | { | ||
| 63 | IccDetectContext *s = avctx->priv; | ||
| 64 | return ff_icc_context_init(&s->icc, avctx); | ||
| 65 | } | ||
| 66 | |||
| 67 | static int iccdetect_filter_frame(AVFilterLink *inlink, AVFrame *frame) | ||
| 68 | { | ||
| 69 | AVFilterContext *avctx = inlink->dst; | ||
| 70 | IccDetectContext *s = avctx->priv; | ||
| 71 | const AVFrameSideData *sd; | ||
| 72 | struct ColorPrimaries coeffs; | ||
| 73 | cmsHPROFILE profile; | ||
| 74 | int ret; | ||
| 75 | |||
| 76 | sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE); | ||
| 77 | if (!sd) | ||
| 78 | return ff_filter_frame(inlink->dst->outputs[0], frame); | ||
| 79 | |||
| 80 | if (s->profile && s->profile->data == sd->buf->data) { | ||
| 81 | /* No change from previous ICC profile */ | ||
| 82 | goto done; | ||
| 83 | } | ||
| 84 | |||
| 85 | if ((ret = av_buffer_replace(&s->profile, sd->buf)) < 0) | ||
| 86 | return ret; | ||
| 87 | s->profile_prim = AVCOL_PRI_UNSPECIFIED; | ||
| 88 | s->profile_trc = AVCOL_TRC_UNSPECIFIED; | ||
| 89 | |||
| 90 | profile = cmsOpenProfileFromMemTHR(s->icc.ctx, sd->data, sd->size); | ||
| 91 | if (!profile) | ||
| 92 | return AVERROR_INVALIDDATA; | ||
| 93 | |||
| 94 | ret = ff_icc_profile_read_primaries(&s->icc, profile, &coeffs); | ||
| 95 | if (!ret) | ||
| 96 | ret = ff_icc_profile_detect_transfer(&s->icc, profile, &s->profile_trc); | ||
| 97 | cmsCloseProfile(profile); | ||
| 98 | if (ret < 0) | ||
| 99 | return ret; | ||
| 100 | |||
| 101 | s->profile_prim = ff_detect_color_primaries(&coeffs); | ||
| 102 | |||
| 103 | done: | ||
| 104 | if (s->profile_prim != AVCOL_PRI_UNSPECIFIED) { | ||
| 105 | if (s->force || frame->color_primaries == AVCOL_PRI_UNSPECIFIED) | ||
| 106 | frame->color_primaries = s->profile_prim; | ||
| 107 | } | ||
| 108 | |||
| 109 | if (s->profile_trc != AVCOL_TRC_UNSPECIFIED) { | ||
| 110 | if (s->force || frame->color_trc == AVCOL_TRC_UNSPECIFIED) | ||
| 111 | frame->color_trc = s->profile_trc; | ||
| 112 | } | ||
| 113 | |||
| 114 | return ff_filter_frame(inlink->dst->outputs[0], frame); | ||
| 115 | } | ||
| 116 | |||
| 117 | static const AVFilterPad iccdetect_inputs[] = { | ||
| 118 | { | ||
| 119 | .name = "default", | ||
| 120 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 121 | .filter_frame = iccdetect_filter_frame, | ||
| 122 | }, | ||
| 123 | }; | ||
| 124 | |||
| 125 | static const AVFilterPad iccdetect_outputs[] = { | ||
| 126 | { | ||
| 127 | .name = "default", | ||
| 128 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 129 | }, | ||
| 130 | }; | ||
| 131 | |||
| 132 | const AVFilter ff_vf_iccdetect = { | ||
| 133 | .name = "iccdetect", | ||
| 134 | .description = NULL_IF_CONFIG_SMALL("Detect and parse ICC profiles."), | ||
| 135 | .priv_size = sizeof(IccDetectContext), | ||
| 136 | .priv_class = &iccdetect_class, | ||
| 137 | .flags = AVFILTER_FLAG_METADATA_ONLY, | ||
| 138 | .init = &iccdetect_init, | ||
| 139 | .uninit = &iccdetect_uninit, | ||
| 140 | FILTER_INPUTS(iccdetect_inputs), | ||
| 141 | FILTER_OUTPUTS(iccdetect_outputs), | ||
| 142 | }; | ||