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

Paul B Mahol onemda at gmail.com
Sat Feb 4 12:32:49 EET 2023


On 2/4/23, Gyan Doshi <ffmpeg at gyani.pro> wrote:
>
>
> On 2023-02-04 03:46 pm, Paul B Mahol wrote:
>> On 2/4/23, Gyan Doshi <ffmpeg at 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.
>>>>>
>>>>> + at 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.


More information about the ffmpeg-devel mailing list