diff --git a/doc/APIchanges b/doc/APIchanges
index 12383a28d3..ce3f90a674 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,14 @@ The last version increases of all libraries were on 2023-02-09
 
 API changes, most recent first:
 
+2023-11-xx - xxxxxxxxxx - lavf 58.14.100 - avfilter.h formats.h
+  Add AVFilterFormatsConfig.color_spaces, AVFilterFormatsConfig.color_ranges,
+  AVFilterLink.colorspace, AVFilterLink.color_range, ff_all_color_spaces,
+  ff_all_color_ranges, ff_set_common_color_spaces, ff_set_common_color_ranges,
+  ff_set_common_color_spaces_from_list, ff_set_common_color_ranges_from_list,
+  ff_set_common_all_color_spaces, ff_set_common_all_color_ranges,
+  ff_formats_check_color_spaces, ff_formats_check_color_ranges.
+
 2023-11-08 - xxxxxxxxxx - lavu 58.32.100 - channel_layout.h
   Add AV_CH_LAYOUT_7POINT2POINT3 and AV_CHANNEL_LAYOUT_7POINT2POINT3.
   Add AV_CH_LAYOUT_9POINT1POINT4_BACK and AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK.
diff --git a/libavfilter/avfilter.c b/libavfilter/avfilter.c
index ab7782862a..77bfec00c5 100644
--- a/libavfilter/avfilter.c
+++ b/libavfilter/avfilter.c
@@ -185,6 +185,7 @@ int avfilter_link(AVFilterContext *src, unsigned srcpad,
     link->type    = src->output_pads[srcpad].type;
     av_assert0(AV_PIX_FMT_NONE == -1 && AV_SAMPLE_FMT_NONE == -1);
     link->format  = -1;
+    link->colorspace = AVCOL_SPC_UNSPECIFIED;
     ff_framequeue_init(&link->fifo, &src->graph->internal->frame_queues);
 
     return 0;
@@ -286,6 +287,12 @@ int avfilter_insert_filter(AVFilterLink *link, AVFilterContext *filt,
     if (link->outcfg.formats)
         ff_formats_changeref(&link->outcfg.formats,
                              &filt->outputs[filt_dstpad_idx]->outcfg.formats);
+    if (link->outcfg.color_spaces)
+        ff_formats_changeref(&link->outcfg.color_spaces,
+                             &filt->outputs[filt_dstpad_idx]->outcfg.color_spaces);
+    if (link->outcfg.color_ranges)
+        ff_formats_changeref(&link->outcfg.color_ranges,
+                             &filt->outputs[filt_dstpad_idx]->outcfg.color_ranges);
     if (link->outcfg.samplerates)
         ff_formats_changeref(&link->outcfg.samplerates,
                              &filt->outputs[filt_dstpad_idx]->outcfg.samplerates);
@@ -730,6 +737,10 @@ static void free_link(AVFilterLink *link)
 
     ff_formats_unref(&link->incfg.formats);
     ff_formats_unref(&link->outcfg.formats);
+    ff_formats_unref(&link->incfg.color_spaces);
+    ff_formats_unref(&link->outcfg.color_spaces);
+    ff_formats_unref(&link->incfg.color_ranges);
+    ff_formats_unref(&link->outcfg.color_ranges);
     ff_formats_unref(&link->incfg.samplerates);
     ff_formats_unref(&link->outcfg.samplerates);
     ff_channel_layouts_unref(&link->incfg.channel_layouts);
@@ -987,9 +998,11 @@ int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
             strcmp(link->dst->filter->name, "idet") &&
             strcmp(link->dst->filter->name, "null") &&
             strcmp(link->dst->filter->name, "scale")) {
-            av_assert1(frame->format                 == link->format);
-            av_assert1(frame->width               == link->w);
-            av_assert1(frame->height               == link->h);
+            av_assert1(frame->format        == link->format);
+            av_assert1(frame->width         == link->w);
+            av_assert1(frame->height        == link->h);
+            av_assert1(frame->colorspace    == link->color_space);
+            av_assert1(frame->color_range   == link->color_range);
         }
 
         frame->sample_aspect_ratio = link->sample_aspect_ratio;
diff --git a/libavfilter/avfilter.h b/libavfilter/avfilter.h
index d69381aed4..246d000251 100644
--- a/libavfilter/avfilter.h
+++ b/libavfilter/avfilter.h
@@ -301,6 +301,14 @@ typedef struct AVFilter {
          * @ref AVFilterFormatsConfig.formats "incfg.formats"
          * on every output link to a list of pixel/sample formats that the filter
          * supports on that link.
+         * For video links, this filter may also set
+         * @ref AVFilterFormatsConfig.color_spaces "incfg.color_spaces"
+         *  /
+         * @ref AVFilterFormatsConfig.color_spaces "outcfg.color_spaces"
+         * and @ref AVFilterFormatsConfig.color_ranges "incfg.color_ranges"
+         *  /
+         * @ref AVFilterFormatsConfig.color_ranges "outcfg.color_ranges"
+         * analogously.
          * For audio links, this filter must also set
          * @ref AVFilterFormatsConfig.samplerates "incfg.samplerates"
          *  /
@@ -322,6 +330,10 @@ typedef struct AVFilter {
          * to indicate that this filter supports each of these pixel formats,
          * provided that all inputs and outputs use the same pixel format.
          *
+         * In addition to that the generic code will mark all inputs
+         * and all outputs as supporting all color spaces and ranges, as
+         * long as all inputs and outputs use the same color space/range.
+         *
          * This list must never be NULL if the union is in this state.
          * The type of all inputs and outputs of filters using this must
          * be AVMEDIA_TYPE_VIDEO.
@@ -514,6 +526,12 @@ typedef struct AVFilterFormatsConfig {
      */
     AVFilterChannelLayouts  *channel_layouts;
 
+    /**
+     * Lists of supported YUV color metadata, only for YUV video.
+     */
+    AVFilterFormats *color_spaces;  ///< AVColorSpace
+    AVFilterFormats *color_ranges;  ///< AVColorRange
+
 } AVFilterFormatsConfig;
 
 /**
@@ -565,6 +583,16 @@ struct AVFilterLink {
 
     AVChannelLayout ch_layout;  ///< channel layout of current buffer (see libavutil/channel_layout.h)
 
+    /**
+     * For non-YUV links, these are respectively set to fallback values (as
+     * appropriate for that colorspace).
+     *
+     * Note: This includes grayscale formats, as these are currently treated
+     * as forced full range always.
+     */
+    enum AVColorSpace colorspace;   ///< agreed upon YUV color space
+    enum AVColorRange color_range;  ///< agreed upon YUV color range
+
     /*****************************************************************
      * All fields below this line are not part of the public API. They
      * may not be used outside of libavfilter and can be changed and
diff --git a/libavfilter/avfiltergraph.c b/libavfilter/avfiltergraph.c
index 625cbc022e..e7fabe85ea 100644
--- a/libavfilter/avfiltergraph.c
+++ b/libavfilter/avfiltergraph.c
@@ -298,7 +298,9 @@ static int filter_link_check_formats(void *log, AVFilterLink *link, AVFilterForm
     switch (link->type) {
 
     case AVMEDIA_TYPE_VIDEO:
-        if ((ret = ff_formats_check_pixel_formats(log, cfg->formats)) < 0)
+        if ((ret = ff_formats_check_pixel_formats(log, cfg->formats)) < 0 ||
+            (ret = ff_formats_check_color_spaces(log, cfg->color_spaces)) < 0 ||
+            (ret = ff_formats_check_color_ranges(log, cfg->color_ranges)) < 0)
             return ret;
         break;
 
@@ -365,6 +367,10 @@ static int formats_declared(AVFilterContext *f)
     for (i = 0; i < f->nb_inputs; i++) {
         if (!f->inputs[i]->outcfg.formats)
             return 0;
+        if (f->inputs[i]->type == AVMEDIA_TYPE_VIDEO &&
+            !(f->inputs[i]->outcfg.color_ranges &&
+              f->inputs[i]->outcfg.color_spaces))
+            return 0;
         if (f->inputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->inputs[i]->outcfg.samplerates &&
               f->inputs[i]->outcfg.channel_layouts))
@@ -373,6 +379,10 @@ static int formats_declared(AVFilterContext *f)
     for (i = 0; i < f->nb_outputs; i++) {
         if (!f->outputs[i]->incfg.formats)
             return 0;
+        if (f->outputs[i]->type == AVMEDIA_TYPE_VIDEO &&
+            !(f->outputs[i]->incfg.color_ranges &&
+              f->outputs[i]->incfg.color_spaces))
+            return 0;
         if (f->outputs[i]->type == AVMEDIA_TYPE_AUDIO &&
             !(f->outputs[i]->incfg.samplerates &&
               f->outputs[i]->incfg.channel_layouts))
@@ -493,7 +503,16 @@ static int query_formats(AVFilterGraph *graph, void *log_ctx)
                 av_assert0( inlink->outcfg.formats->refcount > 0);
                 av_assert0(outlink->incfg.formats->refcount > 0);
                 av_assert0(outlink->outcfg.formats->refcount > 0);
-                if (outlink->type == AVMEDIA_TYPE_AUDIO) {
+                if (outlink->type == AVMEDIA_TYPE_VIDEO) {
+                    av_assert0( inlink-> incfg.color_spaces->refcount > 0);
+                    av_assert0( inlink->outcfg.color_spaces->refcount > 0);
+                    av_assert0(outlink-> incfg.color_spaces->refcount > 0);
+                    av_assert0(outlink->outcfg.color_spaces->refcount > 0);
+                    av_assert0( inlink-> incfg.color_ranges->refcount > 0);
+                    av_assert0( inlink->outcfg.color_ranges->refcount > 0);
+                    av_assert0(outlink-> incfg.color_ranges->refcount > 0);
+                    av_assert0(outlink->outcfg.color_ranges->refcount > 0);
+                } else if (outlink->type == AVMEDIA_TYPE_AUDIO) {
                     av_assert0( inlink-> incfg.samplerates->refcount > 0);
                     av_assert0( inlink->outcfg.samplerates->refcount > 0);
                     av_assert0(outlink-> incfg.samplerates->refcount > 0);
@@ -583,6 +602,29 @@ static enum AVSampleFormat find_best_sample_fmt_of_2(enum AVSampleFormat dst_fmt
     return score1 < score2 ? dst_fmt1 : dst_fmt2;
 }
 
+int ff_fmt_is_regular_yuv(enum AVPixelFormat fmt)
+{
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+    if (!desc)
+        return 0;
+    if (desc->nb_components < 3)
+        return 0; /* Grayscale is explicitly full-range in swscale */
+    if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL |
+                       AV_PIX_FMT_FLAG_XYZ | AV_PIX_FMT_FLAG_FLOAT))
+        return 0;
+
+    switch (fmt) {
+    case AV_PIX_FMT_YUVJ420P:
+    case AV_PIX_FMT_YUVJ422P:
+    case AV_PIX_FMT_YUVJ444P:
+    case AV_PIX_FMT_YUVJ440P:
+    case AV_PIX_FMT_YUVJ411P:
+        return 0;
+    default:
+        return 1;
+    }
+}
+
 static int pick_format(AVFilterLink *link, AVFilterLink *ref)
 {
     if (!link || !link->incfg.formats)
@@ -603,6 +645,7 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref)
                    av_get_pix_fmt_name(ref->format), has_alpha);
             link->incfg.formats->formats[0] = best;
         }
