diff --git a/configure b/configure
index 96d6274d8a..da72148279 100755
--- a/configure
+++ b/configure
@@ -2855,6 +2855,7 @@ h264_metadata_bsf_select="cbs_h264"
 h264_redundant_pps_bsf_select="cbs_h264"
 hevc_metadata_bsf_select="cbs_h265"
 mjpeg2jpeg_bsf_select="jpegtables"
+mpeg2_metadata_bsf_select="cbs_mpeg2"
 trace_headers_bsf_select="cbs_h264 cbs_h265 cbs_mpeg2"
 
 # external libraries
diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index 71c307f2e2..8a8756bdcc 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -324,6 +324,42 @@ See also the @ref{text2movsub} filter.
 
 Decompress non-standard compressed MP3 audio headers.
 
+@section mpeg2_metadata
+
+Modify metadata embedded in an MPEG-2 stream.
+
+@table @option
+@item display_aspect_ratio
+Set the display aspect ratio in the stream.
+
+The following fixed values are supported:
+@table @option
+@item 4/3
+@item 16/9
+@item 221/100
+@end table
+Any other value will result in square pixels being signalled instead
+(see H.262 section 6.3.3 and table 6-3).
+
+@item frame_rate
+Set the frame rate in the stream.  This is constructed from a table
+of known values combined with a small multiplier and divisor - if
+the supplied value is not exactly representable, the nearest
+representable value will be used instead (see H.262 section 6.3.3
+and table 6-4).
+
+@item video_format
+Set the video format in the stream (see H.262 section 6.3.6 and
+table 6-6).
+
+@item colour_primaries
+@item transfer_characteristics
+@item matrix_coefficients
+Set the colour description in the stream (see H.262 section 6.3.6
+and tables 6-7, 6-8 and 6-9).
+
+@end table
+
 @section mpeg4_unpack_bframes
 
 Unpack DivX-style packed B-frames.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 76ba2acafb..948c7ec21f 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1006,6 +1006,7 @@ OBJS-$(CONFIG_MPEG4_UNPACK_BFRAMES_BSF)   += mpeg4_unpack_bframes_bsf.o
 OBJS-$(CONFIG_MOV2TEXTSUB_BSF)            += movsub_bsf.o
 OBJS-$(CONFIG_MP3_HEADER_DECOMPRESS_BSF)  += mp3_header_decompress_bsf.o \
                                              mpegaudiodata.o
+OBJS-$(CONFIG_MPEG2_METADATA_BSF)         += mpeg2_metadata_bsf.o
 OBJS-$(CONFIG_NOISE_BSF)                  += noise_bsf.o
 OBJS-$(CONFIG_NULL_BSF)                   += null_bsf.o
 OBJS-$(CONFIG_REMOVE_EXTRADATA_BSF)       += remove_extradata_bsf.o
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index 6e6b894e7f..7b0cb5032a 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -38,6 +38,7 @@ extern const AVBitStreamFilter ff_imx_dump_header_bsf;
 extern const AVBitStreamFilter ff_mjpeg2jpeg_bsf;
 extern const AVBitStreamFilter ff_mjpega_dump_header_bsf;
 extern const AVBitStreamFilter ff_mp3_header_decompress_bsf;
+extern const AVBitStreamFilter ff_mpeg2_metadata_bsf;
 extern const AVBitStreamFilter ff_mpeg4_unpack_bframes_bsf;
 extern const AVBitStreamFilter ff_mov2textsub_bsf;
 extern const AVBitStreamFilter ff_noise_bsf;
