diff --git a/libavcodec/cbs_h2645.c b/libavcodec/cbs_h2645.c
index 1677731db9..b9f219cdf7 100644
--- a/libavcodec/cbs_h2645.c
+++ b/libavcodec/cbs_h2645.c
@@ -1386,7 +1386,7 @@ static const CodedBitstreamUnitTypeDescriptor cbs_h264_unit_types[] = {
     CBS_UNIT_TYPE_END_OF_LIST
 };
 
-static void cbs_h265_free_sei_payload(H265RawSEIPayload *payload)
+void ff_cbs_h265_free_sei_payload(H265RawSEIPayload *payload)
 {
     switch (payload->payload_type) {
     case HEVC_SEI_TYPE_BUFFERING_PERIOD:
@@ -1420,7 +1420,7 @@ static void cbs_h265_free_sei(void *opaque, uint8_t *content)
     H265RawSEI *sei = (H265RawSEI*)content;
     int i;
     for (i = 0; i < sei->payload_count; i++)
-        cbs_h265_free_sei_payload(&sei->payload[i]);
+        ff_cbs_h265_free_sei_payload(&sei->payload[i]);
     av_freep(&content);
 }
 
diff --git a/libavcodec/cbs_h265.c b/libavcodec/cbs_h265.c
index f6226d8743..3d2c99af03 100644
--- a/libavcodec/cbs_h265.c
+++ b/libavcodec/cbs_h265.c
@@ -20,8 +20,145 @@
 #include "libavutil/mastering_display_metadata.h"
 
 #include "cbs_h265.h"
+#include "cbs_internal.h"
 
 
+int ff_cbs_h265_find_sei_message(CodedBitstreamFragment *au,
+                                 int type,
+                                 H265RawSEIPayload **message)
+{
+    H265RawSEI *sei;
+    int i, j, found_message;
+
+    found_message = !*message;
+    for (i = 0; i < au->nb_units; i++) {
+        if (au->units[i].type != HEVC_NAL_SEI_PREFIX &&
+            au->units[i].type != HEVC_NAL_SEI_SUFFIX)
+            continue;
+        sei = au->units[i].content;
+
+        for (j = 0; j < sei->payload_count; j++) {
+            if (sei->payload[j].payload_type == type) {
+                if (found_message) {
+                    *message = &sei->payload[j];
+                    return 0;
+                } else {
+                    if (&sei->payload[j] == *message)
+                        found_message = 1;
+                }
+            }
+        }
+    }
+
+    *message = NULL;
+    return AVERROR(ENOENT);
+}
+
+int ff_cbs_h265_add_sei_message(CodedBitstreamFragment *au,
+                                H265RawSEIPayload *payload, int prefix)
+{
+    H265RawSEI *sei = NULL;
+    int sei_type = prefix ? HEVC_NAL_SEI_PREFIX
+                          : HEVC_NAL_SEI_SUFFIX;
+    int err, i;
+
+    // Find an existing SEI NAL unit to add to.
+    for (i = 0; i < au->nb_units; i++) {
+        if (au->units[i].type == sei_type) {
+            sei = au->units[i].content;
+            if (sei->payload_count < H265_MAX_SEI_PAYLOADS)
+                break;
+
+            sei = NULL;
+        }
+    }
+
+    if (!sei) {
+        // Need to make a new SEI NAL unit.  Insert it before the VCL if
+        // prefix, or after if suffix.
+        CodedBitstreamUnit *unit;
+        int position = -1;
+
+        for (i = 0; i < au->nb_units; i++) {
+            if (au->units[i].type <= HEVC_NAL_RSV_VCL31) {
+                if (prefix) {
+                    position = i;
+                    break;
+                } else {
+                    position = i + 1;
+                }
+            }
+        }
+
+        err = ff_cbs_insert_unit(au, position);
+        if (err < 0)
+            goto fail;
+        unit = &au->units[position];
+        unit->type = sei_type;
+
+        // Needs a context for the type information but we don't have
+        // one, so forge one containing only the type information.
+        err = ff_cbs_alloc_unit_content2(&(CodedBitstreamContext) {
+                                       .codec = &ff_cbs_type_h265 }, unit);
+        if (err < 0) {
+            ff_cbs_delete_unit(au, i);
+            goto fail;
+        }
+        sei = unit->content;
+
+        *sei = (H265RawSEI) {
+            .nal_unit_header = {
+                .nal_unit_type = sei_type,
+            },
+        };
+    }
+
+    memcpy(&sei->payload[sei->payload_count], payload, sizeof(*payload));
+    ++sei->payload_count;
+
+    return 0;
+fail:
+    ff_cbs_h265_free_sei_payload(payload);
+    return err;
+}
+
+void ff_cbs_h265_delete_sei_message(CodedBitstreamFragment *au,
+                                    H265RawSEIPayload *message)
+{
+    H265RawSEI *sei;
+    int i, j, position;
+
+    position = -1;
+    for (i = 0; i < au->nb_units; i++) {
+        if (au->units[i].type != HEVC_NAL_SEI_PREFIX &&
+            au->units[i].type != HEVC_NAL_SEI_SUFFIX)
+            continue;
+        sei = au->units[i].content;
+        for (j = 0; j < sei->payload_count; j++) {
+            if (&sei->payload[j] == message) {
+                position = j;
+                break;
+            }
+        }
+        if (position >= 0)
+            break;
+    }
+    // Message not found in this access unit.
+    av_assert0(position >= 0);
+
+    if (position == 0 && sei->payload_count == 1) {
+        // Deleting NAL unit entirely.
+        ff_cbs_delete_unit(au, i);
+    } else {
+        ff_cbs_h265_free_sei_payload(&sei->payload[position]);
+
+        --sei->payload_count;
+        memmove(sei->payload + position,
+                sei->payload + position + 1,
+                (sei->payload_count - position) * sizeof(*sei->payload));
+    }
+}
+
 static uint32_t rescale_clip(AVRational value, uint32_t scale,
                              uint32_t min, uint32_t max)
 {
@@ -93,3 +230,56 @@ void ff_cbs_h265_fill_sei_content_light_level(H265RawSEIContentLightLevelInfo *c
     cll->max_content_light_level     = av_clip_uintp2(clm->MaxCLL,  16);
     cll->max_pic_average_light_level = av_clip_uintp2(clm->MaxFALL, 16);
 }
+
+
+void ff_cbs_h265_extract_sei_mastering_display(AVMasteringDisplayMetadata *mdm,
+                                               const H265RawSEIMasteringDisplayColourVolume *mdcv)
+{
+#define IN_RANGE(v, min, max) ((v) >= (min) && (v) <= (max))
+#define IS_VALID_COORD(x, y) (IN_RANGE(x, 5, 37000) && IN_RANGE(y, 5, 42000))
+    int valid_chromaticity = 1;
+    for (int a = 0; a < 3; a++) {
+        if (!IS_VALID_COORD(mdcv->display_primaries_x[a],
+                            mdcv->display_primaries_y[a]))
+            valid_chromaticity = 0;
+    }
+    if (!IS_VALID_COORD(mdcv->white_point_x, mdcv->white_point_y))
+        valid_chromaticity = 0;
+
+    memset(mdm, 0, sizeof(*mdm));
+
+    if (valid_chromaticity) {
+        for (int a = 0; a < 3; a++) {
+            // SEI message in GBR order, but metadata structure in RGB order.
+            static const uint8_t mapping[] = { 2, 0, 1 };
+            int b = mapping[a];
+
+            mdm->display_primaries[a][0] =
+                av_make_q(mdcv->display_primaries_x[b], 50000);
+            mdm->display_primaries[a][1] =
+                av_make_q(mdcv->display_primaries_y[b], 50000);
+        }
+
+        mdm->white_point[0] = av_make_q(mdcv->white_point_x, 50000);
+        mdm->white_point[1] = av_make_q(mdcv->white_point_y, 50000);
+
+        mdm->has_primaries = 1;
+    }
+
+    if (IN_RANGE(mdcv->min_display_mastering_luminance, 1, 50000) &&
+        IN_RANGE(mdcv->max_display_mastering_luminance, 50000, 100000000)) {
+        mdm->min_luminance = av_make_q(mdcv->min_display_mastering_luminance, 10000);
+        mdm->max_luminance = av_make_q(mdcv->max_display_mastering_luminance, 10000);
+
+        mdm->has_luminance = 1;
+    }
+#undef IN_RANGE
+#undef IS_VALID_COORD
+}
+
+void ff_cbs_h265_extract_sei_content_light_level(AVContentLightMetadata *clm,
+                                                 const H265RawSEIContentLightLevelInfo *cll)
+{
+    clm->MaxCLL  = cll->max_content_light_level;
+    clm->MaxFALL = cll->max_pic_average_light_level;
+}
diff --git a/libavcodec/cbs_h265.h b/libavcodec/cbs_h265.h
index 999353f607..eb6d4b0702 100644
--- a/libavcodec/cbs_h265.h
+++ b/libavcodec/cbs_h265.h
@@ -22,6 +22,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "cbs.h"
 #include "cbs_h2645.h"
 #include "hevc.h"
 
@@ -747,6 +748,42 @@ typedef struct CodedBitstreamH265Context {
 } CodedBitstreamH265Context;
 
 
+/**
+ * Free an SEI payload structure.
+ */
+void ff_cbs_h265_free_sei_payload(H265RawSEIPayload *payload);
+
+/**
+ * Find an SEI message of the given type within an access unit.
+ *
+ * If message is already set, only looks after it - this can be used to
+ * iterate over all messages of a given type.
+ */
+int ff_cbs_h265_find_sei_message(CodedBitstreamFragment *au,
+                                 int type,
+                                 H265RawSEIPayload **message);
+
+/**
+ * Add an SEI message to an access unit.
+ *
+ * Adds at the end of an existing SEI NAL unit of the appropriate type
+ * (PREFIX_SEI if prefix is set, otherwise SUFFIX_SEI), otherwise creates
+ * a new NAL unit to contain the message.
+ */
+int ff_cbs_h265_add_sei_message(CodedBitstreamFragment *au,
+                                H265RawSEIPayload *payload,
+                                int prefix);
+
+/**
+ * Delete an SEI message from an access unit.
+ *
+ * Deletes the given message from the NAL Unit containing it.  If it is
+ * the last message in the NAL unit, also deletes it from the access unit.
+ */
+void ff_cbs_h265_delete_sei_message(CodedBitstreamFragment *au,
+                                    H265RawSEIPayload *message);
+
+
 struct AVMasteringDisplayMetadata;
 struct AVContentLightMetadata;
 
@@ -764,5 +801,18 @@ void ff_cbs_h265_fill_sei_mastering_display(H265RawSEIMasteringDisplayColourVolu
 void ff_cbs_h265_fill_sei_content_light_level(H265RawSEIContentLightLevelInfo *cll,
                                               const struct AVContentLightMetadata *clm);
 
+/**
+ * Fill an AVMasteringDisplayMetadata side-data structure with values
+ * derived from the SEI Mastering Display Colour Volume structure.
+ */
+void ff_cbs_h265_extract_sei_mastering_display(struct AVMasteringDisplayMetadata *mdm,
+                                               const H265RawSEIMasteringDisplayColourVolume *mdcv);
+
+/**
+ * Fill an AVContentLightMetadata structure with values derived from
+ * the SEI Content Light Level Information structure.
+ */
+void ff_cbs_h265_extract_sei_content_light_level(struct AVContentLightMetadata *clm,
+                                                 const H265RawSEIContentLightLevelInfo *cll);
 
 #endif /* AVCODEC_CBS_H265_H */
diff --git a/libavcodec/h265_metadata_bsf.c b/libavcodec/h265_metadata_bsf.c
index c3eadee92b..8e18e88a3d 100644
--- a/libavcodec/h265_metadata_bsf.c
+++ b/libavcodec/h265_metadata_bsf.c
@@ -17,6 +17,7 @@
  */
 
 #include "libavutil/common.h"
+#include "libavutil/mastering_display_metadata.h"
 #include "libavutil/opt.h"
 
 #include "bsf.h"
@@ -24,6 +25,7 @@
 #include "cbs_bsf.h"
 #include "cbs_h265.h"
 #include "hevc.h"
+#include "hevc_sei.h"
 #include "h265_profile_level.h"
 
 enum {
@@ -34,6 +36,8 @@ enum {
 typedef struct H265MetadataContext {
     CBSBSFContext common;
 
+    int done_first_au;
+
     H265RawAUD aud_nal;
 
     int aud;
@@ -60,6 +64,18 @@ typedef struct H265MetadataContext {
     int level;
     int level_guess;
     int level_warned;
+
+    int mdcv_sei;
+    int mdcv_multiple_warned;
+    AVMasteringDisplayMetadata mdcv_side_data;
+    H265RawSEIMasteringDisplayColourVolume mdcv_x265;
+    const char *mdcv_x265_str;
+
+    int cll_sei;
+    int cll_multiple_warned;
+    AVContentLightMetadata cll_side_data;
+    H265RawSEIContentLightLevelInfo cll_x265;
+    const char *cll_x265_str;
 } H265MetadataContext;
 
 
@@ -328,11 +344,157 @@ static int h265_metadata_update_sps(AVBSFContext *bsf,
     return 0;
 }
 
+static int h265_metadata_update_mdcv(AVBSFContext *bsf, AVPacket *pkt,
+                                     CodedBitstreamFragment *au,
+                                     int seek_point)
+{
+    H265MetadataContext   *ctx = bsf->priv_data;
+    H265RawSEIPayload *message = NULL;
+    AVMasteringDisplayMetadata *mdm;
+    uint8_t *data;
+    int err, first;
+
+    first = 1;
+    while (1) {
+        err = ff_cbs_h265_find_sei_message(au,
+            HEVC_SEI_TYPE_MASTERING_DISPLAY_INFO, &message);
+        if (err < 0)
+            break;
+
+        if (ctx->mdcv_sei == BSF_ELEMENT_REMOVE ||
+            ctx->mdcv_sei == BSF_ELEMENT_INSERT) {
+            ff_cbs_h265_delete_sei_message(au, message);
+
+        } else if (ctx->mdcv_sei == BSF_ELEMENT_EXTRACT && pkt) {
+            if (first) {
+                data = av_packet_new_side_data(pkt,
+                    AV_PKT_DATA_MASTERING_DISPLAY_METADATA, sizeof(*mdm));
+                if (!data)
+                    return AVERROR(ENOMEM);
+
+                mdm = (AVMasteringDisplayMetadata*)data;
+                ff_cbs_h265_extract_sei_mastering_display(mdm,
+                    &message->payload.mastering_display);
+            } else if (!ctx->mdcv_multiple_warned) {
+                av_log(bsf, AV_LOG_WARNING, "Multiple MDCV SEI "
+                       "messages found in access unit: "
+                       "all but the first will be ignored.\n");
+                ctx->mdcv_multiple_warned = 1;
+            }
+        }
+
+        first = 0;
+    }
+
+    if (seek_point && ctx->mdcv_sei == BSF_ELEMENT_INSERT) {
+        H265RawSEIPayload payload = {
+            .payload_type = HEVC_SEI_TYPE_MASTERING_DISPLAY_INFO,
+        };
+        H265RawSEIMasteringDisplayColourVolume *mdcv =
+            &payload.payload.mastering_display;
+        int size;
+
+        data = av_packet_get_side_data(pkt,
+            AV_PKT_DATA_MASTERING_DISPLAY_METADATA, &size);
+        if (data) {
+            av_assert0(size >= sizeof(*mdm));
+            mdm = (AVMasteringDisplayMetadata*)data;
+            ctx->mdcv_side_data = *mdm;
+        }
+
+        if (ctx->mdcv_x265_str) {
+            *mdcv = ctx->mdcv_x265;
+        } else {
+            ff_cbs_h265_fill_sei_mastering_display(mdcv,
+                                                   &ctx->mdcv_side_data);
+        }
+
+        err = ff_cbs_h265_add_sei_message(au, &payload, 1);
+        if (err < 0)
+            return err;
+    }
+
+    return 0;
+}
+
+static int h265_metadata_update_cll(AVBSFContext *bsf, AVPacket *pkt,
+                                    CodedBitstreamFragment *au,
+                                    int seek_point)
+{
+    H265MetadataContext   *ctx = bsf->priv_data;
+    H265RawSEIPayload *message = NULL;
+    AVContentLightMetadata *clm;
+    uint8_t *data;
+    int err, first;
+
+    first = 1;
+    while (1) {
+        err = ff_cbs_h265_find_sei_message(au,
+            HEVC_SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO, &message);
+        if (err < 0)
+            break;
+
+        if (ctx->cll_sei == BSF_ELEMENT_REMOVE ||
+            ctx->cll_sei == BSF_ELEMENT_INSERT) {
+            ff_cbs_h265_delete_sei_message(au, message);
+
+        } else if (ctx->cll_sei == BSF_ELEMENT_EXTRACT && pkt) {
+            if (first) {
+                data = av_packet_new_side_data(pkt,
+                    AV_PKT_DATA_CONTENT_LIGHT_LEVEL, sizeof(*clm));
+                if (!data)
+                    return AVERROR(ENOMEM);
+
+                clm = (AVContentLightMetadata*)data;
+                ff_cbs_h265_extract_sei_content_light_level(clm,
+                    &message->payload.content_light_level);
+            } else if (!ctx->cll_multiple_warned) {
+                av_log(bsf, AV_LOG_WARNING, "Multiple CLL SEI "
+                       "messages found in access unit: "
+                       "all but the first will be ignored.\n");
+                ctx->cll_multiple_warned = 1;
+            }
+        }
+
+        first = 0;
+    }
+
+    if (seek_point && ctx->cll_sei == BSF_ELEMENT_INSERT) {
+        H265RawSEIPayload payload = {
+            .payload_type = HEVC_SEI_TYPE_CONTENT_LIGHT_LEVEL_INFO,
+        };
+        H265RawSEIContentLightLevelInfo *cll =
+            &payload.payload.content_light_level;
+        int size;
+
+        data = av_packet_get_side_data(pkt,
+            AV_PKT_DATA_MASTERING_DISPLAY_METADATA, &size);
+        if (data) {
+            av_assert0(size >= sizeof(*clm));
+            clm = (AVContentLightMetadata*)data;
+            ctx->cll_side_data = *clm;
+        }
+
+        if (ctx->cll_x265_str) {
+            *cll = ctx->cll_x265;
+        } else {
+            ff_cbs_h265_fill_sei_content_light_level(cll,
+                                                     &ctx->cll_side_data);
+        }
+
+        err = ff_cbs_h265_add_sei_message(au, &payload, 1);
+        if (err < 0)
+            return err;
+    }
+
+    return 0;
+}
+
 static int h265_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
                                          CodedBitstreamFragment *au)
 {
     H265MetadataContext *ctx = bsf->priv_data;
-    int err, i;
+    int err, i, has_vps, has_sps, seek_point;
 
     // If an AUD is present, it must be the first NAL unit.
     if (au->units[0].type == HEVC_NAL_AUD) {
@@ -380,19 +542,48 @@ static int h265_metadata_update_fragment(AVBSFContext *bsf, AVPacket *pkt,
     if (ctx->level == LEVEL_AUTO && !ctx->level_guess)
         h265_metadata_guess_level(bsf, au);
 
+    has_vps = has_sps = 0;
     for (i = 0; i < au->nb_units; i++) {
         if (au->units[i].type == HEVC_NAL_VPS) {
+            has_vps = 1;
             err = h265_metadata_update_vps(bsf, au->units[i].content);
             if (err < 0)
                 return err;
         }
         if (au->units[i].type == HEVC_NAL_SPS) {
+            has_sps = 1;
             err = h265_metadata_update_sps(bsf, au->units[i].content);
             if (err < 0)
                 return err;
         }
     }
 
+    if (pkt) {
+        // The current packet should be treated as a seek point for
+        // metadata insertion if any of:
+        // - It is the first packet in the stream.
+        // - It contains a VPS or SPS, indicating that a sequence might
+        //   start here.
+        // - It is marked as containing a key frame.
+        seek_point = !ctx->done_first_au || has_vps || has_sps ||
+            (pkt->flags & AV_PKT_FLAG_KEY);
+    } else {
+        seek_point = 0;
+    }
+
+    if (ctx->mdcv_sei != BSF_ELEMENT_PASS) {
+        err = h265_metadata_update_mdcv(bsf, pkt, au, seek_point);
+        if (err < 0)
+            return err;
+    }
+    if (ctx->cll_sei != BSF_ELEMENT_PASS) {
+        err = h265_metadata_update_cll(bsf, pkt, au, seek_point);
+        if (err < 0)
+            return err;
+    }
+
+    if (pkt)
+        ctx->done_first_au = 1;
     return 0;
 }
 
@@ -405,6 +596,44 @@ static const CBSBSFType h265_metadata_type = {
 
 static int h265_metadata_init(AVBSFContext *bsf)
 {
+    H265MetadataContext *ctx = bsf->priv_data;
+    int ret;
+
+    if (ctx->mdcv_x265_str) {
+        ret = sscanf(ctx->mdcv_x265_str,
+                     "G(%"SCNu16",%"SCNu16")"
+                     "B(%"SCNu16",%"SCNu16")"
+                     "R(%"SCNu16",%"SCNu16")"
+                     "WP(%"SCNu16",%"SCNu16")"
+                     "L(%"SCNu32",%"SCNu32")",
+                     &ctx->mdcv_x265.display_primaries_x[0],
+                     &ctx->mdcv_x265.display_primaries_y[0],
+                     &ctx->mdcv_x265.display_primaries_x[1],
+                     &ctx->mdcv_x265.display_primaries_y[1],
+                     &ctx->mdcv_x265.display_primaries_x[2],
+                     &ctx->mdcv_x265.display_primaries_y[2],
+                     &ctx->mdcv_x265.white_point_x,
+                     &ctx->mdcv_x265.white_point_y,
+                     &ctx->mdcv_x265.max_display_mastering_luminance,
+                     &ctx->mdcv_x265.min_display_mastering_luminance);
+        if (ret != 10) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to parse "
+                   "x265_master_display string: check formatting.\n");
+            return AVERROR(EINVAL);
+        }
+    }
+
+    if (ctx->cll_x265_str) {
+        ret = sscanf(ctx->cll_x265_str, "%"SCNu16",%"SCNu16,
+                     &ctx->cll_x265.max_content_light_level,
+                     &ctx->cll_x265.max_pic_average_light_level);
+        if (ret != 2) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to parse "
+                   "x265_max_cll string: check formatting.\n");
+            return AVERROR(EINVAL);
+        }
+    }
+
     return ff_cbs_bsf_init(bsf, &h265_metadata_type);
 }
 
@@ -484,6 +713,23 @@ static const AVOption h265_metadata_options[] = {
     { LEVEL("8.5", 255) },
 #undef LEVEL
 
+    BSF_ELEMENT_OPTIONS_PIRE("mastering_display_colour_volume",
+                             "Mastering display colour volume SEI",
+                             mdcv_sei, "mdcv_sei"),
+    { "x265_master_display",
+        "Mastering display colour volume values in x265 format",
+        OFFSET(mdcv_x265_str), AV_OPT_TYPE_STRING,
+        { .str = NULL }, .flags = FLAGS },
+
+    BSF_ELEMENT_OPTIONS_PIRE("content_light_level_information",
+                             "Content light level information SEI",
+                             cll_sei, "cll_sei"),
+
+    { "x265_max_cll",
+        "Content light level values in x265 format",
+        OFFSET(cll_x265_str), AV_OPT_TYPE_STRING,
+        { .str = NULL }, .flags = FLAGS },
+
     { NULL }
 };
 