+
     } else if (link->type == AVMEDIA_TYPE_AUDIO) {
         if(ref && ref->type == AVMEDIA_TYPE_AUDIO){
             enum AVSampleFormat best= AV_SAMPLE_FMT_NONE;
@@ -621,7 +664,40 @@ static int pick_format(AVFilterLink *link, AVFilterLink *ref)
     link->incfg.formats->nb_formats = 1;
     link->format = link->incfg.formats->formats[0];
 
-    if (link->type == AVMEDIA_TYPE_AUDIO) {
+    if (link->type == AVMEDIA_TYPE_VIDEO) {
+        if (!ff_fmt_is_regular_yuv(link->format)) {
+            const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
+            /* These fields are explicitly documented as affecting YUV only,
+             * so set them to sane values for other formats. */
+            if (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
+                link->color_range = AVCOL_RANGE_UNSPECIFIED;
+            else
+                link->color_range = AVCOL_RANGE_JPEG;
+            if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_XYZ)) {
+                link->colorspace = AVCOL_SPC_RGB;
+            } else {
+                link->colorspace = AVCOL_SPC_UNSPECIFIED;
+            }
+        } else {
+            if (!link->incfg.color_spaces->nb_formats) {
+                av_log(link->src, AV_LOG_ERROR, "Cannot select color space for"
+                       " the link between filters %s and %s.\n", link->src->name,
+                       link->dst->name);
+                return AVERROR(EINVAL);
+            }
+            link->incfg.color_spaces->nb_formats = 1;
+            link->colorspace = link->incfg.color_spaces->formats[0];
+
+            if (!link->incfg.color_ranges->nb_formats) {
+                av_log(link->src, AV_LOG_ERROR, "Cannot select color range for"
+                       " the link between filters %s and %s.\n", link->src->name,
+                       link->dst->name);
+                return AVERROR(EINVAL);
+            }
+            link->incfg.color_ranges->nb_formats = 1;
+            link->color_range = link->incfg.color_ranges->formats[0];
+        }
+    } else if (link->type == AVMEDIA_TYPE_AUDIO) {
         int ret;
 
         if (!link->incfg.samplerates->nb_formats) {
@@ -661,6 +737,10 @@ FF_ENABLE_DEPRECATION_WARNINGS
     ff_formats_unref(&link->outcfg.samplerates);
     ff_channel_layouts_unref(&link->incfg.channel_layouts);
     ff_channel_layouts_unref(&link->outcfg.channel_layouts);
+    ff_formats_unref(&link->incfg.color_spaces);
+    ff_formats_unref(&link->outcfg.color_spaces);
+    ff_formats_unref(&link->incfg.color_ranges);
+    ff_formats_unref(&link->outcfg.color_ranges);
 
     return 0;
 }
@@ -822,6 +902,82 @@ static void swap_samplerates(AVFilterGraph *graph)
         swap_samplerates_on_filter(graph->filters[i]);
 }
 
+static void swap_color_spaces_on_filter(AVFilterContext *filter)
+{
+    AVFilterLink *link = NULL;
+    enum AVColorSpace csp;
+    int i;
+
+    for (i = 0; i < filter->nb_inputs; i++) {
+        link = filter->inputs[i];
+        if (link->type == AVMEDIA_TYPE_VIDEO &&
+            link->outcfg.color_spaces->nb_formats == 1)
+            break;
+    }
+    if (i == filter->nb_inputs)
+        return;
+
+    csp = link->outcfg.color_spaces->formats[0];
+
+    for (i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *outlink = filter->outputs[i];
+        if (outlink->type != AVMEDIA_TYPE_VIDEO)
+            continue;
+        /* there is no meaningful 'score' between different yuv matrices,
+         * so just prioritize an exact match if it exists */
+        for (int j = 0; j < outlink->incfg.color_spaces->nb_formats; j++) {
+            if (csp == outlink->incfg.color_spaces->formats[j]) {
+                FFSWAP(int, outlink->incfg.color_spaces->formats[0],
+                       outlink->incfg.color_spaces->formats[j]);
+                break;
+            }
+        }
+    }
+}
+
+static void swap_color_spaces(AVFilterGraph *graph)
+{
+    for (int i = 0; i < graph->nb_filters; i++)
+        swap_color_spaces_on_filter(graph->filters[i]);
+}
+
+static void swap_color_ranges_on_filter(AVFilterContext *filter)
+{
+    AVFilterLink *link = NULL;
+    enum AVColorRange range;
+    int i;
+
+    for (i = 0; i < filter->nb_inputs; i++) {
+        link = filter->inputs[i];
+        if (link->type == AVMEDIA_TYPE_VIDEO &&
+            link->outcfg.color_ranges->nb_formats == 1)
+            break;
+    }
+    if (i == filter->nb_inputs)
+        return;
+
+    range = link->outcfg.color_ranges->formats[0];
+
+    for (i = 0; i < filter->nb_outputs; i++) {
+        AVFilterLink *outlink = filter->outputs[i];
+        if (outlink->type != AVMEDIA_TYPE_VIDEO)
+            continue;
+        for (int j = 0; j < outlink->incfg.color_ranges->nb_formats; j++) {
+            if (range == outlink->incfg.color_ranges->formats[j]) {
+                FFSWAP(int, outlink->incfg.color_ranges->formats[0],
+                       outlink->incfg.color_ranges->formats[j]);
+                break;
+            }
+        }
+    }
+}
+
+static void swap_color_ranges(AVFilterGraph *graph)
+{
+    for (int i = 0; i < graph->nb_filters; i++)
+        swap_color_ranges_on_filter(graph->filters[i]);
+}
+
 #define CH_CENTER_PAIR (AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER)
 #define CH_FRONT_PAIR  (AV_CH_FRONT_LEFT           | AV_CH_FRONT_RIGHT)
 #define CH_STEREO_PAIR (AV_CH_STEREO_LEFT          | AV_CH_STEREO_RIGHT)
@@ -1098,6 +1254,10 @@ static int graph_config_formats(AVFilterGraph *graph, void *log_ctx)
     if ((ret = reduce_formats(graph)) < 0)
         return ret;
 
+    /* for video filters, ensure that the best colorspace metadata is selected */
+    swap_color_spaces(graph);
+    swap_color_ranges(graph);
+
     /* for audio filters, ensure the best format, sample rate and channel layout
      * is selected */
     swap_sample_fmts(graph);
diff --git a/libavfilter/formats.c b/libavfilter/formats.c
index d1c97daf64..20f00d2db4 100644
--- a/libavfilter/formats.c
+++ b/libavfilter/formats.c
@@ -321,12 +321,46 @@ static int merge_channel_layouts(void *a, void *b)
     return merge_channel_layouts_internal(a, b, 0);
 }
 
+static int merge_generic_internal(AVFilterFormats *a,
+                                  AVFilterFormats *b, int check)
+{
+    av_assert2(check || (a->refcount && b->refcount));
+
+    if (a == b)
+        return 1;
+
+    MERGE_FORMATS(a, b, formats, nb_formats, AVFilterFormats, check, 0);
+
+    return 1;
+}
+
+static int can_merge_generic(const void *a, const void *b)
+{
+    return merge_generic_internal((AVFilterFormats *)a,
+                                  (AVFilterFormats *)b, 1);
+}
+
+static int merge_generic(void *a, void *b)
+{
+    return merge_generic_internal(a, b, 0);
+}
+
 static const AVFilterFormatsMerger mergers_video[] = {
     {
         .offset     = offsetof(AVFilterFormatsConfig, formats),
         .merge      = merge_pix_fmts,
         .can_merge  = can_merge_pix_fmts,
     },
+    {
+        .offset     = offsetof(AVFilterFormatsConfig, color_spaces),
+        .merge      = merge_generic,
+        .can_merge  = can_merge_generic,
+    },
+    {
+        .offset     = offsetof(AVFilterFormatsConfig, color_ranges),
+        .merge      = merge_generic,
+        .can_merge  = can_merge_generic,
+    },
 };
 
 static const AVFilterFormatsMerger mergers_audio[] = {
@@ -594,6 +628,33 @@ AVFilterChannelLayouts *ff_all_channel_counts(void)
     return ret;
 }
 
+AVFilterFormats *ff_all_color_spaces(void)
+{
+    AVFilterFormats *ret = NULL;
+    if (ff_add_format(&ret, AVCOL_SPC_UNSPECIFIED) < 0)
+        return NULL;
+    for (int csp = 0; csp < AVCOL_SPC_NB; csp++) {
+        if (csp == AVCOL_SPC_RESERVED ||
+            csp == AVCOL_SPC_UNSPECIFIED)
+            continue;
+        if (ff_add_format(&ret, csp) < 0)
+            return NULL;
+    }
+
+    return ret;
+}
+
+AVFilterFormats *ff_all_color_ranges(void)
+{
+    AVFilterFormats *ret = NULL;
+    for (int range = 0; range < AVCOL_RANGE_NB; range++) {
+        if (ff_add_format(&ret, range) < 0)
+            return NULL;
+    }
+
+    return ret;
+}
+
 #define FORMATS_REF(f, ref, unref_fn)                                           \
     void *tmp;                                                                  \
                                                                                 \
@@ -763,6 +824,42 @@ int ff_set_common_all_samplerates(AVFilterContext *ctx)
     return ff_set_common_samplerates(ctx, ff_all_samplerates());
 }
 
