Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * APE tag handling | |
3 | * Copyright (c) 2007 Benjamin Zores <ben@geexbox.org> | |
4 | * based upon libdemac from Dave Chapman. | |
5 | * | |
6 | * This file is part of FFmpeg. | |
7 | * | |
8 | * FFmpeg is free software; you can redistribute it and/or | |
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 | * | |
13 | * FFmpeg is distributed in the hope that it will be useful, | |
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 | |
19 | * License along with FFmpeg; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
21 | */ | |
22 | ||
23 | #include <inttypes.h> | |
24 | ||
25 | #include "libavutil/intreadwrite.h" | |
26 | #include "libavutil/dict.h" | |
27 | #include "avformat.h" | |
28 | #include "avio_internal.h" | |
29 | #include "apetag.h" | |
30 | #include "internal.h" | |
31 | ||
32 | #define APE_TAG_FLAG_CONTAINS_HEADER (1 << 31) | |
33 | #define APE_TAG_FLAG_CONTAINS_FOOTER (1 << 30) | |
34 | #define APE_TAG_FLAG_IS_HEADER (1 << 29) | |
35 | #define APE_TAG_FLAG_IS_BINARY (1 << 1) | |
36 | ||
37 | static int ape_tag_read_field(AVFormatContext *s) | |
38 | { | |
39 | AVIOContext *pb = s->pb; | |
40 | uint8_t key[1024], *value; | |
41 | uint32_t size, flags; | |
42 | int i, c; | |
43 | ||
44 | size = avio_rl32(pb); /* field size */ | |
45 | flags = avio_rl32(pb); /* field flags */ | |
46 | for (i = 0; i < sizeof(key) - 1; i++) { | |
47 | c = avio_r8(pb); | |
48 | if (c < 0x20 || c > 0x7E) | |
49 | break; | |
50 | else | |
51 | key[i] = c; | |
52 | } | |
53 | key[i] = 0; | |
54 | if (c != 0) { | |
55 | av_log(s, AV_LOG_WARNING, "Invalid APE tag key '%s'.\n", key); | |
56 | return -1; | |
57 | } | |
58 | if (size > INT32_MAX - FF_INPUT_BUFFER_PADDING_SIZE) { | |
59 | av_log(s, AV_LOG_ERROR, "APE tag size too large.\n"); | |
60 | return AVERROR_INVALIDDATA; | |
61 | } | |
62 | if (flags & APE_TAG_FLAG_IS_BINARY) { | |
63 | uint8_t filename[1024]; | |
64 | enum AVCodecID id; | |
65 | AVStream *st = avformat_new_stream(s, NULL); | |
66 | if (!st) | |
67 | return AVERROR(ENOMEM); | |
68 | ||
69 | size -= avio_get_str(pb, size, filename, sizeof(filename)); | |
70 | if (size <= 0) { | |
71 | av_log(s, AV_LOG_WARNING, "Skipping binary tag '%s'.\n", key); | |
72 | return 0; | |
73 | } | |
74 | ||
75 | av_dict_set(&st->metadata, key, filename, 0); | |
76 | ||
77 | if ((id = ff_guess_image2_codec(filename)) != AV_CODEC_ID_NONE) { | |
78 | AVPacket pkt; | |
79 | int ret; | |
80 | ||
81 | ret = av_get_packet(s->pb, &pkt, size); | |
82 | if (ret < 0) { | |
83 | av_log(s, AV_LOG_ERROR, "Error reading cover art.\n"); | |
84 | return ret; | |
85 | } | |
86 | ||
87 | st->disposition |= AV_DISPOSITION_ATTACHED_PIC; | |
88 | st->codec->codec_type = AVMEDIA_TYPE_VIDEO; | |
89 | st->codec->codec_id = id; | |
90 | ||
91 | st->attached_pic = pkt; | |
92 | st->attached_pic.stream_index = st->index; | |
93 | st->attached_pic.flags |= AV_PKT_FLAG_KEY; | |
94 | } else { | |
95 | if (ff_get_extradata(st->codec, s->pb, size) < 0) | |
96 | return AVERROR(ENOMEM); | |
97 | st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; | |
98 | } | |
99 | } else { | |
100 | value = av_malloc(size+1); | |
101 | if (!value) | |
102 | return AVERROR(ENOMEM); | |
103 | c = avio_read(pb, value, size); | |
104 | if (c < 0) { | |
105 | av_free(value); | |
106 | return c; | |
107 | } | |
108 | value[c] = 0; | |
109 | av_dict_set(&s->metadata, key, value, AV_DICT_DONT_STRDUP_VAL); | |
110 | } | |
111 | return 0; | |
112 | } | |
113 | ||
114 | int64_t ff_ape_parse_tag(AVFormatContext *s) | |
115 | { | |
116 | AVIOContext *pb = s->pb; | |
117 | int64_t file_size = avio_size(pb); | |
118 | uint32_t val, fields, tag_bytes; | |
119 | uint8_t buf[8]; | |
120 | int64_t tag_start; | |
121 | int i; | |
122 | ||
123 | if (file_size < APE_TAG_FOOTER_BYTES) | |
124 | return 0; | |
125 | ||
126 | avio_seek(pb, file_size - APE_TAG_FOOTER_BYTES, SEEK_SET); | |
127 | ||
128 | avio_read(pb, buf, 8); /* APETAGEX */ | |
129 | if (strncmp(buf, APE_TAG_PREAMBLE, 8)) { | |
130 | return 0; | |
131 | } | |
132 | ||
133 | val = avio_rl32(pb); /* APE tag version */ | |
134 | if (val > APE_TAG_VERSION) { | |
135 | av_log(s, AV_LOG_ERROR, "Unsupported tag version. (>=%d)\n", APE_TAG_VERSION); | |
136 | return 0; | |
137 | } | |
138 | ||
139 | tag_bytes = avio_rl32(pb); /* tag size */ | |
140 | if (tag_bytes - APE_TAG_FOOTER_BYTES > (1024 * 1024 * 16)) { | |
141 | av_log(s, AV_LOG_ERROR, "Tag size is way too big\n"); | |
142 | return 0; | |
143 | } | |
144 | ||
145 | if (tag_bytes > file_size - APE_TAG_FOOTER_BYTES) { | |
146 | av_log(s, AV_LOG_ERROR, "Invalid tag size %"PRIu32".\n", tag_bytes); | |
147 | return 0; | |
148 | } | |
149 | tag_start = file_size - tag_bytes - APE_TAG_FOOTER_BYTES; | |
150 | ||
151 | fields = avio_rl32(pb); /* number of fields */ | |
152 | if (fields > 65536) { | |
153 | av_log(s, AV_LOG_ERROR, "Too many tag fields (%"PRIu32")\n", fields); | |
154 | return 0; | |
155 | } | |
156 | ||
157 | val = avio_rl32(pb); /* flags */ | |
158 | if (val & APE_TAG_FLAG_IS_HEADER) { | |
159 | av_log(s, AV_LOG_ERROR, "APE Tag is a header\n"); | |
160 | return 0; | |
161 | } | |
162 | ||
163 | avio_seek(pb, file_size - tag_bytes, SEEK_SET); | |
164 | ||
165 | for (i=0; i<fields; i++) | |
166 | if (ape_tag_read_field(s) < 0) break; | |
167 | ||
168 | return tag_start; | |
169 | } | |
170 | ||
171 | static int string_is_ascii(const uint8_t *str) | |
172 | { | |
173 | while (*str && *str >= 0x20 && *str <= 0x7e ) str++; | |
174 | return !*str; | |
175 | } | |
176 | ||
177 | int ff_ape_write_tag(AVFormatContext *s) | |
178 | { | |
179 | AVDictionaryEntry *e = NULL; | |
180 | int size, ret, count = 0; | |
181 | AVIOContext *dyn_bc = NULL; | |
182 | uint8_t *dyn_buf = NULL; | |
183 | ||
184 | if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0) | |
185 | goto end; | |
186 | ||
187 | // flags | |
188 | avio_wl32(dyn_bc, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER | | |
189 | APE_TAG_FLAG_IS_HEADER); | |
190 | ffio_fill(dyn_bc, 0, 8); // reserved | |
191 | ||
192 | while ((e = av_dict_get(s->metadata, "", e, AV_DICT_IGNORE_SUFFIX))) { | |
193 | int val_len; | |
194 | ||
195 | if (!string_is_ascii(e->key)) { | |
196 | av_log(s, AV_LOG_WARNING, "Non ASCII keys are not allowed\n"); | |
197 | continue; | |
198 | } | |
199 | ||
200 | val_len = strlen(e->value); | |
201 | avio_wl32(dyn_bc, val_len); // value length | |
202 | avio_wl32(dyn_bc, 0); // item flags | |
203 | avio_put_str(dyn_bc, e->key); // key | |
204 | avio_write(dyn_bc, e->value, val_len); // value | |
205 | count++; | |
206 | } | |
207 | if (!count) | |
208 | goto end; | |
209 | ||
210 | size = avio_close_dyn_buf(dyn_bc, &dyn_buf); | |
211 | if (size <= 0) | |
212 | goto end; | |
213 | size += 20; | |
214 | ||
215 | // header | |
216 | avio_write(s->pb, "APETAGEX", 8); // id | |
217 | avio_wl32(s->pb, APE_TAG_VERSION); // version | |
218 | avio_wl32(s->pb, size); | |
219 | avio_wl32(s->pb, count); | |
220 | ||
221 | avio_write(s->pb, dyn_buf, size - 20); | |
222 | ||
223 | // footer | |
224 | avio_write(s->pb, "APETAGEX", 8); // id | |
225 | avio_wl32(s->pb, APE_TAG_VERSION); // version | |
226 | avio_wl32(s->pb, size); // size | |
227 | avio_wl32(s->pb, count); // tag count | |
228 | ||
229 | // flags | |
230 | avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_CONTAINS_FOOTER); | |
231 | ffio_fill(s->pb, 0, 8); // reserved | |
232 | ||
233 | end: | |
234 | if (dyn_bc && !dyn_buf) | |
235 | avio_close_dyn_buf(dyn_bc, &dyn_buf); | |
236 | av_freep(&dyn_buf); | |
237 | ||
238 | return ret; | |
239 | } |