diff mbox series

[FFmpeg-devel,v2,2/2] avformat/flvenc: add option to read metadata from file

Message ID 20230202160708.29461-2-ffmpeg@gyani.pro
State New
Headers show
Series [FFmpeg-devel,v2,1/2] avformat/flvenc: add option meta_period | expand

Checks

Context Check Description
yinshiyou/make_fate_loongarch64 success Make fate finished
yinshiyou/make_loongarch64 warning New warnings during build
andriy/make_fate_x86 success Make fate finished
andriy/make_x86 warning New warnings during build

Commit Message

Gyan Doshi Feb. 2, 2023, 4:07 p.m. UTC
Useful, in conjuntion with option meta_period, to vary metadata during
stream.

File format is ffmetadata.
---
 configure            |   2 +-
 doc/muxers.texi      |   3 +
 libavformat/flvenc.c | 133 +++++++++++++++++++++++++++++++++----------
 3 files changed, 107 insertions(+), 31 deletions(-)

Comments

Andreas Rheinhardt Feb. 3, 2023, 3:34 p.m. UTC | #1
Gyan Doshi:
> Useful, in conjuntion with option meta_period, to vary metadata during
> stream.
> 
> File format is ffmetadata.
> ---
>  configure            |   2 +-
>  doc/muxers.texi      |   3 +
>  libavformat/flvenc.c | 133 +++++++++++++++++++++++++++++++++----------
>  3 files changed, 107 insertions(+), 31 deletions(-)
> 
> diff --git a/configure b/configure
> index 9d78a244a3..de371632c4 100755
> --- a/configure
> +++ b/configure
> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>  f4v_muxer_select="mov_muxer"
>  fifo_muxer_deps="threads"
>  flac_demuxer_select="flac_parser"
> -flv_muxer_select="aac_adtstoasc_bsf"
> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>  gxf_muxer_select="pcm_rechunk_bsf"
>  hds_muxer_select="flv_muxer"
>  hls_demuxer_select="adts_header ac3_parser"
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index 02ecddf186..000c92b2a7 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -555,6 +555,9 @@ With every video packet.
>  @end table
>  Note that metadata will always be re-emitted if a metadata update event is signalled.
>  
> +@item meta_filename
> +Specify a ffmetadata file from which to load metadata.
> +
>  @end table
>  
>  @anchor{framecrc}
> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
> index d1c7a493d1..1332b18b41 100644
> --- a/libavformat/flvenc.c
> +++ b/libavformat/flvenc.c
> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>      double framerate;
>      AVCodecParameters *data_par;
>  
> +    char *meta_filename;
> +    AVFormatContext *meta_ctx;
> +    AVDictionary *meta_dict;
> +
>      int flags;
>      int meta_period;
>  } FLVContext;
> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>      avio_w8(pb, !!b);
>  }
>  
> +static int read_metadata_from_file(AVFormatContext *s, unsigned int ts, int header)
> +{
> +    FLVContext *flv = s->priv_data;
> +    const AVInputFormat *meta_format = NULL;
> +    AVDictionary *current_meta_dict = NULL;
> +    float timestamp = ts/1000.f;
> +    int ret;
> +
> +    if (!flv->meta_filename)
> +        return 0;
> +
> +    meta_format = av_find_input_format("ffmetadata");
> +    if (!meta_format) {
> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
> +        return AVERROR(ENOSYS);
> +    }
> +
> +    avformat_close_input(&flv->meta_ctx);
> +
> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename, meta_format, NULL);
> +    if (ret < 0) {
> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s t: %f.", flv->meta_filename, timestamp);
> +        if (flv->meta_dict)
> +            av_log(s, AV_LOG_ERROR, " Continuing with old metadata.");
> +        av_log(s, AV_LOG_ERROR, "\n");
> +        return ret;
> +    }
> +
> +    if (flv->meta_dict) {
> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
> +        av_dict_free(&flv->meta_dict);
> +    }
> +
> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
> +    if (ret < 0) {
> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s at %f seconds. Continuing with old metadata.\n", flv->meta_filename, timestamp);
> +        av_dict_free(&flv->meta_dict);
> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
> +        av_dict_free(&current_meta_dict);
> +        return ret;
> +    }
> +
> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f seconds.\n", flv->meta_filename, header ? "in header" : "in video packet", timestamp);
> +    av_dict_free(&current_meta_dict);
> +    avformat_close_input(&flv->meta_ctx);
> +
> +    return 0;
> +}
> +
> +static int write_user_metadata_tag(AVFormatContext *s, AVDictionaryEntry *tag, AVIOContext *pb, int *metadata_count)
> +{
> +
> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count: %d\n", tag->key, tag->value, *metadata_count);
> +
> +    if(   !strcmp(tag->key, "width")
> +        ||!strcmp(tag->key, "height")
> +        ||!strcmp(tag->key, "videodatarate")
> +        ||!strcmp(tag->key, "framerate")
> +        ||!strcmp(tag->key, "videocodecid")
> +        ||!strcmp(tag->key, "audiodatarate")
> +        ||!strcmp(tag->key, "audiosamplerate")
> +        ||!strcmp(tag->key, "audiosamplesize")
> +        ||!strcmp(tag->key, "stereo")
> +        ||!strcmp(tag->key, "audiocodecid")
> +        ||!strcmp(tag->key, "duration")
> +        ||!strcmp(tag->key, "onMetaData")
> +        ||!strcmp(tag->key, "datasize")
> +        ||!strcmp(tag->key, "lasttimestamp")
> +        ||!strcmp(tag->key, "totalframes")
> +        ||!strcmp(tag->key, "hasAudio")
> +        ||!strcmp(tag->key, "hasVideo")
> +        ||!strcmp(tag->key, "hasCuePoints")
> +        ||!strcmp(tag->key, "hasMetadata")
> +        ||!strcmp(tag->key, "hasKeyframes")
> +    ){
> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
> +        return AVERROR(EINVAL);
> +    }
> +    put_amf_string(pb, tag->key);
> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
> +    put_amf_string(pb, tag->value);
> +    (*metadata_count)++;
> +
> +    return 0;
> +}
> +
>  static void write_metadata(AVFormatContext *s, unsigned int ts)
>  {
>      AVIOContext *pb = s->pb;
> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s, unsigned int ts)
>      }
>  
>      ff_standardize_creation_time(s);
> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
> -        if(   !strcmp(tag->key, "width")
> -            ||!strcmp(tag->key, "height")
> -            ||!strcmp(tag->key, "videodatarate")
> -            ||!strcmp(tag->key, "framerate")
> -            ||!strcmp(tag->key, "videocodecid")
> -            ||!strcmp(tag->key, "audiodatarate")
> -            ||!strcmp(tag->key, "audiosamplerate")
> -            ||!strcmp(tag->key, "audiosamplesize")
> -            ||!strcmp(tag->key, "stereo")
> -            ||!strcmp(tag->key, "audiocodecid")
> -            ||!strcmp(tag->key, "duration")
> -            ||!strcmp(tag->key, "onMetaData")
> -            ||!strcmp(tag->key, "datasize")
> -            ||!strcmp(tag->key, "lasttimestamp")
> -            ||!strcmp(tag->key, "totalframes")
> -            ||!strcmp(tag->key, "hasAudio")
> -            ||!strcmp(tag->key, "hasVideo")
> -            ||!strcmp(tag->key, "hasCuePoints")
> -            ||!strcmp(tag->key, "hasMetadata")
> -            ||!strcmp(tag->key, "hasKeyframes")
> -        ){
> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
> -            continue;
> -        }
> -        put_amf_string(pb, tag->key);
> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
> -        put_amf_string(pb, tag->value);
> -        metadata_count++;
> -    }
> +
> +    while (tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))
> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
> +    while (tag = av_dict_get(flv->meta_dict, "", tag, AV_DICT_IGNORE_SUFFIX))
> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>  
>      if (write_duration_filesize) {
>          put_amf_string(pb, "filesize");
> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>      if (flv->flags & FLV_NO_METADATA) {
>          pb->seekable = 0;
>      } else {
> +        read_metadata_from_file(s, 0, 1);
>          write_metadata(s, 0);
>      }
>  
> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
>      if (meta_upd_flag ||
>          (flv->meta_period == FLV_META_AT_KF        && par->codec_type == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>          (flv->meta_period == FLV_META_EVERY_PACKET && par->codec_type == AVMEDIA_TYPE_VIDEO )) {
> +        read_metadata_from_file(s, ts, 0);
>          write_metadata(s, ts);
>          if (meta_upd_flag)
>              s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>      }
>      flv->filepositions = flv->head_filepositions = NULL;
>      flv->filepositions_count = 0;
> +
> +    if (flv->meta_dict)
> +        av_dict_free(&flv->meta_dict);
> +
> +    avformat_close_input(&flv->meta_ctx);
>  }
>  
>  static const AVOption options[] = {
> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>      { "at_start",      "only once at start",        0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>      { "at_keyframes",  "with every video keyframe", 0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>      { "every_packet",  "with every video packet",   0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
> +    { "meta_filename", "ffmetadata file to import metadata", offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>      { NULL },
>  };
>  

What does this achieve that can't be achieved by other means (namely by
an API-user updating the relevant metadata dictionaries)?

- Andreas
Gyan Doshi Feb. 4, 2023, 10:11 a.m. UTC | #2
On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
> Gyan Doshi:
>> Useful, in conjuntion with option meta_period, to vary metadata during
>> stream.
>>
>> File format is ffmetadata.
>> ---
>>   configure            |   2 +-
>>   doc/muxers.texi      |   3 +
>>   libavformat/flvenc.c | 133 +++++++++++++++++++++++++++++++++----------
>>   3 files changed, 107 insertions(+), 31 deletions(-)
>>
>> diff --git a/configure b/configure
>> index 9d78a244a3..de371632c4 100755
>> --- a/configure
>> +++ b/configure
>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>   f4v_muxer_select="mov_muxer"
>>   fifo_muxer_deps="threads"
>>   flac_demuxer_select="flac_parser"
>> -flv_muxer_select="aac_adtstoasc_bsf"
>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>   gxf_muxer_select="pcm_rechunk_bsf"
>>   hds_muxer_select="flv_muxer"
>>   hls_demuxer_select="adts_header ac3_parser"
>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>> index 02ecddf186..000c92b2a7 100644
>> --- a/doc/muxers.texi
>> +++ b/doc/muxers.texi
>> @@ -555,6 +555,9 @@ With every video packet.
>>   @end table
>>   Note that metadata will always be re-emitted if a metadata update event is signalled.
>>   
>> +@item meta_filename
>> +Specify a ffmetadata file from which to load metadata.
>> +
>>   @end table
>>   
>>   @anchor{framecrc}
>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>> index d1c7a493d1..1332b18b41 100644
>> --- a/libavformat/flvenc.c
>> +++ b/libavformat/flvenc.c
>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>       double framerate;
>>       AVCodecParameters *data_par;
>>   
>> +    char *meta_filename;
>> +    AVFormatContext *meta_ctx;
>> +    AVDictionary *meta_dict;
>> +
>>       int flags;
>>       int meta_period;
>>   } FLVContext;
>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>>       avio_w8(pb, !!b);
>>   }
>>   
>> +static int read_metadata_from_file(AVFormatContext *s, unsigned int ts, int header)
>> +{
>> +    FLVContext *flv = s->priv_data;
>> +    const AVInputFormat *meta_format = NULL;
>> +    AVDictionary *current_meta_dict = NULL;
>> +    float timestamp = ts/1000.f;
>> +    int ret;
>> +
>> +    if (!flv->meta_filename)
>> +        return 0;
>> +
>> +    meta_format = av_find_input_format("ffmetadata");
>> +    if (!meta_format) {
>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
>> +        return AVERROR(ENOSYS);
>> +    }
>> +
>> +    avformat_close_input(&flv->meta_ctx);
>> +
>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename, meta_format, NULL);
>> +    if (ret < 0) {
>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s t: %f.", flv->meta_filename, timestamp);
>> +        if (flv->meta_dict)
>> +            av_log(s, AV_LOG_ERROR, " Continuing with old metadata.");
>> +        av_log(s, AV_LOG_ERROR, "\n");
>> +        return ret;
>> +    }
>> +
>> +    if (flv->meta_dict) {
>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>> +        av_dict_free(&flv->meta_dict);
>> +    }
>> +
>> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
>> +    if (ret < 0) {
>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s at %f seconds. Continuing with old metadata.\n", flv->meta_filename, timestamp);
>> +        av_dict_free(&flv->meta_dict);
>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>> +        av_dict_free(&current_meta_dict);
>> +        return ret;
>> +    }
>> +
>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f seconds.\n", flv->meta_filename, header ? "in header" : "in video packet", timestamp);
>> +    av_dict_free(&current_meta_dict);
>> +    avformat_close_input(&flv->meta_ctx);
>> +
>> +    return 0;
>> +}
>> +
>> +static int write_user_metadata_tag(AVFormatContext *s, AVDictionaryEntry *tag, AVIOContext *pb, int *metadata_count)
>> +{
>> +
>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count: %d\n", tag->key, tag->value, *metadata_count);
>> +
>> +    if(   !strcmp(tag->key, "width")
>> +        ||!strcmp(tag->key, "height")
>> +        ||!strcmp(tag->key, "videodatarate")
>> +        ||!strcmp(tag->key, "framerate")
>> +        ||!strcmp(tag->key, "videocodecid")
>> +        ||!strcmp(tag->key, "audiodatarate")
>> +        ||!strcmp(tag->key, "audiosamplerate")
>> +        ||!strcmp(tag->key, "audiosamplesize")
>> +        ||!strcmp(tag->key, "stereo")
>> +        ||!strcmp(tag->key, "audiocodecid")
>> +        ||!strcmp(tag->key, "duration")
>> +        ||!strcmp(tag->key, "onMetaData")
>> +        ||!strcmp(tag->key, "datasize")
>> +        ||!strcmp(tag->key, "lasttimestamp")
>> +        ||!strcmp(tag->key, "totalframes")
>> +        ||!strcmp(tag->key, "hasAudio")
>> +        ||!strcmp(tag->key, "hasVideo")
>> +        ||!strcmp(tag->key, "hasCuePoints")
>> +        ||!strcmp(tag->key, "hasMetadata")
>> +        ||!strcmp(tag->key, "hasKeyframes")
>> +    ){
>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
>> +        return AVERROR(EINVAL);
>> +    }
>> +    put_amf_string(pb, tag->key);
>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>> +    put_amf_string(pb, tag->value);
>> +    (*metadata_count)++;
>> +
>> +    return 0;
>> +}
>> +
>>   static void write_metadata(AVFormatContext *s, unsigned int ts)
>>   {
>>       AVIOContext *pb = s->pb;
>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s, unsigned int ts)
>>       }
>>   
>>       ff_standardize_creation_time(s);
>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>> -        if(   !strcmp(tag->key, "width")
>> -            ||!strcmp(tag->key, "height")
>> -            ||!strcmp(tag->key, "videodatarate")
>> -            ||!strcmp(tag->key, "framerate")
>> -            ||!strcmp(tag->key, "videocodecid")
>> -            ||!strcmp(tag->key, "audiodatarate")
>> -            ||!strcmp(tag->key, "audiosamplerate")
>> -            ||!strcmp(tag->key, "audiosamplesize")
>> -            ||!strcmp(tag->key, "stereo")
>> -            ||!strcmp(tag->key, "audiocodecid")
>> -            ||!strcmp(tag->key, "duration")
>> -            ||!strcmp(tag->key, "onMetaData")
>> -            ||!strcmp(tag->key, "datasize")
>> -            ||!strcmp(tag->key, "lasttimestamp")
>> -            ||!strcmp(tag->key, "totalframes")
>> -            ||!strcmp(tag->key, "hasAudio")
>> -            ||!strcmp(tag->key, "hasVideo")
>> -            ||!strcmp(tag->key, "hasCuePoints")
>> -            ||!strcmp(tag->key, "hasMetadata")
>> -            ||!strcmp(tag->key, "hasKeyframes")
>> -        ){
>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
>> -            continue;
>> -        }
>> -        put_amf_string(pb, tag->key);
>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>> -        put_amf_string(pb, tag->value);
>> -        metadata_count++;
>> -    }
>> +
>> +    while (tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))
>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>> +    while (tag = av_dict_get(flv->meta_dict, "", tag, AV_DICT_IGNORE_SUFFIX))
>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>   
>>       if (write_duration_filesize) {
>>           put_amf_string(pb, "filesize");
>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>       if (flv->flags & FLV_NO_METADATA) {
>>           pb->seekable = 0;
>>       } else {
>> +        read_metadata_from_file(s, 0, 1);
>>           write_metadata(s, 0);
>>       }
>>   
>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
>>       if (meta_upd_flag ||
>>           (flv->meta_period == FLV_META_AT_KF        && par->codec_type == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>           (flv->meta_period == FLV_META_EVERY_PACKET && par->codec_type == AVMEDIA_TYPE_VIDEO )) {
>> +        read_metadata_from_file(s, ts, 0);
>>           write_metadata(s, ts);
>>           if (meta_upd_flag)
>>               s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>       }
>>       flv->filepositions = flv->head_filepositions = NULL;
>>       flv->filepositions_count = 0;
>> +
>> +    if (flv->meta_dict)
>> +        av_dict_free(&flv->meta_dict);
>> +
>> +    avformat_close_input(&flv->meta_ctx);
>>   }
>>   
>>   static const AVOption options[] = {
>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>       { "at_start",      "only once at start",        0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>       { "at_keyframes",  "with every video keyframe", 0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>       { "every_packet",  "with every video packet",   0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>> +    { "meta_filename", "ffmetadata file to import metadata", offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>       { NULL },
>>   };
>>   
> What does this achieve that can't be achieved by other means (namely by
> an API-user updating the relevant metadata dictionaries)?

This option together with meta_period is for CLI users. Similar to 
drawtext's textfile + reload.
It's been used in production for over a year at a client. Think it will 
be broadly useful.

Regards,
Gyan
Paul B Mahol Feb. 4, 2023, 10:16 a.m. UTC | #3
On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>
>
> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>> Gyan Doshi:
>>> Useful, in conjuntion with option meta_period, to vary metadata during
>>> stream.
>>>
>>> File format is ffmetadata.
>>> ---
>>>   configure            |   2 +-
>>>   doc/muxers.texi      |   3 +
>>>   libavformat/flvenc.c | 133 +++++++++++++++++++++++++++++++++----------
>>>   3 files changed, 107 insertions(+), 31 deletions(-)
>>>
>>> diff --git a/configure b/configure
>>> index 9d78a244a3..de371632c4 100755
>>> --- a/configure
>>> +++ b/configure
>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>   f4v_muxer_select="mov_muxer"
>>>   fifo_muxer_deps="threads"
>>>   flac_demuxer_select="flac_parser"
>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>   gxf_muxer_select="pcm_rechunk_bsf"
>>>   hds_muxer_select="flv_muxer"
>>>   hls_demuxer_select="adts_header ac3_parser"
>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>> index 02ecddf186..000c92b2a7 100644
>>> --- a/doc/muxers.texi
>>> +++ b/doc/muxers.texi
>>> @@ -555,6 +555,9 @@ With every video packet.
>>>   @end table
>>>   Note that metadata will always be re-emitted if a metadata update event
>>> is signalled.
>>>
>>> +@item meta_filename
>>> +Specify a ffmetadata file from which to load metadata.
>>> +
>>>   @end table
>>>
>>>   @anchor{framecrc}
>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>> index d1c7a493d1..1332b18b41 100644
>>> --- a/libavformat/flvenc.c
>>> +++ b/libavformat/flvenc.c
>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>       double framerate;
>>>       AVCodecParameters *data_par;
>>>
>>> +    char *meta_filename;
>>> +    AVFormatContext *meta_ctx;
>>> +    AVDictionary *meta_dict;
>>> +
>>>       int flags;
>>>       int meta_period;
>>>   } FLVContext;
>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>>>       avio_w8(pb, !!b);
>>>   }
>>>
>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned int ts,
>>> int header)
>>> +{
>>> +    FLVContext *flv = s->priv_data;
>>> +    const AVInputFormat *meta_format = NULL;
>>> +    AVDictionary *current_meta_dict = NULL;
>>> +    float timestamp = ts/1000.f;
>>> +    int ret;
>>> +
>>> +    if (!flv->meta_filename)
>>> +        return 0;
>>> +
>>> +    meta_format = av_find_input_format("ffmetadata");
>>> +    if (!meta_format) {
>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
>>> +        return AVERROR(ENOSYS);
>>> +    }
>>> +
>>> +    avformat_close_input(&flv->meta_ctx);
>>> +
>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>> meta_format, NULL);
>>> +    if (ret < 0) {
>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s t:
>>> %f.", flv->meta_filename, timestamp);
>>> +        if (flv->meta_dict)
>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old metadata.");
>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (flv->meta_dict) {
>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>> +        av_dict_free(&flv->meta_dict);
>>> +    }
>>> +
>>> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
>>> +    if (ret < 0) {
>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s at
>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>> timestamp);
>>> +        av_dict_free(&flv->meta_dict);
>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>> +        av_dict_free(&current_meta_dict);
>>> +        return ret;
>>> +    }
>>> +
>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f
>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>> packet", timestamp);
>>> +    av_dict_free(&current_meta_dict);
>>> +    avformat_close_input(&flv->meta_ctx);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int write_user_metadata_tag(AVFormatContext *s, AVDictionaryEntry
>>> *tag, AVIOContext *pb, int *metadata_count)
>>> +{
>>> +
>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count: %d\n",
>>> tag->key, tag->value, *metadata_count);
>>> +
>>> +    if(   !strcmp(tag->key, "width")
>>> +        ||!strcmp(tag->key, "height")
>>> +        ||!strcmp(tag->key, "videodatarate")
>>> +        ||!strcmp(tag->key, "framerate")
>>> +        ||!strcmp(tag->key, "videocodecid")
>>> +        ||!strcmp(tag->key, "audiodatarate")
>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>> +        ||!strcmp(tag->key, "stereo")
>>> +        ||!strcmp(tag->key, "audiocodecid")
>>> +        ||!strcmp(tag->key, "duration")
>>> +        ||!strcmp(tag->key, "onMetaData")
>>> +        ||!strcmp(tag->key, "datasize")
>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>> +        ||!strcmp(tag->key, "totalframes")
>>> +        ||!strcmp(tag->key, "hasAudio")
>>> +        ||!strcmp(tag->key, "hasVideo")
>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>> +        ||!strcmp(tag->key, "hasMetadata")
>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>> +    ){
>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>> tag->key);
>>> +        return AVERROR(EINVAL);
>>> +    }
>>> +    put_amf_string(pb, tag->key);
>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>> +    put_amf_string(pb, tag->value);
>>> +    (*metadata_count)++;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>>   static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>   {
>>>       AVIOContext *pb = s->pb;
>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s,
>>> unsigned int ts)
>>>       }
>>>
>>>       ff_standardize_creation_time(s);
>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>> -        if(   !strcmp(tag->key, "width")
>>> -            ||!strcmp(tag->key, "height")
>>> -            ||!strcmp(tag->key, "videodatarate")
>>> -            ||!strcmp(tag->key, "framerate")
>>> -            ||!strcmp(tag->key, "videocodecid")
>>> -            ||!strcmp(tag->key, "audiodatarate")
>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>> -            ||!strcmp(tag->key, "stereo")
>>> -            ||!strcmp(tag->key, "audiocodecid")
>>> -            ||!strcmp(tag->key, "duration")
>>> -            ||!strcmp(tag->key, "onMetaData")
>>> -            ||!strcmp(tag->key, "datasize")
>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>> -            ||!strcmp(tag->key, "totalframes")
>>> -            ||!strcmp(tag->key, "hasAudio")
>>> -            ||!strcmp(tag->key, "hasVideo")
>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>> -            ||!strcmp(tag->key, "hasMetadata")
>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>> -        ){
>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>> tag->key);
>>> -            continue;
>>> -        }
>>> -        put_amf_string(pb, tag->key);
>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>> -        put_amf_string(pb, tag->value);
>>> -        metadata_count++;
>>> -    }
>>> +
>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>> AV_DICT_IGNORE_SUFFIX))
>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>> AV_DICT_IGNORE_SUFFIX))
>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>
>>>       if (write_duration_filesize) {
>>>           put_amf_string(pb, "filesize");
>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>       if (flv->flags & FLV_NO_METADATA) {
>>>           pb->seekable = 0;
>>>       } else {
>>> +        read_metadata_from_file(s, 0, 1);
>>>           write_metadata(s, 0);
>>>       }
>>>
>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>> AVPacket *pkt)
>>>       if (meta_upd_flag ||
>>>           (flv->meta_period == FLV_META_AT_KF        && par->codec_type
>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>           (flv->meta_period == FLV_META_EVERY_PACKET && par->codec_type
>>> == AVMEDIA_TYPE_VIDEO )) {
>>> +        read_metadata_from_file(s, ts, 0);
>>>           write_metadata(s, ts);
>>>           if (meta_upd_flag)
>>>               s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>       }
>>>       flv->filepositions = flv->head_filepositions = NULL;
>>>       flv->filepositions_count = 0;
>>> +
>>> +    if (flv->meta_dict)
>>> +        av_dict_free(&flv->meta_dict);
>>> +
>>> +    avformat_close_input(&flv->meta_ctx);
>>>   }
>>>
>>>   static const AVOption options[] = {
>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>       { "at_start",      "only once at start",        0,
>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX,
>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>       { "at_keyframes",  "with every video keyframe", 0,
>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX,
>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>       { "every_packet",  "with every video packet",   0,
>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX,
>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str = NULL},
>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>       { NULL },
>>>   };
>>>
>> What does this achieve that can't be achieved by other means (namely by
>> an API-user updating the relevant metadata dictionaries)?
>
> This option together with meta_period is for CLI users. Similar to
> drawtext's textfile + reload.
> It's been used in production for over a year at a client. Think it will
> be broadly useful.

Not everything that is useful now is correct on long term.

Muxer specific metadata handling by reading from files is big hack.

NAK.

>
> Regards,
> Gyan
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Gyan Doshi Feb. 4, 2023, 10:24 a.m. UTC | #4
On 2023-02-04 03:46 pm, Paul B Mahol wrote:
> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>
>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>> Gyan Doshi:
>>>> Useful, in conjuntion with option meta_period, to vary metadata during
>>>> stream.
>>>>
>>>> File format is ffmetadata.
>>>> ---
>>>>    configure            |   2 +-
>>>>    doc/muxers.texi      |   3 +
>>>>    libavformat/flvenc.c | 133 +++++++++++++++++++++++++++++++++----------
>>>>    3 files changed, 107 insertions(+), 31 deletions(-)
>>>>
>>>> diff --git a/configure b/configure
>>>> index 9d78a244a3..de371632c4 100755
>>>> --- a/configure
>>>> +++ b/configure
>>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>>    f4v_muxer_select="mov_muxer"
>>>>    fifo_muxer_deps="threads"
>>>>    flac_demuxer_select="flac_parser"
>>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>>    gxf_muxer_select="pcm_rechunk_bsf"
>>>>    hds_muxer_select="flv_muxer"
>>>>    hls_demuxer_select="adts_header ac3_parser"
>>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>>> index 02ecddf186..000c92b2a7 100644
>>>> --- a/doc/muxers.texi
>>>> +++ b/doc/muxers.texi
>>>> @@ -555,6 +555,9 @@ With every video packet.
>>>>    @end table
>>>>    Note that metadata will always be re-emitted if a metadata update event
>>>> is signalled.
>>>>
>>>> +@item meta_filename
>>>> +Specify a ffmetadata file from which to load metadata.
>>>> +
>>>>    @end table
>>>>
>>>>    @anchor{framecrc}
>>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>>> index d1c7a493d1..1332b18b41 100644
>>>> --- a/libavformat/flvenc.c
>>>> +++ b/libavformat/flvenc.c
>>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>>        double framerate;
>>>>        AVCodecParameters *data_par;
>>>>
>>>> +    char *meta_filename;
>>>> +    AVFormatContext *meta_ctx;
>>>> +    AVDictionary *meta_dict;
>>>> +
>>>>        int flags;
>>>>        int meta_period;
>>>>    } FLVContext;
>>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>>>>        avio_w8(pb, !!b);
>>>>    }
>>>>
>>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned int ts,
>>>> int header)
>>>> +{
>>>> +    FLVContext *flv = s->priv_data;
>>>> +    const AVInputFormat *meta_format = NULL;
>>>> +    AVDictionary *current_meta_dict = NULL;
>>>> +    float timestamp = ts/1000.f;
>>>> +    int ret;
>>>> +
>>>> +    if (!flv->meta_filename)
>>>> +        return 0;
>>>> +
>>>> +    meta_format = av_find_input_format("ffmetadata");
>>>> +    if (!meta_format) {
>>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
>>>> +        return AVERROR(ENOSYS);
>>>> +    }
>>>> +
>>>> +    avformat_close_input(&flv->meta_ctx);
>>>> +
>>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>>> meta_format, NULL);
>>>> +    if (ret < 0) {
>>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s t:
>>>> %f.", flv->meta_filename, timestamp);
>>>> +        if (flv->meta_dict)
>>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old metadata.");
>>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    if (flv->meta_dict) {
>>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>>> +        av_dict_free(&flv->meta_dict);
>>>> +    }
>>>> +
>>>> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
>>>> +    if (ret < 0) {
>>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s at
>>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>>> timestamp);
>>>> +        av_dict_free(&flv->meta_dict);
>>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>>> +        av_dict_free(&current_meta_dict);
>>>> +        return ret;
>>>> +    }
>>>> +
>>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f
>>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>>> packet", timestamp);
>>>> +    av_dict_free(&current_meta_dict);
>>>> +    avformat_close_input(&flv->meta_ctx);
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int write_user_metadata_tag(AVFormatContext *s, AVDictionaryEntry
>>>> *tag, AVIOContext *pb, int *metadata_count)
>>>> +{
>>>> +
>>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count: %d\n",
>>>> tag->key, tag->value, *metadata_count);
>>>> +
>>>> +    if(   !strcmp(tag->key, "width")
>>>> +        ||!strcmp(tag->key, "height")
>>>> +        ||!strcmp(tag->key, "videodatarate")
>>>> +        ||!strcmp(tag->key, "framerate")
>>>> +        ||!strcmp(tag->key, "videocodecid")
>>>> +        ||!strcmp(tag->key, "audiodatarate")
>>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>>> +        ||!strcmp(tag->key, "stereo")
>>>> +        ||!strcmp(tag->key, "audiocodecid")
>>>> +        ||!strcmp(tag->key, "duration")
>>>> +        ||!strcmp(tag->key, "onMetaData")
>>>> +        ||!strcmp(tag->key, "datasize")
>>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>>> +        ||!strcmp(tag->key, "totalframes")
>>>> +        ||!strcmp(tag->key, "hasAudio")
>>>> +        ||!strcmp(tag->key, "hasVideo")
>>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>>> +        ||!strcmp(tag->key, "hasMetadata")
>>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>>> +    ){
>>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>> tag->key);
>>>> +        return AVERROR(EINVAL);
>>>> +    }
>>>> +    put_amf_string(pb, tag->key);
>>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>> +    put_amf_string(pb, tag->value);
>>>> +    (*metadata_count)++;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>>    static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>>    {
>>>>        AVIOContext *pb = s->pb;
>>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s,
>>>> unsigned int ts)
>>>>        }
>>>>
>>>>        ff_standardize_creation_time(s);
>>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>>> -        if(   !strcmp(tag->key, "width")
>>>> -            ||!strcmp(tag->key, "height")
>>>> -            ||!strcmp(tag->key, "videodatarate")
>>>> -            ||!strcmp(tag->key, "framerate")
>>>> -            ||!strcmp(tag->key, "videocodecid")
>>>> -            ||!strcmp(tag->key, "audiodatarate")
>>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>>> -            ||!strcmp(tag->key, "stereo")
>>>> -            ||!strcmp(tag->key, "audiocodecid")
>>>> -            ||!strcmp(tag->key, "duration")
>>>> -            ||!strcmp(tag->key, "onMetaData")
>>>> -            ||!strcmp(tag->key, "datasize")
>>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>>> -            ||!strcmp(tag->key, "totalframes")
>>>> -            ||!strcmp(tag->key, "hasAudio")
>>>> -            ||!strcmp(tag->key, "hasVideo")
>>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>>> -            ||!strcmp(tag->key, "hasMetadata")
>>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>>> -        ){
>>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>> tag->key);
>>>> -            continue;
>>>> -        }
>>>> -        put_amf_string(pb, tag->key);
>>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>> -        put_amf_string(pb, tag->value);
>>>> -        metadata_count++;
>>>> -    }
>>>> +
>>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>>> AV_DICT_IGNORE_SUFFIX))
>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>>> AV_DICT_IGNORE_SUFFIX))
>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>
>>>>        if (write_duration_filesize) {
>>>>            put_amf_string(pb, "filesize");
>>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>>        if (flv->flags & FLV_NO_METADATA) {
>>>>            pb->seekable = 0;
>>>>        } else {
>>>> +        read_metadata_from_file(s, 0, 1);
>>>>            write_metadata(s, 0);
>>>>        }
>>>>
>>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>>> AVPacket *pkt)
>>>>        if (meta_upd_flag ||
>>>>            (flv->meta_period == FLV_META_AT_KF        && par->codec_type
>>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>>            (flv->meta_period == FLV_META_EVERY_PACKET && par->codec_type
>>>> == AVMEDIA_TYPE_VIDEO )) {
>>>> +        read_metadata_from_file(s, ts, 0);
>>>>            write_metadata(s, ts);
>>>>            if (meta_upd_flag)
>>>>                s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>>        }
>>>>        flv->filepositions = flv->head_filepositions = NULL;
>>>>        flv->filepositions_count = 0;
>>>> +
>>>> +    if (flv->meta_dict)
>>>> +        av_dict_free(&flv->meta_dict);
>>>> +
>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>    }
>>>>
>>>>    static const AVOption options[] = {
>>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>>        { "at_start",      "only once at start",        0,
>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX,
>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>        { "at_keyframes",  "with every video keyframe", 0,
>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX,
>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>        { "every_packet",  "with every video packet",   0,
>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX,
>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str = NULL},
>>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>>        { NULL },
>>>>    };
>>>>
>>> What does this achieve that can't be achieved by other means (namely by
>>> an API-user updating the relevant metadata dictionaries)?
>> This option together with meta_period is for CLI users. Similar to
>> drawtext's textfile + reload.
>> It's been used in production for over a year at a client. Think it will
>> be broadly useful.
> Not everything that is useful now is correct on long term.
>
> Muxer specific metadata handling by reading from files is big hack.

What would you suggest?

Regards,
Gyan
Paul B Mahol Feb. 4, 2023, 10:32 a.m. UTC | #5
On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>
>
> On 2023-02-04 03:46 pm, Paul B Mahol wrote:
>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>
>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>> Gyan Doshi:
>>>>> Useful, in conjuntion with option meta_period, to vary metadata during
>>>>> stream.
>>>>>
>>>>> File format is ffmetadata.
>>>>> ---
>>>>>    configure            |   2 +-
>>>>>    doc/muxers.texi      |   3 +
>>>>>    libavformat/flvenc.c | 133
>>>>> +++++++++++++++++++++++++++++++++----------
>>>>>    3 files changed, 107 insertions(+), 31 deletions(-)
>>>>>
>>>>> diff --git a/configure b/configure
>>>>> index 9d78a244a3..de371632c4 100755
>>>>> --- a/configure
>>>>> +++ b/configure
>>>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>>>    f4v_muxer_select="mov_muxer"
>>>>>    fifo_muxer_deps="threads"
>>>>>    flac_demuxer_select="flac_parser"
>>>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>>>    gxf_muxer_select="pcm_rechunk_bsf"
>>>>>    hds_muxer_select="flv_muxer"
>>>>>    hls_demuxer_select="adts_header ac3_parser"
>>>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>>>> index 02ecddf186..000c92b2a7 100644
>>>>> --- a/doc/muxers.texi
>>>>> +++ b/doc/muxers.texi
>>>>> @@ -555,6 +555,9 @@ With every video packet.
>>>>>    @end table
>>>>>    Note that metadata will always be re-emitted if a metadata update
>>>>> event
>>>>> is signalled.
>>>>>
>>>>> +@item meta_filename
>>>>> +Specify a ffmetadata file from which to load metadata.
>>>>> +
>>>>>    @end table
>>>>>
>>>>>    @anchor{framecrc}
>>>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>>>> index d1c7a493d1..1332b18b41 100644
>>>>> --- a/libavformat/flvenc.c
>>>>> +++ b/libavformat/flvenc.c
>>>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>>>        double framerate;
>>>>>        AVCodecParameters *data_par;
>>>>>
>>>>> +    char *meta_filename;
>>>>> +    AVFormatContext *meta_ctx;
>>>>> +    AVDictionary *meta_dict;
>>>>> +
>>>>>        int flags;
>>>>>        int meta_period;
>>>>>    } FLVContext;
>>>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>>>>>        avio_w8(pb, !!b);
>>>>>    }
>>>>>
>>>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned int
>>>>> ts,
>>>>> int header)
>>>>> +{
>>>>> +    FLVContext *flv = s->priv_data;
>>>>> +    const AVInputFormat *meta_format = NULL;
>>>>> +    AVDictionary *current_meta_dict = NULL;
>>>>> +    float timestamp = ts/1000.f;
>>>>> +    int ret;
>>>>> +
>>>>> +    if (!flv->meta_filename)
>>>>> +        return 0;
>>>>> +
>>>>> +    meta_format = av_find_input_format("ffmetadata");
>>>>> +    if (!meta_format) {
>>>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
>>>>> +        return AVERROR(ENOSYS);
>>>>> +    }
>>>>> +
>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>> +
>>>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>>>> meta_format, NULL);
>>>>> +    if (ret < 0) {
>>>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s
>>>>> t:
>>>>> %f.", flv->meta_filename, timestamp);
>>>>> +        if (flv->meta_dict)
>>>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old
>>>>> metadata.");
>>>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>>>> +        return ret;
>>>>> +    }
>>>>> +
>>>>> +    if (flv->meta_dict) {
>>>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>>>> +        av_dict_free(&flv->meta_dict);
>>>>> +    }
>>>>> +
>>>>> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
>>>>> +    if (ret < 0) {
>>>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s
>>>>> at
>>>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>>>> timestamp);
>>>>> +        av_dict_free(&flv->meta_dict);
>>>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>>>> +        av_dict_free(&current_meta_dict);
>>>>> +        return ret;
>>>>> +    }
>>>>> +
>>>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f
>>>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>>>> packet", timestamp);
>>>>> +    av_dict_free(&current_meta_dict);
>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int write_user_metadata_tag(AVFormatContext *s,
>>>>> AVDictionaryEntry
>>>>> *tag, AVIOContext *pb, int *metadata_count)
>>>>> +{
>>>>> +
>>>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count:
>>>>> %d\n",
>>>>> tag->key, tag->value, *metadata_count);
>>>>> +
>>>>> +    if(   !strcmp(tag->key, "width")
>>>>> +        ||!strcmp(tag->key, "height")
>>>>> +        ||!strcmp(tag->key, "videodatarate")
>>>>> +        ||!strcmp(tag->key, "framerate")
>>>>> +        ||!strcmp(tag->key, "videocodecid")
>>>>> +        ||!strcmp(tag->key, "audiodatarate")
>>>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>>>> +        ||!strcmp(tag->key, "stereo")
>>>>> +        ||!strcmp(tag->key, "audiocodecid")
>>>>> +        ||!strcmp(tag->key, "duration")
>>>>> +        ||!strcmp(tag->key, "onMetaData")
>>>>> +        ||!strcmp(tag->key, "datasize")
>>>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>>>> +        ||!strcmp(tag->key, "totalframes")
>>>>> +        ||!strcmp(tag->key, "hasAudio")
>>>>> +        ||!strcmp(tag->key, "hasVideo")
>>>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>>>> +        ||!strcmp(tag->key, "hasMetadata")
>>>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>>>> +    ){
>>>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>> tag->key);
>>>>> +        return AVERROR(EINVAL);
>>>>> +    }
>>>>> +    put_amf_string(pb, tag->key);
>>>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>> +    put_amf_string(pb, tag->value);
>>>>> +    (*metadata_count)++;
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>>    static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>>>    {
>>>>>        AVIOContext *pb = s->pb;
>>>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s,
>>>>> unsigned int ts)
>>>>>        }
>>>>>
>>>>>        ff_standardize_creation_time(s);
>>>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>>>> -        if(   !strcmp(tag->key, "width")
>>>>> -            ||!strcmp(tag->key, "height")
>>>>> -            ||!strcmp(tag->key, "videodatarate")
>>>>> -            ||!strcmp(tag->key, "framerate")
>>>>> -            ||!strcmp(tag->key, "videocodecid")
>>>>> -            ||!strcmp(tag->key, "audiodatarate")
>>>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>>>> -            ||!strcmp(tag->key, "stereo")
>>>>> -            ||!strcmp(tag->key, "audiocodecid")
>>>>> -            ||!strcmp(tag->key, "duration")
>>>>> -            ||!strcmp(tag->key, "onMetaData")
>>>>> -            ||!strcmp(tag->key, "datasize")
>>>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>>>> -            ||!strcmp(tag->key, "totalframes")
>>>>> -            ||!strcmp(tag->key, "hasAudio")
>>>>> -            ||!strcmp(tag->key, "hasVideo")
>>>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>>>> -            ||!strcmp(tag->key, "hasMetadata")
>>>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>>>> -        ){
>>>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>> tag->key);
>>>>> -            continue;
>>>>> -        }
>>>>> -        put_amf_string(pb, tag->key);
>>>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>> -        put_amf_string(pb, tag->value);
>>>>> -        metadata_count++;
>>>>> -    }
>>>>> +
>>>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>
>>>>>        if (write_duration_filesize) {
>>>>>            put_amf_string(pb, "filesize");
>>>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>>>        if (flv->flags & FLV_NO_METADATA) {
>>>>>            pb->seekable = 0;
>>>>>        } else {
>>>>> +        read_metadata_from_file(s, 0, 1);
>>>>>            write_metadata(s, 0);
>>>>>        }
>>>>>
>>>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>>>> AVPacket *pkt)
>>>>>        if (meta_upd_flag ||
>>>>>            (flv->meta_period == FLV_META_AT_KF        &&
>>>>> par->codec_type
>>>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>>>            (flv->meta_period == FLV_META_EVERY_PACKET &&
>>>>> par->codec_type
>>>>> == AVMEDIA_TYPE_VIDEO )) {
>>>>> +        read_metadata_from_file(s, ts, 0);
>>>>>            write_metadata(s, ts);
>>>>>            if (meta_upd_flag)
>>>>>                s->event_flags &=
>>>>> ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>>>        }
>>>>>        flv->filepositions = flv->head_filepositions = NULL;
>>>>>        flv->filepositions_count = 0;
>>>>> +
>>>>> +    if (flv->meta_dict)
>>>>> +        av_dict_free(&flv->meta_dict);
>>>>> +
>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>    }
>>>>>
>>>>>    static const AVOption options[] = {
>>>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>>>        { "at_start",      "only once at start",        0,
>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX,
>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>        { "at_keyframes",  "with every video keyframe", 0,
>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX,
>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>        { "every_packet",  "with every video packet",   0,
>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX,
>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str =
>>>>> NULL},
>>>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>>>        { NULL },
>>>>>    };
>>>>>
>>>> What does this achieve that can't be achieved by other means (namely by
>>>> an API-user updating the relevant metadata dictionaries)?
>>> This option together with meta_period is for CLI users. Similar to
>>> drawtext's textfile + reload.
>>> It's been used in production for over a year at a client. Think it will
>>> be broadly useful.
>> Not everything that is useful now is correct on long term.
>>
>> Muxer specific metadata handling by reading from files is big hack.
>
> What would you suggest?

There should be way to handle this generically see Anton's patch that
adds new AVOption type.

Also those strcmp() spaghetti lines are unacceptable.
Gyan Doshi Feb. 4, 2023, 10:42 a.m. UTC | #6
On 2023-02-04 04:02 pm, Paul B Mahol wrote:
> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>
>> On 2023-02-04 03:46 pm, Paul B Mahol wrote:
>>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>>> Gyan Doshi:
>>>>>> Useful, in conjuntion with option meta_period, to vary metadata during
>>>>>> stream.
>>>>>>
>>>>>> File format is ffmetadata.
>>>>>> ---
>>>>>>     configure            |   2 +-
>>>>>>     doc/muxers.texi      |   3 +
>>>>>>     libavformat/flvenc.c | 133
>>>>>> +++++++++++++++++++++++++++++++++----------
>>>>>>     3 files changed, 107 insertions(+), 31 deletions(-)
>>>>>>
>>>>>> diff --git a/configure b/configure
>>>>>> index 9d78a244a3..de371632c4 100755
>>>>>> --- a/configure
>>>>>> +++ b/configure
>>>>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>>>>     f4v_muxer_select="mov_muxer"
>>>>>>     fifo_muxer_deps="threads"
>>>>>>     flac_demuxer_select="flac_parser"
>>>>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>>>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>>>>     gxf_muxer_select="pcm_rechunk_bsf"
>>>>>>     hds_muxer_select="flv_muxer"
>>>>>>     hls_demuxer_select="adts_header ac3_parser"
>>>>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>>>>> index 02ecddf186..000c92b2a7 100644
>>>>>> --- a/doc/muxers.texi
>>>>>> +++ b/doc/muxers.texi
>>>>>> @@ -555,6 +555,9 @@ With every video packet.
>>>>>>     @end table
>>>>>>     Note that metadata will always be re-emitted if a metadata update
>>>>>> event
>>>>>> is signalled.
>>>>>>
>>>>>> +@item meta_filename
>>>>>> +Specify a ffmetadata file from which to load metadata.
>>>>>> +
>>>>>>     @end table
>>>>>>
>>>>>>     @anchor{framecrc}
>>>>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>>>>> index d1c7a493d1..1332b18b41 100644
>>>>>> --- a/libavformat/flvenc.c
>>>>>> +++ b/libavformat/flvenc.c
>>>>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>>>>         double framerate;
>>>>>>         AVCodecParameters *data_par;
>>>>>>
>>>>>> +    char *meta_filename;
>>>>>> +    AVFormatContext *meta_ctx;
>>>>>> +    AVDictionary *meta_dict;
>>>>>> +
>>>>>>         int flags;
>>>>>>         int meta_period;
>>>>>>     } FLVContext;
>>>>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, int b)
>>>>>>         avio_w8(pb, !!b);
>>>>>>     }
>>>>>>
>>>>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned int
>>>>>> ts,
>>>>>> int header)
>>>>>> +{
>>>>>> +    FLVContext *flv = s->priv_data;
>>>>>> +    const AVInputFormat *meta_format = NULL;
>>>>>> +    AVDictionary *current_meta_dict = NULL;
>>>>>> +    float timestamp = ts/1000.f;
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    if (!flv->meta_filename)
>>>>>> +        return 0;
>>>>>> +
>>>>>> +    meta_format = av_find_input_format("ffmetadata");
>>>>>> +    if (!meta_format) {
>>>>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
>>>>>> +        return AVERROR(ENOSYS);
>>>>>> +    }
>>>>>> +
>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>> +
>>>>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>>>>> meta_format, NULL);
>>>>>> +    if (ret < 0) {
>>>>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s
>>>>>> t:
>>>>>> %f.", flv->meta_filename, timestamp);
>>>>>> +        if (flv->meta_dict)
>>>>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old
>>>>>> metadata.");
>>>>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>>>>> +        return ret;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (flv->meta_dict) {
>>>>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
>>>>>> +    if (ret < 0) {
>>>>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s
>>>>>> at
>>>>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>>>>> timestamp);
>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>>>>> +        av_dict_free(&current_meta_dict);
>>>>>> +        return ret;
>>>>>> +    }
>>>>>> +
>>>>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f
>>>>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>>>>> packet", timestamp);
>>>>>> +    av_dict_free(&current_meta_dict);
>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int write_user_metadata_tag(AVFormatContext *s,
>>>>>> AVDictionaryEntry
>>>>>> *tag, AVIOContext *pb, int *metadata_count)
>>>>>> +{
>>>>>> +
>>>>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count:
>>>>>> %d\n",
>>>>>> tag->key, tag->value, *metadata_count);
>>>>>> +
>>>>>> +    if(   !strcmp(tag->key, "width")
>>>>>> +        ||!strcmp(tag->key, "height")
>>>>>> +        ||!strcmp(tag->key, "videodatarate")
>>>>>> +        ||!strcmp(tag->key, "framerate")
>>>>>> +        ||!strcmp(tag->key, "videocodecid")
>>>>>> +        ||!strcmp(tag->key, "audiodatarate")
>>>>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>>>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>>>>> +        ||!strcmp(tag->key, "stereo")
>>>>>> +        ||!strcmp(tag->key, "audiocodecid")
>>>>>> +        ||!strcmp(tag->key, "duration")
>>>>>> +        ||!strcmp(tag->key, "onMetaData")
>>>>>> +        ||!strcmp(tag->key, "datasize")
>>>>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>>>>> +        ||!strcmp(tag->key, "totalframes")
>>>>>> +        ||!strcmp(tag->key, "hasAudio")
>>>>>> +        ||!strcmp(tag->key, "hasVideo")
>>>>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>>>>> +        ||!strcmp(tag->key, "hasMetadata")
>>>>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>>>>> +    ){
>>>>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>> tag->key);
>>>>>> +        return AVERROR(EINVAL);
>>>>>> +    }
>>>>>> +    put_amf_string(pb, tag->key);
>>>>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>> +    put_amf_string(pb, tag->value);
>>>>>> +    (*metadata_count)++;
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>>     static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>>>>     {
>>>>>>         AVIOContext *pb = s->pb;
>>>>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext *s,
>>>>>> unsigned int ts)
>>>>>>         }
>>>>>>
>>>>>>         ff_standardize_creation_time(s);
>>>>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>>>>> -        if(   !strcmp(tag->key, "width")
>>>>>> -            ||!strcmp(tag->key, "height")
>>>>>> -            ||!strcmp(tag->key, "videodatarate")
>>>>>> -            ||!strcmp(tag->key, "framerate")
>>>>>> -            ||!strcmp(tag->key, "videocodecid")
>>>>>> -            ||!strcmp(tag->key, "audiodatarate")
>>>>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>>>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>>>>> -            ||!strcmp(tag->key, "stereo")
>>>>>> -            ||!strcmp(tag->key, "audiocodecid")
>>>>>> -            ||!strcmp(tag->key, "duration")
>>>>>> -            ||!strcmp(tag->key, "onMetaData")
>>>>>> -            ||!strcmp(tag->key, "datasize")
>>>>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>>>>> -            ||!strcmp(tag->key, "totalframes")
>>>>>> -            ||!strcmp(tag->key, "hasAudio")
>>>>>> -            ||!strcmp(tag->key, "hasVideo")
>>>>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>>>>> -            ||!strcmp(tag->key, "hasMetadata")
>>>>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>>>>> -        ){
>>>>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>> tag->key);
>>>>>> -            continue;
>>>>>> -        }
>>>>>> -        put_amf_string(pb, tag->key);
>>>>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>> -        put_amf_string(pb, tag->value);
>>>>>> -        metadata_count++;
>>>>>> -    }
>>>>>> +
>>>>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>>
>>>>>>         if (write_duration_filesize) {
>>>>>>             put_amf_string(pb, "filesize");
>>>>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>>>>         if (flv->flags & FLV_NO_METADATA) {
>>>>>>             pb->seekable = 0;
>>>>>>         } else {
>>>>>> +        read_metadata_from_file(s, 0, 1);
>>>>>>             write_metadata(s, 0);
>>>>>>         }
>>>>>>
>>>>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>>>>> AVPacket *pkt)
>>>>>>         if (meta_upd_flag ||
>>>>>>             (flv->meta_period == FLV_META_AT_KF        &&
>>>>>> par->codec_type
>>>>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>>>>             (flv->meta_period == FLV_META_EVERY_PACKET &&
>>>>>> par->codec_type
>>>>>> == AVMEDIA_TYPE_VIDEO )) {
>>>>>> +        read_metadata_from_file(s, ts, 0);
>>>>>>             write_metadata(s, ts);
>>>>>>             if (meta_upd_flag)
>>>>>>                 s->event_flags &=
>>>>>> ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>>>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>>>>         }
>>>>>>         flv->filepositions = flv->head_filepositions = NULL;
>>>>>>         flv->filepositions_count = 0;
>>>>>> +
>>>>>> +    if (flv->meta_dict)
>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>> +
>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>     }
>>>>>>
>>>>>>     static const AVOption options[] = {
>>>>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>>>>         { "at_start",      "only once at start",        0,
>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX,
>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>         { "at_keyframes",  "with every video keyframe", 0,
>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX,
>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>         { "every_packet",  "with every video packet",   0,
>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX,
>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>>>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str =
>>>>>> NULL},
>>>>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>>>>         { NULL },
>>>>>>     };
>>>>>>
>>>>> What does this achieve that can't be achieved by other means (namely by
>>>>> an API-user updating the relevant metadata dictionaries)?
>>>> This option together with meta_period is for CLI users. Similar to
>>>> drawtext's textfile + reload.
>>>> It's been used in production for over a year at a client. Think it will
>>>> be broadly useful.
>>> Not everything that is useful now is correct on long term.
>>>
>>> Muxer specific metadata handling by reading from files is big hack.
>> What would you suggest?
> There should be way to handle this generically see Anton's patch that
> adds new AVOption type.
Link?

>
> Also those strcmp() spaghetti lines are unacceptable.

I didn't add those, only shifted them.

Regards,
Gyan
Gyan Doshi Feb. 5, 2023, 10:01 a.m. UTC | #7
On 2023-02-04 04:12 pm, Gyan Doshi wrote:
>
>
> On 2023-02-04 04:02 pm, Paul B Mahol wrote:
>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>
>>> On 2023-02-04 03:46 pm, Paul B Mahol wrote:
>>>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>>>> Gyan Doshi:
>>>>>>> Useful, in conjuntion with option meta_period, to vary metadata 
>>>>>>> during
>>>>>>> stream.
>>>>>>>
>>>>>>> File format is ffmetadata.
>>>>>>> ---
>>>>>>>     configure            |   2 +-
>>>>>>>     doc/muxers.texi      |   3 +
>>>>>>>     libavformat/flvenc.c | 133
>>>>>>> +++++++++++++++++++++++++++++++++----------
>>>>>>>     3 files changed, 107 insertions(+), 31 deletions(-)
>>>>>>>
>>>>>>> diff --git a/configure b/configure
>>>>>>> index 9d78a244a3..de371632c4 100755
>>>>>>> --- a/configure
>>>>>>> +++ b/configure
>>>>>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>>>>>     f4v_muxer_select="mov_muxer"
>>>>>>>     fifo_muxer_deps="threads"
>>>>>>>     flac_demuxer_select="flac_parser"
>>>>>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>>>>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>>>>>     gxf_muxer_select="pcm_rechunk_bsf"
>>>>>>>     hds_muxer_select="flv_muxer"
>>>>>>>     hls_demuxer_select="adts_header ac3_parser"
>>>>>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>>>>>> index 02ecddf186..000c92b2a7 100644
>>>>>>> --- a/doc/muxers.texi
>>>>>>> +++ b/doc/muxers.texi
>>>>>>> @@ -555,6 +555,9 @@ With every video packet.
>>>>>>>     @end table
>>>>>>>     Note that metadata will always be re-emitted if a metadata 
>>>>>>> update
>>>>>>> event
>>>>>>> is signalled.
>>>>>>>
>>>>>>> +@item meta_filename
>>>>>>> +Specify a ffmetadata file from which to load metadata.
>>>>>>> +
>>>>>>>     @end table
>>>>>>>
>>>>>>>     @anchor{framecrc}
>>>>>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>>>>>> index d1c7a493d1..1332b18b41 100644
>>>>>>> --- a/libavformat/flvenc.c
>>>>>>> +++ b/libavformat/flvenc.c
>>>>>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>>>>>         double framerate;
>>>>>>>         AVCodecParameters *data_par;
>>>>>>>
>>>>>>> +    char *meta_filename;
>>>>>>> +    AVFormatContext *meta_ctx;
>>>>>>> +    AVDictionary *meta_dict;
>>>>>>> +
>>>>>>>         int flags;
>>>>>>>         int meta_period;
>>>>>>>     } FLVContext;
>>>>>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb, 
>>>>>>> int b)
>>>>>>>         avio_w8(pb, !!b);
>>>>>>>     }
>>>>>>>
>>>>>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned 
>>>>>>> int
>>>>>>> ts,
>>>>>>> int header)
>>>>>>> +{
>>>>>>> +    FLVContext *flv = s->priv_data;
>>>>>>> +    const AVInputFormat *meta_format = NULL;
>>>>>>> +    AVDictionary *current_meta_dict = NULL;
>>>>>>> +    float timestamp = ts/1000.f;
>>>>>>> +    int ret;
>>>>>>> +
>>>>>>> +    if (!flv->meta_filename)
>>>>>>> +        return 0;
>>>>>>> +
>>>>>>> +    meta_format = av_find_input_format("ffmetadata");
>>>>>>> +    if (!meta_format) {
>>>>>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not 
>>>>>>> found.\n");
>>>>>>> +        return AVERROR(ENOSYS);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>> +
>>>>>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>>>>>> meta_format, NULL);
>>>>>>> +    if (ret < 0) {
>>>>>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from 
>>>>>>> file %s
>>>>>>> t:
>>>>>>> %f.", flv->meta_filename, timestamp);
>>>>>>> +        if (flv->meta_dict)
>>>>>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old
>>>>>>> metadata.");
>>>>>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>>>>>> +        return ret;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (flv->meta_dict) {
>>>>>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    ret = av_dict_copy(&flv->meta_dict, 
>>>>>>> flv->meta_ctx->metadata, 0);
>>>>>>> +    if (ret < 0) {
>>>>>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata 
>>>>>>> from %s
>>>>>>> at
>>>>>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>>>>>> timestamp);
>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>>>>>> +        av_dict_free(&current_meta_dict);
>>>>>>> +        return ret;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s 
>>>>>>> at %f
>>>>>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>>>>>> packet", timestamp);
>>>>>>> +    av_dict_free(&current_meta_dict);
>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>> +
>>>>>>> +    return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int write_user_metadata_tag(AVFormatContext *s,
>>>>>>> AVDictionaryEntry
>>>>>>> *tag, AVIOContext *pb, int *metadata_count)
>>>>>>> +{
>>>>>>> +
>>>>>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count:
>>>>>>> %d\n",
>>>>>>> tag->key, tag->value, *metadata_count);
>>>>>>> +
>>>>>>> +    if(   !strcmp(tag->key, "width")
>>>>>>> +        ||!strcmp(tag->key, "height")
>>>>>>> +        ||!strcmp(tag->key, "videodatarate")
>>>>>>> +        ||!strcmp(tag->key, "framerate")
>>>>>>> +        ||!strcmp(tag->key, "videocodecid")
>>>>>>> +        ||!strcmp(tag->key, "audiodatarate")
>>>>>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>>>>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>>>>>> +        ||!strcmp(tag->key, "stereo")
>>>>>>> +        ||!strcmp(tag->key, "audiocodecid")
>>>>>>> +        ||!strcmp(tag->key, "duration")
>>>>>>> +        ||!strcmp(tag->key, "onMetaData")
>>>>>>> +        ||!strcmp(tag->key, "datasize")
>>>>>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>>>>>> +        ||!strcmp(tag->key, "totalframes")
>>>>>>> +        ||!strcmp(tag->key, "hasAudio")
>>>>>>> +        ||!strcmp(tag->key, "hasVideo")
>>>>>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>>>>>> +        ||!strcmp(tag->key, "hasMetadata")
>>>>>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>>>>>> +    ){
>>>>>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>>> tag->key);
>>>>>>> +        return AVERROR(EINVAL);
>>>>>>> +    }
>>>>>>> +    put_amf_string(pb, tag->key);
>>>>>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>>> +    put_amf_string(pb, tag->value);
>>>>>>> +    (*metadata_count)++;
>>>>>>> +
>>>>>>> +    return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>>     static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>>>>>     {
>>>>>>>         AVIOContext *pb = s->pb;
>>>>>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext 
>>>>>>> *s,
>>>>>>> unsigned int ts)
>>>>>>>         }
>>>>>>>
>>>>>>>         ff_standardize_creation_time(s);
>>>>>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>>>>>> -        if(   !strcmp(tag->key, "width")
>>>>>>> -            ||!strcmp(tag->key, "height")
>>>>>>> -            ||!strcmp(tag->key, "videodatarate")
>>>>>>> -            ||!strcmp(tag->key, "framerate")
>>>>>>> -            ||!strcmp(tag->key, "videocodecid")
>>>>>>> -            ||!strcmp(tag->key, "audiodatarate")
>>>>>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>>>>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>>>>>> -            ||!strcmp(tag->key, "stereo")
>>>>>>> -            ||!strcmp(tag->key, "audiocodecid")
>>>>>>> -            ||!strcmp(tag->key, "duration")
>>>>>>> -            ||!strcmp(tag->key, "onMetaData")
>>>>>>> -            ||!strcmp(tag->key, "datasize")
>>>>>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>>>>>> -            ||!strcmp(tag->key, "totalframes")
>>>>>>> -            ||!strcmp(tag->key, "hasAudio")
>>>>>>> -            ||!strcmp(tag->key, "hasVideo")
>>>>>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>>>>>> -            ||!strcmp(tag->key, "hasMetadata")
>>>>>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>>>>>> -        ){
>>>>>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>>> tag->key);
>>>>>>> -            continue;
>>>>>>> -        }
>>>>>>> -        put_amf_string(pb, tag->key);
>>>>>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>>> -        put_amf_string(pb, tag->value);
>>>>>>> -        metadata_count++;
>>>>>>> -    }
>>>>>>> +
>>>>>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>>>
>>>>>>>         if (write_duration_filesize) {
>>>>>>>             put_amf_string(pb, "filesize");
>>>>>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>>>>>         if (flv->flags & FLV_NO_METADATA) {
>>>>>>>             pb->seekable = 0;
>>>>>>>         } else {
>>>>>>> +        read_metadata_from_file(s, 0, 1);
>>>>>>>             write_metadata(s, 0);
>>>>>>>         }
>>>>>>>
>>>>>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>>>>>> AVPacket *pkt)
>>>>>>>         if (meta_upd_flag ||
>>>>>>>             (flv->meta_period == FLV_META_AT_KF        &&
>>>>>>> par->codec_type
>>>>>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>>>>>             (flv->meta_period == FLV_META_EVERY_PACKET &&
>>>>>>> par->codec_type
>>>>>>> == AVMEDIA_TYPE_VIDEO )) {
>>>>>>> +        read_metadata_from_file(s, ts, 0);
>>>>>>>             write_metadata(s, ts);
>>>>>>>             if (meta_upd_flag)
>>>>>>>                 s->event_flags &=
>>>>>>> ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>>>>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>>>>>         }
>>>>>>>         flv->filepositions = flv->head_filepositions = NULL;
>>>>>>>         flv->filepositions_count = 0;
>>>>>>> +
>>>>>>> +    if (flv->meta_dict)
>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>> +
>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>>     }
>>>>>>>
>>>>>>>     static const AVOption options[] = {
>>>>>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>>>>>         { "at_start",      "only once at start",        0,
>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, 
>>>>>>> INT_MAX,
>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>>         { "at_keyframes",  "with every video keyframe", 0,
>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF}, INT_MIN, INT_MAX,
>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>>         { "every_packet",  "with every video packet",   0,
>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET}, INT_MIN, 
>>>>>>> INT_MAX,
>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>>>>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str =
>>>>>>> NULL},
>>>>>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>>>>>         { NULL },
>>>>>>>     };
>>>>>>>
>>>>>> What does this achieve that can't be achieved by other means 
>>>>>> (namely by
>>>>>> an API-user updating the relevant metadata dictionaries)?
>>>>> This option together with meta_period is for CLI users. Similar to
>>>>> drawtext's textfile + reload.
>>>>> It's been used in production for over a year at a client. Think it 
>>>>> will
>>>>> be broadly useful.
>>>> Not everything that is useful now is correct on long term.
>>>>
>>>> Muxer specific metadata handling by reading from files is big hack.
>>> What would you suggest?
>> There should be way to handle this generically see Anton's patch that
>> adds new AVOption type.
> Link?