+int ff_set_common_color_spaces(AVFilterContext *ctx,
+                               AVFilterFormats *color_spaces)
+{
+    SET_COMMON_FORMATS(ctx, color_spaces, AVMEDIA_TYPE_VIDEO,
+                       ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_color_spaces_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges)
+{
+    return ff_set_common_color_spaces(ctx, ff_make_format_list(color_ranges));
+}
+
+int ff_set_common_all_color_spaces(AVFilterContext *ctx)
+{
+    return ff_set_common_color_spaces(ctx, ff_all_color_spaces());
+}
+
+int ff_set_common_color_ranges(AVFilterContext *ctx,
+                               AVFilterFormats *color_ranges)
+{
+    SET_COMMON_FORMATS(ctx, color_ranges, AVMEDIA_TYPE_VIDEO,
+                       ff_formats_ref, ff_formats_unref);
+}
+
+int ff_set_common_color_ranges_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges)
+{
+    return ff_set_common_color_ranges(ctx, ff_make_format_list(color_ranges));
+}
+
+int ff_set_common_all_color_ranges(AVFilterContext *ctx)
+{
+    return ff_set_common_color_ranges(ctx, ff_all_color_ranges());
+}
+
 /**
  * A helper for query_formats() which sets all links to the same list of
  * formats. If there are no links hooked to this filter, the list of formats is
@@ -817,7 +914,14 @@ int ff_default_query_formats(AVFilterContext *ctx)
     ret = ff_set_common_formats(ctx, formats);
     if (ret < 0)
         return ret;
-    if (type == AVMEDIA_TYPE_AUDIO) {
+    if (type == AVMEDIA_TYPE_VIDEO) {
+        ret = ff_set_common_all_color_spaces(ctx);
+        if (ret < 0)
+            return ret;
+        ret = ff_set_common_all_color_ranges(ctx);
+        if (ret < 0)
+            return ret;
+    } else if (type == AVMEDIA_TYPE_AUDIO) {
         ret = ff_set_common_all_channel_counts(ctx);
         if (ret < 0)
             return ret;
@@ -935,6 +1039,22 @@ int ff_formats_check_sample_rates(void *log, const AVFilterFormats *fmts)
     return check_list(log, "sample rate", fmts);
 }
 
+int ff_formats_check_color_spaces(void *log, const AVFilterFormats *fmts)
+{
+    for (int i = 0; fmts && i < fmts->nb_formats; i++) {
+        if (fmts->formats[i] == AVCOL_SPC_RESERVED) {
+            av_log(log, AV_LOG_ERROR, "Invalid color range\n");
+            return AVERROR(EINVAL);
+        }
+    }
+    return check_list(log, "color space", fmts);
+}
+
+int ff_formats_check_color_ranges(void *log, const AVFilterFormats *fmts)
+{
+    return check_list(log, "color range", fmts);
+}
+
 static int layouts_compatible(const AVChannelLayout *a, const AVChannelLayout *b)
 {
     return !av_channel_layout_compare(a, b) ||
diff --git a/libavfilter/formats.h b/libavfilter/formats.h
index d44890109e..82b3af4be1 100644
--- a/libavfilter/formats.h
+++ b/libavfilter/formats.h
@@ -130,6 +130,20 @@ AVFilterChannelLayouts *ff_all_channel_counts(void);
 av_warn_unused_result
 AVFilterChannelLayouts *ff_make_channel_layout_list(const AVChannelLayout *fmts);
 
+/**
+ * Construct an AVFilterFormats representing all possible color spaces.
+ *
+ * Note: This list does not include AVCOL_SPC_RESERVED.
+ */
+av_warn_unused_result
+AVFilterFormats *ff_all_color_spaces(void);
+
+/**
+ * Construct an AVFilterFormats representing all possible color ranges.
+ */
+av_warn_unused_result
+AVFilterFormats *ff_all_color_ranges(void);
+
 /**
  * Helpers for query_formats() which set all free audio links to the same list
  * of channel layouts/sample rates. If there are no links hooked to this list,
@@ -165,6 +179,38 @@ int ff_set_common_samplerates_from_list(AVFilterContext *ctx,
 av_warn_unused_result
 int ff_set_common_all_samplerates(AVFilterContext *ctx);
 
+av_warn_unused_result
+int ff_set_common_color_spaces(AVFilterContext *ctx,
+                               AVFilterFormats *color_spaces);
+/**
+ * Equivalent to ff_set_common_color_spaces(ctx, ff_make_format_list(color_spaces))
+ */
+av_warn_unused_result
+int ff_set_common_color_spaces_from_list(AVFilterContext *ctx,
+                                         const int *color_spaces);
+
+/**
+ * Equivalent to ff_set_common_color_spaces(ctx, ff_all_color_spaces())
+ */
+av_warn_unused_result
+int ff_set_common_all_color_spaces(AVFilterContext *ctx);
+
+av_warn_unused_result
+int ff_set_common_color_ranges(AVFilterContext *ctx,
+                               AVFilterFormats *color_ranges);
+/**
+ * Equivalent to ff_set_common_color_ranges(ctx, ff_make_format_list(color_ranges))
+ */
+av_warn_unused_result
+int ff_set_common_color_ranges_from_list(AVFilterContext *ctx,
+                                         const int *color_ranges);
+
+/**
+ * Equivalent to ff_set_common_color_ranges(ctx, ff_all_color_ranges())
+ */
+av_warn_unused_result
+int ff_set_common_all_color_ranges(AVFilterContext *ctx);
+
 /**
  * A helper for query_formats() which sets all links to the same list of
  * formats. If there are no links hooked to this filter, the list of formats is
@@ -328,6 +374,14 @@ int ff_formats_check_sample_rates(void *log, const AVFilterFormats *fmts);
  */
 int ff_formats_check_channel_layouts(void *log, const AVFilterChannelLayouts *fmts);
 
