Commit | Line | Data |
---|---|---|
191e34cd KS |
1 | /* |
2 | * APE tag handling | |
3 | * Copyright (c) 2007 Benjamin Zores <ben@geexbox.org> | |
4 | * based upon libdemac from Dave Chapman. | |
5 | * | |
2912e87a | 6 | * This file is part of Libav. |
191e34cd | 7 | * |
2912e87a | 8 | * Libav is free software; you can redistribute it and/or |
191e34cd KS |
9 | * modify it under the terms of the GNU Lesser General Public |
10 | * License as published by the Free Software Foundation; either | |
11 | * version 2.1 of the License, or (at your option) any later version. | |
12 | * | |
2912e87a | 13 | * Libav is distributed in the hope that it will be useful, |
191e34cd KS |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * Lesser General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU Lesser General Public | |
2912e87a | 19 | * License along with Libav; if not, write to the Free Software |
191e34cd KS |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | */ | |
22 | ||
d92024f1 DB |
23 | #include <inttypes.h> |
24 | ||
191e34cd | 25 | #include "libavutil/intreadwrite.h" |
d2d67e42 | 26 | #include "libavutil/dict.h" |
191e34cd | 27 | #include "avformat.h" |
88de0c79 | 28 | #include "avio_internal.h" |
c53ffb2c | 29 | #include "apetag.h" |
02a951b9 | 30 | #include "internal.h" |
191e34cd | 31 | |
191e34cd KS |
32 | #define APE_TAG_VERSION 2000 |
33 | #define APE_TAG_FOOTER_BYTES 32 | |
34 | #define APE_TAG_FLAG_CONTAINS_HEADER (1 << 31) | |
88de0c79 | 35 | #define APE_TAG_FLAG_CONTAINS_FOOTER (1 << 30) |
191e34cd | 36 | #define APE_TAG_FLAG_IS_HEADER (1 << 29) |
f02edc4c | 37 | #define APE_TAG_FLAG_IS_BINARY (1 << 1) |
191e34cd KS |
38 | |
39 | static int ape_tag_read_field(AVFormatContext *s) | |
40 | { | |
ae628ec1 | 41 | AVIOContext *pb = s->pb; |
12ad6671 | 42 | uint8_t key[1024], *value; |
e1a57cbb | 43 | int64_t size, flags; |
18a49f11 | 44 | int i, c; |
191e34cd | 45 | |
b7effd4e | 46 | size = avio_rl32(pb); /* field size */ |
f02edc4c | 47 | flags = avio_rl32(pb); /* field flags */ |
191e34cd | 48 | for (i = 0; i < sizeof(key) - 1; i++) { |
b7effd4e | 49 | c = avio_r8(pb); |
191e34cd KS |
50 | if (c < 0x20 || c > 0x7E) |
51 | break; | |
52 | else | |
53 | key[i] = c; | |
54 | } | |
55 | key[i] = 0; | |
56 | if (c != 0) { | |
57 | av_log(s, AV_LOG_WARNING, "Invalid APE tag key '%s'.\n", key); | |
58 | return -1; | |
59 | } | |
c5560e72 KBA |
60 | if (size > INT32_MAX - FF_INPUT_BUFFER_PADDING_SIZE) { |
61 | av_log(s, AV_LOG_ERROR, "APE tag size too large.\n"); | |
62 | return AVERROR_INVALIDDATA; | |
63 | } | |
f02edc4c PM |
64 | if (flags & APE_TAG_FLAG_IS_BINARY) { |
65 | uint8_t filename[1024]; | |
36ef5369 | 66 | enum AVCodecID id; |
f02edc4c PM |
67 | AVStream *st = avformat_new_stream(s, NULL); |
68 | if (!st) | |
69 | return AVERROR(ENOMEM); | |
b72767df AK |
70 | |
71 | size -= avio_get_str(pb, size, filename, sizeof(filename)); | |
72 | if (size <= 0) { | |
73 | av_log(s, AV_LOG_WARNING, "Skipping binary tag '%s'.\n", key); | |
74 | return 0; | |
75 | } | |
02a951b9 AK |
76 | |
77 | av_dict_set(&st->metadata, key, filename, 0); | |
78 | ||
36ef5369 | 79 | if ((id = ff_guess_image2_codec(filename)) != AV_CODEC_ID_NONE) { |
02a951b9 AK |
80 | AVPacket pkt; |
81 | int ret; | |
82 | ||
83 | ret = av_get_packet(s->pb, &pkt, size); | |
84 | if (ret < 0) { | |
85 | av_log(s, AV_LOG_ERROR, "Error reading cover art.\n"); | |
86 | return ret; | |
87 | } | |
88 | ||
89 | st->disposition |= AV_DISPOSITION_ATTACHED_PIC; | |
90 | st->codec->codec_type = AVMEDIA_TYPE_VIDEO; | |
91 | st->codec->codec_id = id; | |
92 | ||
93 | st->attached_pic = pkt; | |
94 | st->attached_pic.stream_index = st->index; | |
95 | st->attached_pic.flags |= AV_PKT_FLAG_KEY; | |
96 | } else { | |
728d2afa AK |
97 | st->codec->extradata = av_malloc(size + FF_INPUT_BUFFER_PADDING_SIZE); |
98 | if (!st->codec->extradata) | |
99 | return AVERROR(ENOMEM); | |
100 | if (avio_read(pb, st->codec->extradata, size) != size) { | |
101 | av_freep(&st->codec->extradata); | |
102 | return AVERROR(EIO); | |
103 | } | |
104 | st->codec->extradata_size = size; | |
105 | st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; | |
02a951b9 | 106 | } |
f02edc4c PM |
107 | } else { |
108 | value = av_malloc(size+1); | |
109 | if (!value) | |
110 | return AVERROR(ENOMEM); | |
111 | c = avio_read(pb, value, size); | |
14c98973 PM |
112 | if (c < 0) { |
113 | av_free(value); | |
6d110570 | 114 | return c; |
14c98973 | 115 | } |
f02edc4c PM |
116 | value[c] = 0; |
117 | av_dict_set(&s->metadata, key, value, AV_DICT_DONT_STRDUP_VAL); | |
118 | } | |
191e34cd KS |
119 | return 0; |
120 | } | |
121 | ||
782e64fb | 122 | int64_t ff_ape_parse_tag(AVFormatContext *s) |
191e34cd | 123 | { |
ae628ec1 | 124 | AVIOContext *pb = s->pb; |
e816aaac | 125 | int64_t file_size = avio_size(pb); |
191e34cd KS |
126 | uint32_t val, fields, tag_bytes; |
127 | uint8_t buf[8]; | |
782e64fb | 128 | int64_t tag_start; |
191e34cd KS |
129 | int i; |
130 | ||
131 | if (file_size < APE_TAG_FOOTER_BYTES) | |
782e64fb | 132 | return 0; |
191e34cd | 133 | |
6b4aa5da | 134 | avio_seek(pb, file_size - APE_TAG_FOOTER_BYTES, SEEK_SET); |
191e34cd | 135 | |
b7effd4e | 136 | avio_read(pb, buf, 8); /* APETAGEX */ |
191e34cd | 137 | if (strncmp(buf, "APETAGEX", 8)) { |
782e64fb | 138 | return 0; |
191e34cd KS |
139 | } |
140 | ||
b7effd4e | 141 | val = avio_rl32(pb); /* APE tag version */ |
191e34cd KS |
142 | if (val > APE_TAG_VERSION) { |
143 | av_log(s, AV_LOG_ERROR, "Unsupported tag version. (>=%d)\n", APE_TAG_VERSION); | |
782e64fb | 144 | return 0; |
191e34cd KS |
145 | } |
146 | ||
b7effd4e | 147 | tag_bytes = avio_rl32(pb); /* tag size */ |
191e34cd KS |
148 | if (tag_bytes - APE_TAG_FOOTER_BYTES > (1024 * 1024 * 16)) { |
149 | av_log(s, AV_LOG_ERROR, "Tag size is way too big\n"); | |
782e64fb AK |
150 | return 0; |
151 | } | |
152 | ||
b655cfef | 153 | if (tag_bytes > file_size - APE_TAG_FOOTER_BYTES) { |
d92024f1 | 154 | av_log(s, AV_LOG_ERROR, "Invalid tag size %"PRIu32".\n", tag_bytes); |
782e64fb | 155 | return 0; |
191e34cd | 156 | } |
b655cfef | 157 | tag_start = file_size - tag_bytes - APE_TAG_FOOTER_BYTES; |
191e34cd | 158 | |
b7effd4e | 159 | fields = avio_rl32(pb); /* number of fields */ |
191e34cd | 160 | if (fields > 65536) { |
d92024f1 | 161 | av_log(s, AV_LOG_ERROR, "Too many tag fields (%"PRIu32")\n", fields); |
782e64fb | 162 | return 0; |
191e34cd KS |
163 | } |
164 | ||
b7effd4e | 165 | val = avio_rl32(pb); /* flags */ |
191e34cd KS |
166 | if (val & APE_TAG_FLAG_IS_HEADER) { |
167 | av_log(s, AV_LOG_ERROR, "APE Tag is a header\n"); | |
92106387 | 168 | return 0; |
191e34cd KS |
169 | } |
170 | ||
6b4aa5da | 171 | avio_seek(pb, file_size - tag_bytes, SEEK_SET); |
191e34cd KS |
172 | |
173 | for (i=0; i<fields; i++) | |
174 | if (ape_tag_read_field(s) < 0) break; | |
782e64fb AK |
175 | |
176 | return tag_start; | |
191e34cd | 177 | } |
88de0c79 AK |
178 | |
179 | int ff_ape_write_tag(AVFormatContext *s) | |
180 | { | |
181 | AVDictionaryEntry *e = NULL; | |
182 | int64_t start, end; | |
183 | int size, count = 0; | |
184 | ||
185 | if (!s->pb->seekable) | |
186 | return 0; | |
187 | ||
188 | start = avio_tell(s->pb); | |
189 | ||
190 | // header | |
191 | avio_write(s->pb, "APETAGEX", 8); // id | |
192 | avio_wl32 (s->pb, APE_TAG_VERSION); // version | |
193 | avio_wl32(s->pb, 0); // reserve space for size | |
194 | avio_wl32(s->pb, 0); // reserve space for tag count | |
195 | ||
196 | // flags | |
197 | avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER | | |
198 | APE_TAG_FLAG_IS_HEADER); | |
199 | ffio_fill(s->pb, 0, 8); // reserved | |
200 | ||
201 | while ((e = av_dict_get(s->metadata, "", e, AV_DICT_IGNORE_SUFFIX))) { | |
202 | int val_len = strlen(e->value); | |
203 | ||
204 | avio_wl32(s->pb, val_len); // value length | |
205 | avio_wl32(s->pb, 0); // item flags | |
206 | avio_put_str(s->pb, e->key); // key | |
207 | avio_write(s->pb, e->value, val_len); // value | |
208 | count++; | |
209 | } | |
210 | ||
211 | size = avio_tell(s->pb) - start; | |
212 | ||
213 | // footer | |
214 | avio_write(s->pb, "APETAGEX", 8); // id | |
215 | avio_wl32 (s->pb, APE_TAG_VERSION); // version | |
216 | avio_wl32(s->pb, size); // size | |
217 | avio_wl32(s->pb, count); // tag count | |
218 | ||
219 | // flags | |
220 | avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER); | |
221 | ffio_fill(s->pb, 0, 8); // reserved | |
222 | ||
223 | // update values in the header | |
224 | end = avio_tell(s->pb); | |
225 | avio_seek(s->pb, start + 12, SEEK_SET); | |
226 | avio_wl32(s->pb, size); | |
227 | avio_wl32(s->pb, count); | |
228 | avio_seek(s->pb, end, SEEK_SET); | |
229 | ||
230 | return 0; | |
231 | } |