diff --git a/libavcodec/mpeg2_metadata_bsf.c b/libavcodec/mpeg2_metadata_bsf.c
new file mode 100644
index 0000000000..9da8fd128d
--- /dev/null
+++ b/libavcodec/mpeg2_metadata_bsf.c
@@ -0,0 +1,360 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/avstring.h"
+#include "libavutil/common.h"
+#include "libavutil/opt.h"
+
+#include "bsf.h"
+#include "cbs.h"
+#include "cbs_mpeg2.h"
+
+typedef struct MPEG2MetadataContext {
+    const AVClass *class;
+
+    CodedBitstreamContext cbc;
+    CodedBitstreamFragment fragment;
+
+    MPEG2RawExtensionData sequence_display_extension;
+
+    AVRational display_aspect_ratio;
+
+    AVRational frame_rate;
+
+    int video_format;
+    int colour_primaries;
+    int transfer_characteristics;
+    int matrix_coefficients;
+
+    int mpeg1_warned;
+} MPEG2MetadataContext;
+
+
+static int mpeg2_metadata_update_fragment(AVBSFContext *bsf,
+                                          CodedBitstreamFragment *frag)
+{
+    MPEG2MetadataContext             *ctx = bsf->priv_data;
+    MPEG2RawSequenceHeader            *sh = NULL;
+    MPEG2RawSequenceExtension         *se = NULL;
+    MPEG2RawSequenceDisplayExtension *sde = NULL;
+    int i, se_pos, add_sde = 0;
+
+    for (i = 0; i < frag->nb_units; i++) {
+        if (frag->units[i].type == MPEG2_START_SEQUENCE_HEADER) {
+            sh = frag->units[i].content;
+        } else if (frag->units[i].type == MPEG2_START_EXTENSION) {
+            MPEG2RawExtensionData *ext = frag->units[i].content;
+            if (ext->extension_start_code_identifier ==
+                MPEG2_EXTENSION_SEQUENCE) {
+                se = &ext->data.sequence;
+                se_pos = i;
+            } else if (ext->extension_start_code_identifier ==
+                MPEG2_EXTENSION_SEQUENCE_DISPLAY) {
+                sde = &ext->data.sequence_display;
+            }
+        }
+    }
+
+    if (!sh || !se) {
+        // No sequence header and sequence extension: not an MPEG-2 video
+        // sequence.
+        if (sh && !ctx->mpeg1_warned) {
+            av_log(bsf, AV_LOG_WARNING, "Stream contains a sequence "
+                   "header but not a sequence extension: maybe it's "
+                   "actually MPEG-1?\n");
+            ctx->mpeg1_warned = 1;
+        }
+        return 0;
+    }
+
+    if (ctx->display_aspect_ratio.num && ctx->display_aspect_ratio.den) {
+        int num, den;
+
+        av_reduce(&num, &den, ctx->display_aspect_ratio.num,
+                  ctx->display_aspect_ratio.den, 65535);
+
+        if (num == 4 && den == 3)
+            sh->aspect_ratio_information = 2;
+        else if (num == 16 && den == 9)
+            sh->aspect_ratio_information = 3;
+        else if (num == 221 && den == 100)
+            sh->aspect_ratio_information = 4;
+        else
+            sh->aspect_ratio_information = 1;
+    }
+
+    if (ctx->frame_rate.num && ctx->frame_rate.den) {
+        // Table 6-4.
+        static AVRational frame_rate_table[] = {
+            { 0, 0 },
+            { 24000, 1001 },
+            { 24,    1    },
+            { 25,    1    },
+            { 30000, 1001 },
+            { 30,    1    },
+            { 50,    1    },
+            { 60000, 1001 },
+            { 60,    1    },
+        };
+        int code, ext_n, ext_d;
+        AVRational best_error = { INT_MAX, 1 };
+
+        for (i = 1; i < FF_ARRAY_ELEMS(frame_rate_table); i++) {
+            if (av_cmp_q(ctx->frame_rate, frame_rate_table[i]) == 0) {
+                code  = i;
+                ext_n = 1;
+                ext_d = 1;
+                goto found_frame_rate;
+            }
+        }
+
+        for (i = 1; i < FF_ARRAY_ELEMS(frame_rate_table); i++) {
+            AVRational fr, error;
+            int n, d, cmp;
+            for (n = 1; n <= 4; n++) {
+                for (d = 1; d <= 32; d++) {
+                    fr = av_mul_q(frame_rate_table[i],
+                                  (AVRational) { n, d });
+                    cmp = av_cmp_q(fr, ctx->frame_rate);
+                    if (cmp == 0) {
+                        code  = i;
+                        ext_n = n;
+                        ext_d = d;
+                        goto found_frame_rate;
+                    }
+                    if (cmp < 0)
+                        error = av_div_q(ctx->frame_rate, fr);
+                    else
+                        error = av_div_q(fr, ctx->frame_rate);
+                    cmp = av_cmp_q(error, best_error);
+                    if (cmp < 0 || (cmp == 0 && n == 1 && d == 1)) {
+                        code  = i;
+                        ext_n = n;
+                        ext_d = d;
+                        best_error = error;
+                    }
+                }
+            }
+        }
+
+    found_frame_rate:
+        sh->frame_rate_code        = code;
+        se->frame_rate_extension_n = ext_n - 1;
+        se->frame_rate_extension_d = ext_d - 1;
+    }
+
+    if (ctx->video_format             >= 0 ||
+        ctx->colour_primaries         >= 0 ||
+        ctx->transfer_characteristics >= 0 ||
+        ctx->matrix_coefficients      >= 0) {
+        if (!sde) {
+            add_sde = 1;
+            ctx->sequence_display_extension.extension_start_code =
+                MPEG2_START_EXTENSION;
+            ctx->sequence_display_extension.extension_start_code_identifier =
+                MPEG2_EXTENSION_SEQUENCE_DISPLAY;
+            sde = &ctx->sequence_display_extension.data.sequence_display;
+
+            *sde = (MPEG2RawSequenceDisplayExtension) {
+                .video_format = 5,
+
+                .colour_description       = 0,
+                .colour_primaries         = 2,
+                .transfer_characteristics = 2,
+                .matrix_coefficients      = 2,
+
+                .display_horizontal_size =
+                    se->horizontal_size_extension << 12 | sh->horizontal_size_value,
+                .display_vertical_size =
+                    se->vertical_size_extension << 12 | sh->vertical_size_value,
+            };
+        }
+
+        if (ctx->video_format >= 0)
+            sde->video_format = ctx->video_format;
+
+        if (ctx->colour_primaries         >= 0 ||
+            ctx->transfer_characteristics >= 0 ||
+            ctx->matrix_coefficients      >= 0) {
+            sde->colour_description = 1;
+
+            if (ctx->colour_primaries >= 0)
+                sde->colour_primaries = ctx->colour_primaries;
+            else if (add_sde)
+                sde->colour_primaries = 2;
+
+            if (ctx->transfer_characteristics >= 0)
+                sde->transfer_characteristics = ctx->transfer_characteristics;
+            else if (add_sde)
+                sde->transfer_characteristics = 2;
+
+            if (ctx->matrix_coefficients >= 0)
+                sde->matrix_coefficients = ctx->matrix_coefficients;
+            else if (add_sde)
+                sde->matrix_coefficients = 2;
+        }
+    }
+
+    if (add_sde) {
+        int err;
+
+        err = ff_cbs_insert_unit_content(&ctx->cbc, frag, se_pos + 1,
+                                         MPEG2_START_EXTENSION,
+                                         &ctx->sequence_display_extension);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to insert new sequence "
+                   "display extension.\n");
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+static int mpeg2_metadata_filter(AVBSFContext *bsf, AVPacket *out)
+{
+    MPEG2MetadataContext *ctx = bsf->priv_data;
+    AVPacket *in = NULL;
+    CodedBitstreamFragment *frag = &ctx->fragment;
+    int err;
+
+    err = ff_bsf_get_packet(bsf, &in);
+    if (err < 0)
+        goto fail;
+
+    err = ff_cbs_read_packet(&ctx->cbc, frag, in);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
+        goto fail;
+    }
+
+    err = mpeg2_metadata_update_fragment(bsf, frag);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to update frame fragment.\n");
+        goto fail;
+    }
+
+    err = ff_cbs_write_packet(&ctx->cbc, out, frag);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
+        goto fail;
+    }
+
+    err = av_packet_copy_props(out, in);
+    if (err < 0) {
+        av_packet_unref(out);
+        goto fail;
+    }
+
+    err = 0;
+fail:
+    ff_cbs_fragment_uninit(&ctx->cbc, frag);
+
+    av_packet_free(&in);
+
+    return err;
+}
+
+static int mpeg2_metadata_init(AVBSFContext *bsf)
+{
+    MPEG2MetadataContext *ctx = bsf->priv_data;
+    CodedBitstreamFragment *frag = &ctx->fragment;
+    int err;
+
+    err = ff_cbs_init(&ctx->cbc, AV_CODEC_ID_MPEG2VIDEO, bsf);
+    if (err < 0)
+        return err;
+
+    if (bsf->par_in->extradata) {
+        err = ff_cbs_read_extradata(&ctx->cbc, frag, bsf->par_in);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to read extradata.\n");
+            goto fail;
+        }
+
+        err = mpeg2_metadata_update_fragment(bsf, frag);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to update metadata fragment.\n");
+            goto fail;
+        }
+
+        err = ff_cbs_write_extradata(&ctx->cbc, bsf->par_out, frag);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to write extradata.\n");
+            goto fail;
+        }
+    }
+
+    err = 0;
+fail:
+    ff_cbs_fragment_uninit(&ctx->cbc, frag);
+    return err;
+}
+
+static void mpeg2_metadata_close(AVBSFContext *bsf)
+{
+    MPEG2MetadataContext *ctx = bsf->priv_data;
+    ff_cbs_close(&ctx->cbc);
+}
+
+#define OFFSET(x) offsetof(MPEG2MetadataContext, x)
+static const AVOption mpeg2_metadata_options[] = {
+    { "display_aspect_ratio", "Set display aspect ratio (table 6-3)",
+        OFFSET(display_aspect_ratio), AV_OPT_TYPE_RATIONAL,
+        { .dbl = 0.0 }, 0, 65535 },
+
+    { "frame_rate", "Set frame rate",
+        OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL,
+        { .dbl = 0.0 }, 0, UINT_MAX },
+
+    { "video_format", "Set video format (table 6-6)",
+        OFFSET(video_format), AV_OPT_TYPE_INT,
+        { .i64 = -1 }, -1, 7 },
+    { "colour_primaries", "Set colour primaries (table 6-7)",
+        OFFSET(colour_primaries), AV_OPT_TYPE_INT,
+        { .i64 = -1 }, -1, 255 },
+    { "transfer_characteristics", "Set transfer characteristics (table 6-8)",
+        OFFSET(transfer_characteristics), AV_OPT_TYPE_INT,
+        { .i64 = -1 }, -1, 255 },
+    { "matrix_coefficients", "Set matrix coefficients (table 6-9)",
+        OFFSET(matrix_coefficients), AV_OPT_TYPE_INT,
+        { .i64 = -1 }, -1, 255 },
+
+    { NULL }
+};
+
+static const AVClass mpeg2_metadata_class = {
+    .class_name = "mpeg2_metadata_bsf",
+    .item_name  = av_default_item_name,
+    .option     = mpeg2_metadata_options,
+    .version    = LIBAVCODEC_VERSION_MAJOR,
+};
+
+static const enum AVCodecID mpeg2_metadata_codec_ids[] = {
+    AV_CODEC_ID_MPEG2VIDEO, AV_CODEC_ID_NONE,
+};
+
+const AVBitStreamFilter ff_mpeg2_metadata_bsf = {
+    .name           = "mpeg2_metadata",
+    .priv_data_size = sizeof(MPEG2MetadataContext),
+    .priv_class     = &mpeg2_metadata_class,
+    .init           = &mpeg2_metadata_init,
+    .close          = &mpeg2_metadata_close,
+    .filter         = &mpeg2_metadata_filter,
+    .codec_ids      = mpeg2_metadata_codec_ids,
+};