+/**
+ * Check that fmts is a valid formats list for YUV colorspace metadata.
+ *
+ * In particular, check for duplicates.
+ */
+int ff_formats_check_color_spaces(void *log, const AVFilterFormats *fmts);
+int ff_formats_check_color_ranges(void *log, const AVFilterFormats *fmts);
+
 typedef struct AVFilterFormatMerger {
     unsigned offset;
     int (*merge)(void *a, void *b);
diff --git a/libavfilter/internal.h b/libavfilter/internal.h
index 2dbc5def0a..a6cdf9994c 100644
--- a/libavfilter/internal.h
+++ b/libavfilter/internal.h
@@ -203,6 +203,12 @@ enum FilterFormatsState {
  */
 int ff_fmt_is_in(int fmt, const int *fmts);
 
+/**
+ * Returns true if a pixel format is "regular YUV", which includes all pixel
+ * formats that are affected by YUV colorspace negotiation.
+ */
+int ff_fmt_is_regular_yuv(enum AVPixelFormat fmt);
+
 /* Functions to parse audio format arguments */
 
 /**
diff --git a/libavfilter/vaapi_vpp.c b/libavfilter/vaapi_vpp.c
index cf2592e068..59961bfa4a 100644
--- a/libavfilter/vaapi_vpp.c
+++ b/libavfilter/vaapi_vpp.c
@@ -38,6 +38,10 @@ int ff_vaapi_vpp_query_formats(AVFilterContext *avctx)
                               &avctx->outputs[0]->incfg.formats)) < 0)
         return err;
 
+    if ((err = ff_set_common_all_color_spaces(avctx)) < 0 ||
+        (err = ff_set_common_all_color_ranges(avctx)) < 0)
+        return err;
+
     return 0;
 }
 
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 537df129cd..7642b670d1 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -31,7 +31,7 @@
 
 #include "version_major.h"
 
-#define LIBAVFILTER_VERSION_MINOR  13
+#define LIBAVFILTER_VERSION_MINOR  14
 #define LIBAVFILTER_VERSION_MICRO 100
 
 
diff --git a/libavfilter/video.c b/libavfilter/video.c
index 42eeb98c28..243762c8fd 100644
--- a/libavfilter/video.c
+++ b/libavfilter/video.c
@@ -96,6 +96,8 @@ AVFrame *ff_default_get_video_buffer2(AVFilterLink *link, int w, int h, int alig
         return NULL;
 
     frame->sample_aspect_ratio = link->sample_aspect_ratio;
+    frame->colorspace  = link->colorspace;
+    frame->color_range = link->color_range;
 
     return frame;
 }
diff --git a/tests/ref/fate/shortest-sub b/tests/ref/fate/shortest-sub
index 9caee587ce..16b1324e83 100644
--- a/tests/ref/fate/shortest-sub
+++ b/tests/ref/fate/shortest-sub
@@ -1,5 +1,5 @@
-73d142a80965f9e0884a5863abde0dab *tests/data/fate/shortest-sub.matroska
-139249 tests/data/fate/shortest-sub.matroska
+d6608277c93097383e62388196dc62f0 *tests/data/fate/shortest-sub.matroska
+139260 tests/data/fate/shortest-sub.matroska
 #extradata 1:      167, 0xf7272d5f
 #tb 0: 1/1000
 #media_type 0: video
