color: input colorspace auto-detection, HDR export tags, LGG/white balance nodes, more LUT formats, waveform parade

- media color primaries/transfer tags now flow from the FFmpeg probe
  through VideoParams into Footage::get_colorspace_to_use(); precedence
  is user override > media tags > project default
- export nclc tags derive from the output colorspace (PQ/HLG/BT.2020,
  P3, sRGB, Rec.601, Rec.709) instead of hardcoded BT.709
- new OCIO Color Grading (Log) node (lift/gamma/gain) and White Balance
  node (kelvin temperature + tint, HDR-safe)
- LUT whitelist extended to 9 OCIO-supported formats
- waveform scope gains an RGB parade mode (GPU and software paths)
- tests updated for the new colorspace precedence
This commit is contained in:
2026-07-19 21:45:51 +08:00
parent a7ddc0f114
commit 76f5c2a65b
37 changed files with 1449 additions and 40 deletions
@@ -149,6 +149,27 @@ typedef enum FBColorSpace {
fb_col_spc_b_t2020_ncl = 9
} FBColorSpace;
/** Color primaries. Values mirror AVColorPrimaries (static_assert'ed). */
typedef enum FBColorPrimaries {
fb_color_primaries_bt709 = 1,
fb_color_primaries_unspec = 2,
fb_color_primaries_bt470bg = 5,
fb_color_primaries_smpte170m = 6,
fb_color_primaries_bt2020 = 9,
fb_color_primaries_smpte432 = 12
} FBColorPrimaries;
/** Color transfer characteristics. Values mirror AVColorTransferCharacteristic. */
typedef enum FBColorTrc {
fb_color_trc_bt709 = 1,
fb_color_trc_unspec = 2,
fb_color_trc_gamma28 = 5,
fb_color_trc_smpte170m = 6,
fb_color_trc_srgb = 13,
fb_color_trc_pq = 16,
fb_color_trc_hlg = 18
} FBColorTrc;
/** Media types. Values mirror AVMediaType (static_assert'ed). */
typedef enum FBMediaType {
fb_media_type_video = 0,
@@ -322,6 +343,8 @@ typedef struct FBStreamInfo {
int pixel_format; /* FBPixelFormat */
int field_order; /* FBFieldOrder */
int color_range; /* FBColorRange */
int color_primaries; /* raw AVColorPrimaries value (2 = unspecified) */
int color_trc; /* raw AVColorTransferCharacteristic value */
int sample_rate;
int sample_format; /* FBSampleFormat */
@@ -536,6 +559,13 @@ typedef struct FBEncoderConfig {
int64_t video_buffer_size;
int video_threads;
int video_color_srgb; /* non-zero: tag nclc as sRGB, else Rec.709 */
/* Explicit nclc tags; when video_color_primaries is
* fb_color_primaries_unspec, tags are inferred from video_color_srgb
* instead. Values match the corresponding
* AVColorPrimaries/AVColorTransferCharacteristic/AVColorSpace constants. */
int video_color_primaries; /* FBColorPrimaries */
int video_color_trc; /* FBColorTrc */
int video_colorspace; /* FBColorSpaceTag */
const char **video_opt_keys;
const char **video_opt_values;
int video_opt_count;
+2
View File
@@ -435,6 +435,8 @@ int fb_decoder_get_stream_info(const FBDecoder *decoder, FBStreamInfo *out)
out->field_order = decoder->codec_ctx ? decoder->codec_ctx->field_order :
AV_FIELD_UNKNOWN;
out->color_range = par->color_range;
out->color_primaries = par->color_primaries;
out->color_trc = par->color_trc;
out->sample_rate = par->sample_rate;
out->sample_format = par->format;
out->channel_layout_mask = fb::validate_stream_channel_layout_mask(s);
+14 -1
View File
@@ -153,6 +153,9 @@ struct FBEncoder {
int64_t video_buffer_size = 0;
int video_threads = 0;
int video_color_srgb = 0;
int video_color_primaries = fb_color_primaries_unspec;
int video_color_trc = fb_color_trc_unspec;
int video_colorspace = fb_col_spc_unspec;
std::vector<std::pair<std::string, std::string>> video_opts;
int audio_enabled = 0;
@@ -251,6 +254,9 @@ FBEncoder *fb_encoder_create(const FBEncoderConfig *config)
e->video_buffer_size = config->video_buffer_size;
e->video_threads = config->video_threads;
e->video_color_srgb = config->video_color_srgb;
e->video_color_primaries = config->video_color_primaries;
e->video_color_trc = config->video_color_trc;
e->video_colorspace = config->video_colorspace;
for (int i = 0; i < config->video_opt_count; i++) {
if (config->video_opt_keys[i] && config->video_opt_values[i]) {
e->video_opts.emplace_back(config->video_opt_keys[i],
@@ -868,7 +874,14 @@ bool FBEncoder::initialize_stream(AVMediaType type, AVStream **stream_ptr,
}
// nclc tags. See https://ffmpeg.org/doxygen/4.0/pixfmt_8h.html#ad384ee5a840bafd73daef08e6d9cafe7
if (video_color_srgb) {
if (video_color_primaries != fb_color_primaries_unspec) {
// Explicit tags supplied (e.g. derived from the export colorspace)
codec_ctx->color_primaries =
static_cast<AVColorPrimaries>(video_color_primaries);
codec_ctx->color_trc =
static_cast<AVColorTransferCharacteristic>(video_color_trc);
codec_ctx->colorspace = static_cast<AVColorSpace>(video_colorspace);
} else if (video_color_srgb) {
codec_ctx->color_primaries = AVCOL_PRI_BT709;
codec_ctx->color_trc = AVCOL_TRC_IEC61966_2_1;
codec_ctx->colorspace = AVCOL_SPC_BT709;
+15
View File
@@ -84,6 +84,21 @@ static_assert(fb_col_spc_smpt_e170_m == AVCOL_SPC_SMPTE170M, "colspace mismatch"
static_assert(fb_col_spc_smpt_e240_m == AVCOL_SPC_SMPTE240M, "colspace mismatch");
static_assert(fb_col_spc_b_t2020_ncl == AVCOL_SPC_BT2020_NCL, "colspace mismatch");
static_assert(fb_color_primaries_unspec == AVCOL_PRI_UNSPECIFIED, "primaries mismatch");
static_assert(fb_color_primaries_bt709 == AVCOL_PRI_BT709, "primaries mismatch");
static_assert(fb_color_primaries_bt470bg == AVCOL_PRI_BT470BG, "primaries mismatch");
static_assert(fb_color_primaries_smpte170m == AVCOL_PRI_SMPTE170M, "primaries mismatch");
static_assert(fb_color_primaries_bt2020 == AVCOL_PRI_BT2020, "primaries mismatch");
static_assert(fb_color_primaries_smpte432 == AVCOL_PRI_SMPTE432, "primaries mismatch");
static_assert(fb_color_trc_unspec == AVCOL_TRC_UNSPECIFIED, "trc mismatch");
static_assert(fb_color_trc_bt709 == AVCOL_TRC_BT709, "trc mismatch");
static_assert(fb_color_trc_gamma28 == AVCOL_TRC_GAMMA28, "trc mismatch");
static_assert(fb_color_trc_smpte170m == AVCOL_TRC_SMPTE170M, "trc mismatch");
static_assert(fb_color_trc_srgb == AVCOL_TRC_IEC61966_2_1, "trc mismatch");
static_assert(fb_color_trc_pq == AVCOL_TRC_SMPTE2084, "trc mismatch");
static_assert(fb_color_trc_hlg == AVCOL_TRC_ARIB_STD_B67, "trc mismatch");
static_assert(fb_media_type_video == AVMEDIA_TYPE_VIDEO, "mediatype mismatch");
static_assert(fb_media_type_audio == AVMEDIA_TYPE_AUDIO, "mediatype mismatch");
static_assert(fb_media_type_data == AVMEDIA_TYPE_DATA, "mediatype mismatch");
+2
View File
@@ -48,6 +48,8 @@ void fill_stream_info(const AVStream *s, int has_decoder, FBStreamInfo *out)
out->pixel_format = fb::pix_fmt_from_av(AVPixelFormat(par->format));
out->field_order = fb_field_order_unknown;
out->color_range = par->color_range;
out->color_primaries = par->color_primaries;
out->color_trc = par->color_trc;
out->sample_rate = par->sample_rate;
out->sample_format = par->format;
out->channel_layout_mask = fb::validate_stream_channel_layout_mask(s);