diff options
Diffstat (limited to 'libavformat/id3v2enc.c')
-rw-r--r-- | libavformat/id3v2enc.c | 218 |
1 files changed, 198 insertions, 20 deletions
diff --git a/libavformat/id3v2enc.c b/libavformat/id3v2enc.c index 3f9bd4ecd7..ffe358f019 100644 --- a/libavformat/id3v2enc.c +++ b/libavformat/id3v2enc.c @@ -1,20 +1,20 @@ /* * ID3v2 header writer * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * 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. * - * Libav is distributed in the hope that it will be useful, + * 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 Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -26,6 +26,7 @@ #include "libavutil/intreadwrite.h" #include "avformat.h" #include "avio.h" +#include "avio_internal.h" #include "id3v2.h" static void id3v2_put_size(AVIOContext *pb, int size) @@ -95,6 +96,59 @@ static int id3v2_put_ttag(ID3v2EncContext *id3, AVIOContext *avioc, const char * return len + ID3v2_HEADER_SIZE; } +/** + * Write a priv frame with owner and data. 'key' is the owner prepended with + * ID3v2_PRIV_METADATA_PREFIX. 'data' is provided as a string. Any \xXX + * (where 'X' is a valid hex digit) will be unescaped to the byte value. + */ +static int id3v2_put_priv(ID3v2EncContext *id3, AVIOContext *avioc, const char *key, const char *data) +{ + int len; + uint8_t *pb; + AVIOContext *dyn_buf; + + if (!av_strstart(key, ID3v2_PRIV_METADATA_PREFIX, &key)) { + return 0; + } + + if (avio_open_dyn_buf(&dyn_buf) < 0) + return AVERROR(ENOMEM); + + // owner + null byte. + avio_write(dyn_buf, key, strlen(key) + 1); + + while (*data) { + if (av_strstart(data, "\\x", &data)) { + if (data[0] && data[1] && av_isxdigit(data[0]) && av_isxdigit(data[1])) { + char digits[] = {data[0], data[1], 0}; + avio_w8(dyn_buf, strtol(digits, NULL, 16)); + data += 2; + } else { + ffio_free_dyn_buf(&dyn_buf); + av_log(avioc, AV_LOG_ERROR, "Invalid escape '\\x%.2s' in metadata tag '" + ID3v2_PRIV_METADATA_PREFIX "%s'.\n", data, key); + return AVERROR(EINVAL); + } + } else { + avio_write(dyn_buf, data++, 1); + } + } + + len = avio_close_dyn_buf(dyn_buf, &pb); + + avio_wb32(avioc, MKBETAG('P', 'R', 'I', 'V')); + if (id3->version == 3) + avio_wb32(avioc, len); + else + id3v2_put_size(avioc, len); + avio_wb16(avioc, 0); + avio_write(avioc, pb, len); + + av_free(pb); + + return len + ID3v2_HEADER_SIZE; +} + static int id3v2_check_write_tag(ID3v2EncContext *id3, AVIOContext *pb, AVDictionaryEntry *t, const char table[][4], enum ID3v2Encoding enc) { @@ -110,6 +164,44 @@ static int id3v2_check_write_tag(ID3v2EncContext *id3, AVIOContext *pb, AVDictio return -1; } +static void id3v2_3_metadata_split_date(AVDictionary **pm) +{ + AVDictionaryEntry *mtag = NULL; + AVDictionary *dst = NULL; + const char *key, *value; + char year[5] = {0}, day_month[5] = {0}; + int i; + + while ((mtag = av_dict_get(*pm, "", mtag, AV_DICT_IGNORE_SUFFIX))) { + key = mtag->key; + if (!av_strcasecmp(key, "date")) { + /* split date tag using "YYYY-MM-DD" format into year and month/day segments */ + value = mtag->value; + i = 0; + while (value[i] >= '0' && value[i] <= '9') i++; + if (value[i] == '\0' || value[i] == '-') { + av_strlcpy(year, value, sizeof(year)); + av_dict_set(&dst, "TYER", year, 0); + + if (value[i] == '-' && + value[i+1] >= '0' && value[i+1] <= '1' && + value[i+2] >= '0' && value[i+2] <= '9' && + value[i+3] == '-' && + value[i+4] >= '0' && value[i+4] <= '3' && + value[i+5] >= '0' && value[i+5] <= '9' && + (value[i+6] == '\0' || value[i+6] == ' ')) { + snprintf(day_month, sizeof(day_month), "%.2s%.2s", value + i + 4, value + i + 1); + av_dict_set(&dst, "TDAT", day_month, 0); + } + } else + av_dict_set(&dst, key, value, 0); + } else + av_dict_set(&dst, key, mtag->value, 0); + } + av_dict_free(pm); + *pm = dst; +} + void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, const char *magic) { @@ -124,31 +216,38 @@ void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version, avio_wb32(pb, 0); } -int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) +static int write_metadata(AVIOContext *pb, AVDictionary **metadata, + ID3v2EncContext *id3, int enc) { AVDictionaryEntry *t = NULL; - int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : - ID3v2_ENCODING_UTF8; - - ff_metadata_conv(&s->metadata, ff_id3v2_34_metadata_conv, NULL); - if (id3->version == 4) - ff_metadata_conv(&s->metadata, ff_id3v2_4_metadata_conv, NULL); + int ret; - while ((t = av_dict_get(s->metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { - int ret; + ff_metadata_conv(metadata, ff_id3v2_34_metadata_conv, NULL); + if (id3->version == 3) + id3v2_3_metadata_split_date(metadata); + else if (id3->version == 4) + ff_metadata_conv(metadata, ff_id3v2_4_metadata_conv, NULL); - if ((ret = id3v2_check_write_tag(id3, s->pb, t, ff_id3v2_tags, enc)) > 0) { + while ((t = av_dict_get(*metadata, "", t, AV_DICT_IGNORE_SUFFIX))) { + if ((ret = id3v2_check_write_tag(id3, pb, t, ff_id3v2_tags, enc)) > 0) { id3->len += ret; continue; } - if ((ret = id3v2_check_write_tag(id3, s->pb, t, id3->version == 3 ? - ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0) { + if ((ret = id3v2_check_write_tag(id3, pb, t, id3->version == 3 ? + ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0) { id3->len += ret; continue; } + if ((ret = id3v2_put_priv(id3, pb, t->key, t->value)) > 0) { + id3->len += ret; + continue; + } else if (ret < 0) { + return ret; + } + /* unknown tag, write as TXXX frame */ - if ((ret = id3v2_put_ttag(id3, s->pb, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) + if ((ret = id3v2_put_ttag(id3, pb, t->key, t->value, MKBETAG('T', 'X', 'X', 'X'), enc)) < 0) return ret; id3->len += ret; } @@ -156,6 +255,65 @@ int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) return 0; } +static int write_chapter(AVFormatContext *s, ID3v2EncContext *id3, int id, int enc) +{ + const AVRational time_base = {1, 1000}; + AVChapter *ch = s->chapters[id]; + uint8_t *dyn_buf = NULL; + AVIOContext *dyn_bc = NULL; + char name[123]; + int len, start, end, ret; + + if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0) + goto fail; + + start = av_rescale_q(ch->start, ch->time_base, time_base); + end = av_rescale_q(ch->end, ch->time_base, time_base); + + snprintf(name, 122, "ch%d", id); + id3->len += avio_put_str(dyn_bc, name); + avio_wb32(dyn_bc, start); + avio_wb32(dyn_bc, end); + avio_wb32(dyn_bc, 0xFFFFFFFFu); + avio_wb32(dyn_bc, 0xFFFFFFFFu); + + if ((ret = write_metadata(dyn_bc, &ch->metadata, id3, enc)) < 0) + goto fail; + + len = avio_close_dyn_buf(dyn_bc, &dyn_buf); + id3->len += 16 + ID3v2_HEADER_SIZE; + + avio_wb32(s->pb, MKBETAG('C', 'H', 'A', 'P')); + avio_wb32(s->pb, len); + avio_wb16(s->pb, 0); + avio_write(s->pb, dyn_buf, len); + +fail: + if (dyn_bc && !dyn_buf) + avio_close_dyn_buf(dyn_bc, &dyn_buf); + av_freep(&dyn_buf); + + return ret; +} + +int ff_id3v2_write_metadata(AVFormatContext *s, ID3v2EncContext *id3) +{ + int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM : + ID3v2_ENCODING_UTF8; + int i, ret; + + ff_standardize_creation_time(s); + if ((ret = write_metadata(s->pb, &s->metadata, id3, enc)) < 0) + return ret; + + for (i = 0; i < s->nb_chapters; i++) { + if ((ret = write_chapter(s, id3, i, enc)) < 0) + return ret; + } + + return 0; +} + int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) { AVStream *st = s->streams[pkt->stream_index]; @@ -196,6 +354,10 @@ int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) if ((e = av_dict_get(st->metadata, "title", NULL, 0))) desc = e->value; + /* use UTF16 only for non-ASCII strings */ + if (enc == ID3v2_ENCODING_UTF16BOM && string_is_ascii(desc)) + enc = ID3v2_ENCODING_ISO8859; + /* start writing */ if (avio_open_dyn_buf(&dyn_buf) < 0) return AVERROR(ENOMEM); @@ -221,9 +383,25 @@ int ff_id3v2_write_apic(AVFormatContext *s, ID3v2EncContext *id3, AVPacket *pkt) return 0; } -void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb) +void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb, + int padding_bytes) { - int64_t cur_pos = avio_tell(pb); + int64_t cur_pos; + + if (padding_bytes < 0) + padding_bytes = 10; + + /* The ID3v2.3 specification states that 28 bits are used to represent the + * size of the whole tag. Therefore the current size of the tag needs to be + * subtracted from the upper limit of 2^28-1 to clip the value correctly. */ + /* The minimum of 10 is an arbitrary amount of padding at the end of the tag + * to fix cover art display with some software such as iTunes, Traktor, + * Serato, Torq. */ + padding_bytes = av_clip(padding_bytes, 10, 268435455 - id3->len); + ffio_fill(pb, 0, padding_bytes); + id3->len += padding_bytes; + + cur_pos = avio_tell(pb); avio_seek(pb, id3->size_pos, SEEK_SET); id3v2_put_size(pb, id3->len); avio_seek(pb, cur_pos, SEEK_SET); @@ -238,7 +416,7 @@ int ff_id3v2_write_simple(struct AVFormatContext *s, int id3v2_version, ff_id3v2_start(&id3, s->pb, id3v2_version, magic); if ((ret = ff_id3v2_write_metadata(s, &id3)) < 0) return ret; - ff_id3v2_finish(&id3, s->pb); + ff_id3v2_finish(&id3, s->pb, s->metadata_header_padding); return 0; } |