So, I only found his patch to load filter options from files, but that's 
only inside filtergraphs.

Anyway, this option is for dynamic metadata. Which means that even 
meta_period would have to be a generic lavf option.
But most muxers don't emit metadata post-header.  In fact, flvenc is the 
only muxer that makes use of
AV***_EVENT_FLAG_METADATA_UPDATED.  So, we will need a new AVFMT flag to 
avoid reloading of file metadata in all other cases.
Seems overkill for something useful in one muxer.

Regards,
Gyan
Paul B Mahol Feb. 5, 2023, 10:09 a.m. UTC | #8
On 2/5/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>
>
> On 2023-02-04 04:12 pm, Gyan Doshi wrote:
>>
>>
>> On 2023-02-04 04:02 pm, Paul B Mahol wrote:
>>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>>
>>>> On 2023-02-04 03:46 pm, Paul B Mahol wrote:
>>>>> On 2/4/23, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>>>>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>>>>> Gyan Doshi:
>>>>>>>> Useful, in conjuntion with option meta_period, to vary metadata
>>>>>>>> during
>>>>>>>> stream.
>>>>>>>>
>>>>>>>> File format is ffmetadata.
>>>>>>>> ---
>>>>>>>>     configure            |   2 +-
>>>>>>>>     doc/muxers.texi      |   3 +
>>>>>>>>     libavformat/flvenc.c | 133
>>>>>>>> +++++++++++++++++++++++++++++++++----------
>>>>>>>>     3 files changed, 107 insertions(+), 31 deletions(-)
>>>>>>>>
>>>>>>>> diff --git a/configure b/configure
>>>>>>>> index 9d78a244a3..de371632c4 100755
>>>>>>>> --- a/configure
>>>>>>>> +++ b/configure
>>>>>>>> @@ -3433,7 +3433,7 @@ eac3_demuxer_select="ac3_parser"
>>>>>>>>     f4v_muxer_select="mov_muxer"
>>>>>>>>     fifo_muxer_deps="threads"
>>>>>>>>     flac_demuxer_select="flac_parser"
>>>>>>>> -flv_muxer_select="aac_adtstoasc_bsf"
>>>>>>>> +flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
>>>>>>>>     gxf_muxer_select="pcm_rechunk_bsf"
>>>>>>>>     hds_muxer_select="flv_muxer"
>>>>>>>>     hls_demuxer_select="adts_header ac3_parser"
>>>>>>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>>>>>>> index 02ecddf186..000c92b2a7 100644
>>>>>>>> --- a/doc/muxers.texi
>>>>>>>> +++ b/doc/muxers.texi
>>>>>>>> @@ -555,6 +555,9 @@ With every video packet.
>>>>>>>>     @end table
>>>>>>>>     Note that metadata will always be re-emitted if a metadata
>>>>>>>> update
>>>>>>>> event
>>>>>>>> is signalled.
>>>>>>>>
>>>>>>>> +@item meta_filename
>>>>>>>> +Specify a ffmetadata file from which to load metadata.
>>>>>>>> +
>>>>>>>>     @end table
>>>>>>>>
>>>>>>>>     @anchor{framecrc}
>>>>>>>> diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
>>>>>>>> index d1c7a493d1..1332b18b41 100644
>>>>>>>> --- a/libavformat/flvenc.c
>>>>>>>> +++ b/libavformat/flvenc.c
>>>>>>>> @@ -122,6 +122,10 @@ typedef struct FLVContext {
>>>>>>>>         double framerate;
>>>>>>>>         AVCodecParameters *data_par;
>>>>>>>>
>>>>>>>> +    char *meta_filename;
>>>>>>>> +    AVFormatContext *meta_ctx;
>>>>>>>> +    AVDictionary *meta_dict;
>>>>>>>> +
>>>>>>>>         int flags;
>>>>>>>>         int meta_period;
>>>>>>>>     } FLVContext;
>>>>>>>> @@ -277,6 +281,92 @@ static void put_amf_bool(AVIOContext *pb,
>>>>>>>> int b)
>>>>>>>>         avio_w8(pb, !!b);
>>>>>>>>     }
>>>>>>>>
>>>>>>>> +static int read_metadata_from_file(AVFormatContext *s, unsigned
>>>>>>>> int
>>>>>>>> ts,
>>>>>>>> int header)
>>>>>>>> +{
>>>>>>>> +    FLVContext *flv = s->priv_data;
>>>>>>>> +    const AVInputFormat *meta_format = NULL;
>>>>>>>> +    AVDictionary *current_meta_dict = NULL;
>>>>>>>> +    float timestamp = ts/1000.f;
>>>>>>>> +    int ret;
>>>>>>>> +
>>>>>>>> +    if (!flv->meta_filename)
>>>>>>>> +        return 0;
>>>>>>>> +
>>>>>>>> +    meta_format = av_find_input_format("ffmetadata");
>>>>>>>> +    if (!meta_format) {
>>>>>>>> +        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not
>>>>>>>> found.\n");
>>>>>>>> +        return AVERROR(ENOSYS);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>>> +
>>>>>>>> +    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename,
>>>>>>>> meta_format, NULL);
>>>>>>>> +    if (ret < 0) {
>>>>>>>> +        av_log(s, AV_LOG_ERROR, "Failed to read metadata from
>>>>>>>> file %s
>>>>>>>> t:
>>>>>>>> %f.", flv->meta_filename, timestamp);
>>>>>>>> +        if (flv->meta_dict)
>>>>>>>> +            av_log(s, AV_LOG_ERROR, " Continuing with old
>>>>>>>> metadata.");
>>>>>>>> +        av_log(s, AV_LOG_ERROR, "\n");
>>>>>>>> +        return ret;
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    if (flv->meta_dict) {
>>>>>>>> +        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
>>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    ret = av_dict_copy(&flv->meta_dict,
>>>>>>>> flv->meta_ctx->metadata, 0);
>>>>>>>> +    if (ret < 0) {
>>>>>>>> +        av_log(s, AV_LOG_ERROR, "Could not transfer metadata
>>>>>>>> from %s
>>>>>>>> at
>>>>>>>> %f seconds. Continuing with old metadata.\n", flv->meta_filename,
>>>>>>>> timestamp);
>>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>>> +        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
>>>>>>>> +        av_dict_free(&current_meta_dict);
>>>>>>>> +        return ret;
>>>>>>>> +    }
>>>>>>>> +
>>>>>>>> +    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s
>>>>>>>> at %f
>>>>>>>> seconds.\n", flv->meta_filename, header ? "in header" : "in video
>>>>>>>> packet", timestamp);
>>>>>>>> +    av_dict_free(&current_meta_dict);
>>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>>> +
>>>>>>>> +    return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +static int write_user_metadata_tag(AVFormatContext *s,
>>>>>>>> AVDictionaryEntry
>>>>>>>> *tag, AVIOContext *pb, int *metadata_count)
>>>>>>>> +{
>>>>>>>> +
>>>>>>>> +    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count:
>>>>>>>> %d\n",
>>>>>>>> tag->key, tag->value, *metadata_count);
>>>>>>>> +
>>>>>>>> +    if(   !strcmp(tag->key, "width")
>>>>>>>> +        ||!strcmp(tag->key, "height")
>>>>>>>> +        ||!strcmp(tag->key, "videodatarate")
>>>>>>>> +        ||!strcmp(tag->key, "framerate")
>>>>>>>> +        ||!strcmp(tag->key, "videocodecid")
>>>>>>>> +        ||!strcmp(tag->key, "audiodatarate")
>>>>>>>> +        ||!strcmp(tag->key, "audiosamplerate")
>>>>>>>> +        ||!strcmp(tag->key, "audiosamplesize")
>>>>>>>> +        ||!strcmp(tag->key, "stereo")
>>>>>>>> +        ||!strcmp(tag->key, "audiocodecid")
>>>>>>>> +        ||!strcmp(tag->key, "duration")
>>>>>>>> +        ||!strcmp(tag->key, "onMetaData")
>>>>>>>> +        ||!strcmp(tag->key, "datasize")
>>>>>>>> +        ||!strcmp(tag->key, "lasttimestamp")
>>>>>>>> +        ||!strcmp(tag->key, "totalframes")
>>>>>>>> +        ||!strcmp(tag->key, "hasAudio")
>>>>>>>> +        ||!strcmp(tag->key, "hasVideo")
>>>>>>>> +        ||!strcmp(tag->key, "hasCuePoints")
>>>>>>>> +        ||!strcmp(tag->key, "hasMetadata")
>>>>>>>> +        ||!strcmp(tag->key, "hasKeyframes")
>>>>>>>> +    ){
>>>>>>>> +        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>>>> tag->key);
>>>>>>>> +        return AVERROR(EINVAL);
>>>>>>>> +    }
>>>>>>>> +    put_amf_string(pb, tag->key);
>>>>>>>> +    avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>>>> +    put_amf_string(pb, tag->value);
>>>>>>>> +    (*metadata_count)++;
>>>>>>>> +
>>>>>>>> +    return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>>     static void write_metadata(AVFormatContext *s, unsigned int ts)
>>>>>>>>     {
>>>>>>>>         AVIOContext *pb = s->pb;
>>>>>>>> @@ -360,36 +450,11 @@ static void write_metadata(AVFormatContext
>>>>>>>> *s,
>>>>>>>> unsigned int ts)
>>>>>>>>         }
>>>>>>>>
>>>>>>>>         ff_standardize_creation_time(s);
>>>>>>>> -    while ((tag = av_dict_iterate(s->metadata, tag))) {
>>>>>>>> -        if(   !strcmp(tag->key, "width")
>>>>>>>> -            ||!strcmp(tag->key, "height")
>>>>>>>> -            ||!strcmp(tag->key, "videodatarate")
>>>>>>>> -            ||!strcmp(tag->key, "framerate")
>>>>>>>> -            ||!strcmp(tag->key, "videocodecid")
>>>>>>>> -            ||!strcmp(tag->key, "audiodatarate")
>>>>>>>> -            ||!strcmp(tag->key, "audiosamplerate")
>>>>>>>> -            ||!strcmp(tag->key, "audiosamplesize")
>>>>>>>> -            ||!strcmp(tag->key, "stereo")
>>>>>>>> -            ||!strcmp(tag->key, "audiocodecid")
>>>>>>>> -            ||!strcmp(tag->key, "duration")
>>>>>>>> -            ||!strcmp(tag->key, "onMetaData")
>>>>>>>> -            ||!strcmp(tag->key, "datasize")
>>>>>>>> -            ||!strcmp(tag->key, "lasttimestamp")
>>>>>>>> -            ||!strcmp(tag->key, "totalframes")
>>>>>>>> -            ||!strcmp(tag->key, "hasAudio")
>>>>>>>> -            ||!strcmp(tag->key, "hasVideo")
>>>>>>>> -            ||!strcmp(tag->key, "hasCuePoints")
>>>>>>>> -            ||!strcmp(tag->key, "hasMetadata")
>>>>>>>> -            ||!strcmp(tag->key, "hasKeyframes")
>>>>>>>> -        ){
>>>>>>>> -            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n",
>>>>>>>> tag->key);
>>>>>>>> -            continue;
>>>>>>>> -        }
>>>>>>>> -        put_amf_string(pb, tag->key);
>>>>>>>> -        avio_w8(pb, AMF_DATA_TYPE_STRING);
>>>>>>>> -        put_amf_string(pb, tag->value);
>>>>>>>> -        metadata_count++;
>>>>>>>> -    }
>>>>>>>> +
>>>>>>>> +    while (tag = av_dict_get(s->metadata, "", tag,
>>>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>>>> +    while (tag = av_dict_get(flv->meta_dict, "", tag,
>>>>>>>> AV_DICT_IGNORE_SUFFIX))
>>>>>>>> +        write_user_metadata_tag(s, tag, pb, &metadata_count);
>>>>>>>>
>>>>>>>>         if (write_duration_filesize) {
>>>>>>>>             put_amf_string(pb, "filesize");
>>>>>>>> @@ -718,6 +783,7 @@ static int flv_write_header(AVFormatContext *s)
>>>>>>>>         if (flv->flags & FLV_NO_METADATA) {
>>>>>>>>             pb->seekable = 0;
>>>>>>>>         } else {
>>>>>>>> +        read_metadata_from_file(s, 0, 1);
>>>>>>>>             write_metadata(s, 0);
>>>>>>>>         }
>>>>>>>>
>>>>>>>> @@ -880,6 +946,7 @@ static int flv_write_packet(AVFormatContext *s,
>>>>>>>> AVPacket *pkt)
>>>>>>>>         if (meta_upd_flag ||
>>>>>>>>             (flv->meta_period == FLV_META_AT_KF        &&
>>>>>>>> par->codec_type
>>>>>>>> == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
>>>>>>>>             (flv->meta_period == FLV_META_EVERY_PACKET &&
>>>>>>>> par->codec_type
>>>>>>>> == AVMEDIA_TYPE_VIDEO )) {
>>>>>>>> +        read_metadata_from_file(s, ts, 0);
>>>>>>>>             write_metadata(s, ts);
>>>>>>>>             if (meta_upd_flag)
>>>>>>>>                 s->event_flags &=
>>>>>>>> ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
>>>>>>>> @@ -1053,6 +1120,11 @@ static void flv_deinit(AVFormatContext *s)
>>>>>>>>         }
>>>>>>>>         flv->filepositions = flv->head_filepositions = NULL;
>>>>>>>>         flv->filepositions_count = 0;
>>>>>>>> +
>>>>>>>> +    if (flv->meta_dict)
>>>>>>>> +        av_dict_free(&flv->meta_dict);
>>>>>>>> +
>>>>>>>> +    avformat_close_input(&flv->meta_ctx);
>>>>>>>>     }
>>>>>>>>
>>>>>>>>     static const AVOption options[] = {
>>>>>>>> @@ -1066,6 +1138,7 @@ static const AVOption options[] = {
>>>>>>>>         { "at_start",      "only once at start",        0,
>>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN,
>>>>>>>> INT_MAX,
>>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>>>         { "at_keyframes",  "with every video keyframe", 0,
>>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF}, INT_MIN, INT_MAX,
>>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>>>         { "every_packet",  "with every video packet",   0,
>>>>>>>> AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET}, INT_MIN,
>>>>>>>> INT_MAX,
>>>>>>>> AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
>>>>>>>> +    { "meta_filename", "ffmetadata file to import metadata",
>>>>>>>> offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str =
>>>>>>>> NULL},
>>>>>>>> 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
>>>>>>>>         { NULL },
>>>>>>>>     };
>>>>>>>>
>>>>>>> What does this achieve that can't be achieved by other means
>>>>>>> (namely by
>>>>>>> an API-user updating the relevant metadata dictionaries)?
>>>>>> This option together with meta_period is for CLI users. Similar to
>>>>>> drawtext's textfile + reload.
>>>>>> It's been used in production for over a year at a client. Think it
>>>>>> will
>>>>>> be broadly useful.
>>>>> Not everything that is useful now is correct on long term.
>>>>>
>>>>> Muxer specific metadata handling by reading from files is big hack.
>>>> What would you suggest?
>>> There should be way to handle this generically see Anton's patch that
>>> adds new AVOption type.
>> Link?
>
> So, I only found his patch to load filter options from files, but that's
> only inside filtergraphs.
>
> Anyway, this option is for dynamic metadata. Which means that even
> meta_period would have to be a generic lavf option.
> But most muxers don't emit metadata post-header.  In fact, flvenc is the
> only muxer that makes use of
> AV***_EVENT_FLAG_METADATA_UPDATED.  So, we will need a new AVFMT flag to
> avoid reloading of file metadata in all other cases.
> Seems overkill for something useful in one muxer.
>

Please ask Anton for opinion about this one.


> Regards,
> Gyan
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Anton Khirnov Feb. 5, 2023, 10:37 a.m. UTC | #9
Quoting Gyan Doshi (2023-02-04 11:11:12)
> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
> > What does this achieve that can't be achieved by other means (namely by
> > an API-user updating the relevant metadata dictionaries)?
> This option together with meta_period is for CLI users.

Options for CLI users should be in the CLI, not the libraries.
Gyan Doshi Feb. 5, 2023, 11:05 a.m. UTC | #10
On 2023-02-05 04:07 pm, Anton Khirnov wrote:
> Quoting Gyan Doshi (2023-02-04 11:11:12)
>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>> What does this achieve that can't be achieved by other means (namely by
>>> an API-user updating the relevant metadata dictionaries)?
>> This option together with meta_period is for CLI users.
> Options for CLI users should be in the CLI, not the libraries.

Do I then add and use a AVFMT flag to avoid recurring metadata updates 
for all other muxers?

Regards,
Gyan
Gyan Doshi Feb. 7, 2023, 8:57 a.m. UTC | #11
On 2023-02-05 04:35 pm, Gyan Doshi wrote:
>
>
> On 2023-02-05 04:07 pm, Anton Khirnov wrote:
>> Quoting Gyan Doshi (2023-02-04 11:11:12)
>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>> What does this achieve that can't be achieved by other means 
>>>> (namely by
>>>> an API-user updating the relevant metadata dictionaries)?
>>> This option together with meta_period is for CLI users.
>> Options for CLI users should be in the CLI, not the libraries.
>
> Do I then add and use a AVFMT flag to avoid recurring metadata updates 
> for all other muxers?

To add to this, it's not exactly a 'CLI 'option. It allows CLI users the 
same feature that API users can manually undertake,
similar to the existence of the tee muxer itself which API users don't need.

But I'll see how this can fit inside fftools, though lavf/mux might be a 
better place.

Regards,
Gyan
Anton Khirnov Feb. 9, 2023, 3:45 p.m. UTC | #12
Quoting Gyan Doshi (2023-02-05 12:05:19)
> 
> 
> On 2023-02-05 04:07 pm, Anton Khirnov wrote:
> > Quoting Gyan Doshi (2023-02-04 11:11:12)
> >> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
> >>> What does this achieve that can't be achieved by other means (namely by
> >>> an API-user updating the relevant metadata dictionaries)?
> >> This option together with meta_period is for CLI users.
> > Options for CLI users should be in the CLI, not the libraries.
> 
> Do I then add and use a AVFMT flag to avoid recurring metadata updates 
> for all other muxers?

I don't follow, why should there be a lavf flag for an ffmpeg CLI
option?
Gyan Doshi Feb. 9, 2023, 4:14 p.m. UTC | #13
On 2023-02-09 09:15 pm, Anton Khirnov wrote:
> Quoting Gyan Doshi (2023-02-05 12:05:19)
>>
>> On 2023-02-05 04:07 pm, Anton Khirnov wrote:
>>> Quoting Gyan Doshi (2023-02-04 11:11:12)
>>>> On 2023-02-03 09:04 pm, Andreas Rheinhardt wrote:
>>>>> What does this achieve that can't be achieved by other means (namely by
>>>>> an API-user updating the relevant metadata dictionaries)?
>>>> This option together with meta_period is for CLI users.
>>> Options for CLI users should be in the CLI, not the libraries.
>> Do I then add and use a AVFMT flag to avoid recurring metadata updates
>> for all other muxers?
> I don't follow, why should there be a lavf flag for an ffmpeg CLI
> option?

The meta period option will control how frequently the metadata file is 
read and the muxer ctx dict updated in ffttools write_packet.
Only one muxer - flvenc - can be triggered to recheck the dict in its 
.write_packet, so it seems wasteful to carry out those ops generally.
The AVFMT flag can guard that.

Regards,
Gyan
diff mbox series

Patch

diff --git a/configure b/configure
index 9d78a244a3..de371632c4 100755
--- a/configure
+++ b/configure
@@ -3433,7 +3433,7 @@  eac3_demuxer_select="ac3_parser"
 f4v_muxer_select="mov_muxer"
 fifo_muxer_deps="threads"
 flac_demuxer_select="flac_parser"
-flv_muxer_select="aac_adtstoasc_bsf"
+flv_muxer_select="aac_adtstoasc_bsf ffmetadata_demuxer"
 gxf_muxer_select="pcm_rechunk_bsf"
 hds_muxer_select="flv_muxer"
 hls_demuxer_select="adts_header ac3_parser"
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 02ecddf186..000c92b2a7 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -555,6 +555,9 @@  With every video packet.
 @end table
 Note that metadata will always be re-emitted if a metadata update event is signalled.
 
+@item meta_filename
+Specify a ffmetadata file from which to load metadata.
+
 @end table
 
 @anchor{framecrc}
diff --git a/libavformat/flvenc.c b/libavformat/flvenc.c
index d1c7a493d1..1332b18b41 100644
--- a/libavformat/flvenc.c
+++ b/libavformat/flvenc.c
@@ -122,6 +122,10 @@  typedef struct FLVContext {
     double framerate;
     AVCodecParameters *data_par;
 
+    char *meta_filename;
+    AVFormatContext *meta_ctx;
+    AVDictionary *meta_dict;
+
     int flags;
     int meta_period;
 } FLVContext;
@@ -277,6 +281,92 @@  static void put_amf_bool(AVIOContext *pb, int b)
     avio_w8(pb, !!b);
 }
 
+static int read_metadata_from_file(AVFormatContext *s, unsigned int ts, int header)
+{
+    FLVContext *flv = s->priv_data;
+    const AVInputFormat *meta_format = NULL;
+    AVDictionary *current_meta_dict = NULL;
+    float timestamp = ts/1000.f;
+    int ret;
+
+    if (!flv->meta_filename)
+        return 0;
+
+    meta_format = av_find_input_format("ffmetadata");
+    if (!meta_format) {
+        av_log(s, AV_LOG_ERROR, "ffmetadata demuxer not found.\n");
+        return AVERROR(ENOSYS);
+    }
+
+    avformat_close_input(&flv->meta_ctx);
+
+    ret = avformat_open_input(&flv->meta_ctx, flv->meta_filename, meta_format, NULL);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to read metadata from file %s t: %f.", flv->meta_filename, timestamp);
+        if (flv->meta_dict)
+            av_log(s, AV_LOG_ERROR, " Continuing with old metadata.");
+        av_log(s, AV_LOG_ERROR, "\n");
+        return ret;
+    }
+
+    if (flv->meta_dict) {
+        av_dict_copy(&current_meta_dict, flv->meta_dict, 0);
+        av_dict_free(&flv->meta_dict);
+    }
+
+    ret = av_dict_copy(&flv->meta_dict, flv->meta_ctx->metadata, 0);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Could not transfer metadata from %s at %f seconds. Continuing with old metadata.\n", flv->meta_filename, timestamp);
+        av_dict_free(&flv->meta_dict);
+        av_dict_copy(&flv->meta_dict, current_meta_dict, 0);
+        av_dict_free(&current_meta_dict);
+        return ret;
+    }
+
+    av_log(s, AV_LOG_VERBOSE, "Metadata from file %s updated %s at %f seconds.\n", flv->meta_filename, header ? "in header" : "in video packet", timestamp);
+    av_dict_free(&current_meta_dict);
+    avformat_close_input(&flv->meta_ctx);
+
+    return 0;
+}
+
+static int write_user_metadata_tag(AVFormatContext *s, AVDictionaryEntry *tag, AVIOContext *pb, int *metadata_count)
+{
+
+    av_log(s, AV_LOG_DEBUG, "Writing tag %s with value %s count: %d\n", tag->key, tag->value, *metadata_count);
+
+    if(   !strcmp(tag->key, "width")
+        ||!strcmp(tag->key, "height")
+        ||!strcmp(tag->key, "videodatarate")
+        ||!strcmp(tag->key, "framerate")
+        ||!strcmp(tag->key, "videocodecid")
+        ||!strcmp(tag->key, "audiodatarate")
+        ||!strcmp(tag->key, "audiosamplerate")
+        ||!strcmp(tag->key, "audiosamplesize")
+        ||!strcmp(tag->key, "stereo")
+        ||!strcmp(tag->key, "audiocodecid")
+        ||!strcmp(tag->key, "duration")
+        ||!strcmp(tag->key, "onMetaData")
+        ||!strcmp(tag->key, "datasize")
+        ||!strcmp(tag->key, "lasttimestamp")
+        ||!strcmp(tag->key, "totalframes")
+        ||!strcmp(tag->key, "hasAudio")
+        ||!strcmp(tag->key, "hasVideo")
+        ||!strcmp(tag->key, "hasCuePoints")
+        ||!strcmp(tag->key, "hasMetadata")
+        ||!strcmp(tag->key, "hasKeyframes")
+    ){
+        av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
+        return AVERROR(EINVAL);
+    }
+    put_amf_string(pb, tag->key);
+    avio_w8(pb, AMF_DATA_TYPE_STRING);
+    put_amf_string(pb, tag->value);
+    (*metadata_count)++;
+
+    return 0;
+}
+
 static void write_metadata(AVFormatContext *s, unsigned int ts)
 {
     AVIOContext *pb = s->pb;
@@ -360,36 +450,11 @@  static void write_metadata(AVFormatContext *s, unsigned int ts)
     }
 
     ff_standardize_creation_time(s);
-    while ((tag = av_dict_iterate(s->metadata, tag))) {
-        if(   !strcmp(tag->key, "width")
-            ||!strcmp(tag->key, "height")
-            ||!strcmp(tag->key, "videodatarate")
-            ||!strcmp(tag->key, "framerate")
-            ||!strcmp(tag->key, "videocodecid")
-            ||!strcmp(tag->key, "audiodatarate")
-            ||!strcmp(tag->key, "audiosamplerate")
-            ||!strcmp(tag->key, "audiosamplesize")
-            ||!strcmp(tag->key, "stereo")
-            ||!strcmp(tag->key, "audiocodecid")
-            ||!strcmp(tag->key, "duration")
-            ||!strcmp(tag->key, "onMetaData")
-            ||!strcmp(tag->key, "datasize")
-            ||!strcmp(tag->key, "lasttimestamp")
-            ||!strcmp(tag->key, "totalframes")
-            ||!strcmp(tag->key, "hasAudio")
-            ||!strcmp(tag->key, "hasVideo")
-            ||!strcmp(tag->key, "hasCuePoints")
-            ||!strcmp(tag->key, "hasMetadata")
-            ||!strcmp(tag->key, "hasKeyframes")
-        ){
-            av_log(s, AV_LOG_DEBUG, "Ignoring metadata for %s\n", tag->key);
-            continue;
-        }
-        put_amf_string(pb, tag->key);
-        avio_w8(pb, AMF_DATA_TYPE_STRING);
-        put_amf_string(pb, tag->value);
-        metadata_count++;
-    }
+
+    while (tag = av_dict_get(s->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))
+        write_user_metadata_tag(s, tag, pb, &metadata_count);
+    while (tag = av_dict_get(flv->meta_dict, "", tag, AV_DICT_IGNORE_SUFFIX))
+        write_user_metadata_tag(s, tag, pb, &metadata_count);
 
     if (write_duration_filesize) {
         put_amf_string(pb, "filesize");
@@ -718,6 +783,7 @@  static int flv_write_header(AVFormatContext *s)
     if (flv->flags & FLV_NO_METADATA) {
         pb->seekable = 0;
     } else {
+        read_metadata_from_file(s, 0, 1);
         write_metadata(s, 0);
     }
 
@@ -880,6 +946,7 @@  static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
     if (meta_upd_flag ||
         (flv->meta_period == FLV_META_AT_KF        && par->codec_type == AVMEDIA_TYPE_VIDEO && (pkt->flags & AV_PKT_FLAG_KEY)) ||
         (flv->meta_period == FLV_META_EVERY_PACKET && par->codec_type == AVMEDIA_TYPE_VIDEO )) {
+        read_metadata_from_file(s, ts, 0);
         write_metadata(s, ts);
         if (meta_upd_flag)
             s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
@@ -1053,6 +1120,11 @@  static void flv_deinit(AVFormatContext *s)
     }
     flv->filepositions = flv->head_filepositions = NULL;
     flv->filepositions_count = 0;
+
+    if (flv->meta_dict)
+        av_dict_free(&flv->meta_dict);
+
+    avformat_close_input(&flv->meta_ctx);
 }
 
 static const AVOption options[] = {
@@ -1066,6 +1138,7 @@  static const AVOption options[] = {
     { "at_start",      "only once at start",        0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_ONCE_AT_START}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
     { "at_keyframes",  "with every video keyframe", 0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_AT_KF},         INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
     { "every_packet",  "with every video packet",   0, AV_OPT_TYPE_CONST, {.i64 = FLV_META_EVERY_PACKET},  INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "meta_period" },
+    { "meta_filename", "ffmetadata file to import metadata", offsetof(FLVContext, meta_filename), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0, AV_OPT_FLAG_ENCODING_PARAM },
     { NULL },
 };