diff --git a/libavcodec/Makefile b/libavcodec/Makefile index e4217d6af4..d7a1b4ba8e 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -14,6 +14,7 @@ OBJS = allcodecs.o \ faanidct.o \ imgconvert.o \ jrevdct.o \ + metadata.o \ opt.o \ parser.o \ raw.o \ diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index e45f7d6dfb..7038d2d2c4 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -400,6 +400,51 @@ enum SampleFormat { */ #define FF_MIN_BUFFER_SIZE 16384 + +/* + * public Metadata API. + * Important concepts, to keep in mind + * 1. keys are unique, there are never 2 tags with equal keys, this is also + * meant semantically that is a demuxer should not knowingly produce + * several keys that are litterally different but semantically identical, + * like key=Author5, key=Author6. + * All authors have to be placed in the same tag for the case of Authors. + * 2. Metadata is flat, there are no subtags, if you for whatever obscene + * reason want to store the email address of the child of producer alice + * and actor bob, that could have key=alice_and_bobs_childs_email_address. + * 3. A tag whichs value is translated has the ISO 639 3-letter language code + * with a '-' between appended. So for example Author-ger=Michael, Author-eng=Mike + * the original/default language is in the unqualified "Author" + * A demuxer should set a default if it sets any translated tag. + */ + +#define AV_METADATA_IGNORE_CASE 1 +#define AV_METADATA_IGNORE_SUFFIX 2 + +typedef struct { + char *key; + char *value; +}AVMetaDataTag; + +struct AVMetaData; + +/** + * gets a metadata element with matching key. + * @param prev set to the previous matching element to find the next. + * @param flags allows case as well as suffix insensitive comparissions. + * @return found tag or NULL, changing key or value leads to undefined behavior. + */ +AVMetaDataTag * +av_metadata_get(struct AVMetaData *m, const char *key, const AVMetaDataTag *prev, int flags); + +/** + * sets the given tag in m, overwriting an existing tag. + * @param tag tag to add to m, key and value will be av_strduped. + * @return >= 0 if success otherwise error code that is <0. + */ +int av_metadata_set(struct AVMetaData **m, AVMetaDataTag tag); + + /** * motion estimation type. */ diff --git a/libavcodec/metadata.c b/libavcodec/metadata.c new file mode 100644 index 0000000000..d223d7c0d9 --- /dev/null +++ b/libavcodec/metadata.c @@ -0,0 +1,75 @@ +/* + * copyright (c) 2009 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "metadata.h" + +AVMetaDataTag * +av_metadata_get(struct AVMetaData *m, const char *key, const AVMetaDataTag *prev, int flags) +{ + unsigned int i, j; + + if(!m) + return NULL; + + if(prev) i= prev - m->elems + 1; + else i= 0; + + for(; icount; i++){ + const char *s= m->elems[i].key; + if(flags & AV_METADATA_IGNORE_CASE) for(j=0; toupper(s[j]) == toupper(key[j]) && key[j]; j++); + else for(j=0; s[j] == key[j] && key[j]; j++); + if(key[j]) + continue; + if(s[j] && !(flags & AV_METADATA_IGNORE_SUFFIX)) + continue; + return &m->elems[i]; + } + return NULL; +} + +int av_metadata_set(struct AVMetaData **pm, AVMetaDataTag elem) +{ + struct AVMetaData *m= *pm; + AVMetaDataTag *tag= av_metadata_get(m, elem.key, NULL, 0); + + if(!m) + m=*pm= av_mallocz(sizeof(*m)); + + if(tag){ + av_free(tag->value); + av_free(tag->key); + *tag= m->elems[--m->count]; + }else{ + AVMetaDataTag *tmp= av_realloc(m->elems, (m->count+1) * sizeof(*m->elems)); + if(tmp){ + m->elems= tmp; + }else + return AVERROR(ENOMEM); + } + if(elem.value){ + elem.key = av_strdup(elem.key ); + elem.value= av_strdup(elem.value); + m->elems[m->count++]= elem; + } + if(!m->count) + av_freep(pm); + + return 0; +} diff --git a/libavcodec/metadata.h b/libavcodec/metadata.h new file mode 100644 index 0000000000..0ab39cba26 --- /dev/null +++ b/libavcodec/metadata.h @@ -0,0 +1,38 @@ +/* + * copyright (c) 2009 Michael Niedermayer + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_METADATA_H +#define AVCODEC_METADATA_H + +/** + * @file metadata.h + * internal metadata API header + * see avcodec.h or the public API! + */ + + +#include "avcodec.h" + +struct AVMetaData{ + int count; + AVMetaDataTag *elems; +}; + +#endif /* AVCODEC_METADATA_H */ diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 3f4271ae5f..46e692452e 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -608,6 +608,8 @@ typedef struct AVFormatContext { struct AVPacketList *raw_packet_buffer_end; struct AVPacketList *packet_buffer_end; + + struct AVMetaData *meta_data; } AVFormatContext; typedef struct AVPacketList { diff --git a/libavformat/avidec.c b/libavformat/avidec.c index 83c01a9826..ebb1a4c674 100644 --- a/libavformat/avidec.c +++ b/libavformat/avidec.c @@ -216,13 +216,17 @@ static void clean_index(AVFormatContext *s){ } } -static int avi_read_tag(ByteIOContext *pb, char *buf, int maxlen, unsigned int size) +static int avi_read_tag(AVFormatContext *s, const char *key, unsigned int size) { + ByteIOContext *pb = s->pb; + uint8_t value[1024]; + int64_t i = url_ftell(pb); size += (size & 1); - get_strz(pb, buf, maxlen); + get_strz(pb, value, sizeof(value)); url_fseek(pb, i+size, SEEK_SET); - return 0; + + return av_metadata_set(&s->meta_data, (const AVMetaDataTag){key, value}); } static int avi_read_header(AVFormatContext *s, AVFormatParameters *ap) @@ -235,7 +239,6 @@ static int avi_read_header(AVFormatContext *s, AVFormatParameters *ap) int i; AVStream *st; AVIStream *ast = NULL; - char str_track[4]; int avih_width=0, avih_height=0; int amv_file_format=0; @@ -561,26 +564,25 @@ static int avi_read_header(AVFormatContext *s, AVFormatParameters *ap) url_fseek(pb, size, SEEK_CUR); break; case MKTAG('I', 'N', 'A', 'M'): - avi_read_tag(pb, s->title, sizeof(s->title), size); + avi_read_tag(s, "Title", size); break; case MKTAG('I', 'A', 'R', 'T'): - avi_read_tag(pb, s->author, sizeof(s->author), size); + avi_read_tag(s, "Artist", size); break; case MKTAG('I', 'C', 'O', 'P'): - avi_read_tag(pb, s->copyright, sizeof(s->copyright), size); + avi_read_tag(s, "Copyright", size); break; case MKTAG('I', 'C', 'M', 'T'): - avi_read_tag(pb, s->comment, sizeof(s->comment), size); + avi_read_tag(s, "Comment", size); break; case MKTAG('I', 'G', 'N', 'R'): - avi_read_tag(pb, s->genre, sizeof(s->genre), size); + avi_read_tag(s, "Genre", size); break; case MKTAG('I', 'P', 'R', 'D'): - avi_read_tag(pb, s->album, sizeof(s->album), size); + avi_read_tag(s, "Album", size); break; case MKTAG('I', 'P', 'R', 'T'): - avi_read_tag(pb, str_track, sizeof(str_track), size); - sscanf(str_track, "%d", &s->track); + avi_read_tag(s, "Track", size); break; default: if(size > 1000000){ diff --git a/libavformat/avienc.c b/libavformat/avienc.c index a672e8d85c..4ae608f780 100644 --- a/libavformat/avienc.c +++ b/libavformat/avienc.c @@ -103,6 +103,15 @@ static void avi_write_info_tag(ByteIOContext *pb, const char *tag, const char *s } } +static void avi_write_info_tag2(AVFormatContext *s, const char *fourcc, const char *key1, const char *key2) +{ + AVMetaDataTag *tag= av_metadata_get(s->meta_data, key1, NULL, AV_METADATA_IGNORE_CASE); + if(!tag && key2) + tag= av_metadata_get(s->meta_data, key2, NULL, AV_METADATA_IGNORE_CASE); + if(tag) + avi_write_info_tag(s->pb, fourcc, tag->value); +} + static int avi_write_counters(AVFormatContext* s, int riff_id) { ByteIOContext *pb = s->pb; @@ -332,17 +341,13 @@ static int avi_write_header(AVFormatContext *s) list2 = start_tag(pb, "LIST"); put_tag(pb, "INFO"); - avi_write_info_tag(pb, "INAM", s->title); - avi_write_info_tag(pb, "IART", s->author); - avi_write_info_tag(pb, "ICOP", s->copyright); - avi_write_info_tag(pb, "ICMT", s->comment); - avi_write_info_tag(pb, "IPRD", s->album); - avi_write_info_tag(pb, "IGNR", s->genre); - if (s->track) { - char str_track[4]; - snprintf(str_track, 4, "%d", s->track); - avi_write_info_tag(pb, "IPRT", str_track); - } + avi_write_info_tag2(s, "INAM", "Title", NULL); + avi_write_info_tag2(s, "IART", "Artist", "Author"); + avi_write_info_tag2(s, "ICOP", "Copyright", NULL); + avi_write_info_tag2(s, "ICMT", "Comment", NULL); + avi_write_info_tag2(s, "IPRD", "Album", NULL); + avi_write_info_tag2(s, "IGNR", "Genre", NULL); + avi_write_info_tag2(s, "IPRT", "Track", NULL); if(!(s->streams[0]->codec->flags & CODEC_FLAG_BITEXACT)) avi_write_info_tag(pb, "ISFT", LIBAVFORMAT_IDENT); end_tag(pb, list2); diff --git a/libavformat/utils.c b/libavformat/utils.c index 80171ca903..9bee5cd7e8 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -21,6 +21,7 @@ #include "avformat.h" #include "internal.h" #include "libavcodec/opt.h" +#include "libavcodec/metadata.h" #include "libavutil/avstring.h" #include "riff.h" #include @@ -2305,6 +2306,14 @@ void av_close_input_stream(AVFormatContext *s) av_free(s->chapters[s->nb_chapters]); } av_freep(&s->chapters); + if(s->meta_data){ + while(s->meta_data->count--){ + av_freep(&s->meta_data->elems[s->meta_data->count].key); + av_freep(&s->meta_data->elems[s->meta_data->count].value); + } + av_freep(&s->meta_data->elems); + } + av_freep(&s->meta_data); av_free(s); }