blob: cdc602e1a9c71a4e21c1cd40ea9d3dda5fa9ac7a
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_LACKS_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 | int64_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 - AV_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 | int ret; |
66 | AVStream *st = avformat_new_stream(s, NULL); |
67 | if (!st) |
68 | return AVERROR(ENOMEM); |
69 | |
70 | ret = avio_get_str(pb, size, filename, sizeof(filename)); |
71 | if (ret < 0) |
72 | return ret; |
73 | if (size <= ret) { |
74 | av_log(s, AV_LOG_WARNING, "Skipping binary tag '%s'.\n", key); |
75 | return 0; |
76 | } |
77 | size -= ret; |
78 | |
79 | av_dict_set(&st->metadata, key, filename, 0); |
80 | |
81 | if ((id = ff_guess_image2_codec(filename)) != AV_CODEC_ID_NONE) { |
82 | AVPacket pkt; |
83 | int ret; |
84 | |
85 | ret = av_get_packet(s->pb, &pkt, size); |
86 | if (ret < 0) { |
87 | av_log(s, AV_LOG_ERROR, "Error reading cover art.\n"); |
88 | return ret; |
89 | } |
90 | |
91 | st->disposition |= AV_DISPOSITION_ATTACHED_PIC; |
92 | st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; |
93 | st->codecpar->codec_id = id; |
94 | |
95 | st->attached_pic = pkt; |
96 | st->attached_pic.stream_index = st->index; |
97 | st->attached_pic.flags |= AV_PKT_FLAG_KEY; |
98 | } else { |
99 | if (ff_get_extradata(s, st->codecpar, s->pb, size) < 0) |
100 | return AVERROR(ENOMEM); |
101 | st->codecpar->codec_type = AVMEDIA_TYPE_ATTACHMENT; |
102 | } |
103 | } else { |
104 | value = av_malloc(size+1); |
105 | if (!value) |
106 | return AVERROR(ENOMEM); |
107 | c = avio_read(pb, value, size); |
108 | if (c < 0) { |
109 | av_free(value); |
110 | return c; |
111 | } |
112 | value[c] = 0; |
113 | av_dict_set(&s->metadata, key, value, AV_DICT_DONT_STRDUP_VAL); |
114 | } |
115 | return 0; |
116 | } |
117 | |
118 | int64_t ff_ape_parse_tag(AVFormatContext *s) |
119 | { |
120 | AVIOContext *pb = s->pb; |
121 | int64_t file_size = avio_size(pb); |
122 | uint32_t val, fields, tag_bytes; |
123 | uint8_t buf[8]; |
124 | int64_t tag_start; |
125 | int i; |
126 | |
127 | if (file_size < APE_TAG_FOOTER_BYTES) |
128 | return 0; |
129 | |
130 | avio_seek(pb, file_size - APE_TAG_FOOTER_BYTES, SEEK_SET); |
131 | |
132 | avio_read(pb, buf, 8); /* APETAGEX */ |
133 | if (strncmp(buf, APE_TAG_PREAMBLE, 8)) { |
134 | return 0; |
135 | } |
136 | |
137 | val = avio_rl32(pb); /* APE tag version */ |
138 | if (val > APE_TAG_VERSION) { |
139 | av_log(s, AV_LOG_ERROR, "Unsupported tag version. (>=%d)\n", APE_TAG_VERSION); |
140 | return 0; |
141 | } |
142 | |
143 | tag_bytes = avio_rl32(pb); /* tag size */ |
144 | if (tag_bytes - APE_TAG_FOOTER_BYTES > (1024 * 1024 * 16)) { |
145 | av_log(s, AV_LOG_ERROR, "Tag size is way too big\n"); |
146 | return 0; |
147 | } |
148 | |
149 | if (tag_bytes > file_size - APE_TAG_FOOTER_BYTES) { |
150 | av_log(s, AV_LOG_ERROR, "Invalid tag size %"PRIu32".\n", tag_bytes); |
151 | return 0; |
152 | } |
153 | |
154 | fields = avio_rl32(pb); /* number of fields */ |
155 | if (fields > 65536) { |
156 | av_log(s, AV_LOG_ERROR, "Too many tag fields (%"PRIu32")\n", fields); |
157 | return 0; |
158 | } |
159 | |
160 | val = avio_rl32(pb); /* flags */ |
161 | if (val & APE_TAG_FLAG_IS_HEADER) { |
162 | av_log(s, AV_LOG_ERROR, "APE Tag is a header\n"); |
163 | return 0; |
164 | } |
165 | |
166 | avio_seek(pb, file_size - tag_bytes, SEEK_SET); |
167 | |
168 | if (val & APE_TAG_FLAG_CONTAINS_HEADER) |
169 | tag_bytes += APE_TAG_HEADER_BYTES; |
170 | |
171 | tag_start = file_size - tag_bytes; |
172 | |
173 | for (i=0; i<fields; i++) |
174 | if (ape_tag_read_field(s) < 0) break; |
175 | |
176 | return tag_start; |
177 | } |
178 | |
179 | static int string_is_ascii(const uint8_t *str) |
180 | { |
181 | while (*str && *str >= 0x20 && *str <= 0x7e ) str++; |
182 | return !*str; |
183 | } |
184 | |
185 | int ff_ape_write_tag(AVFormatContext *s) |
186 | { |
187 | AVDictionaryEntry *e = NULL; |
188 | int size, ret, count = 0; |
189 | AVIOContext *dyn_bc = NULL; |
190 | uint8_t *dyn_buf = NULL; |
191 | |
192 | if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0) |
193 | goto end; |
194 | |
195 | ff_standardize_creation_time(s); |
196 | while ((e = av_dict_get(s->metadata, "", e, AV_DICT_IGNORE_SUFFIX))) { |
197 | int val_len; |
198 | |
199 | if (!string_is_ascii(e->key)) { |
200 | av_log(s, AV_LOG_WARNING, "Non ASCII keys are not allowed\n"); |
201 | continue; |
202 | } |
203 | |
204 | val_len = strlen(e->value); |
205 | avio_wl32(dyn_bc, val_len); // value length |
206 | avio_wl32(dyn_bc, 0); // item flags |
207 | avio_put_str(dyn_bc, e->key); // key |
208 | avio_write(dyn_bc, e->value, val_len); // value |
209 | count++; |
210 | } |
211 | if (!count) |
212 | goto end; |
213 | |
214 | size = avio_close_dyn_buf(dyn_bc, &dyn_buf); |
215 | if (size <= 0) |
216 | goto end; |
217 | size += APE_TAG_FOOTER_BYTES; |
218 | |
219 | // header |
220 | avio_write(s->pb, "APETAGEX", 8); // id |
221 | avio_wl32(s->pb, APE_TAG_VERSION); // version |
222 | avio_wl32(s->pb, size); |
223 | avio_wl32(s->pb, count); |
224 | |
225 | // flags |
226 | avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER | APE_TAG_FLAG_IS_HEADER); |
227 | ffio_fill(s->pb, 0, 8); // reserved |
228 | |
229 | avio_write(s->pb, dyn_buf, size - APE_TAG_FOOTER_BYTES); |
230 | |
231 | // footer |
232 | avio_write(s->pb, "APETAGEX", 8); // id |
233 | avio_wl32(s->pb, APE_TAG_VERSION); // version |
234 | avio_wl32(s->pb, size); // size |
235 | avio_wl32(s->pb, count); // tag count |
236 | |
237 | // flags |
238 | avio_wl32(s->pb, APE_TAG_FLAG_CONTAINS_HEADER); |
239 | ffio_fill(s->pb, 0, 8); // reserved |
240 | |
241 | end: |
242 | if (dyn_bc && !dyn_buf) |
243 | avio_close_dyn_buf(dyn_bc, &dyn_buf); |
244 | av_freep(&dyn_buf); |
245 | |
246 | return ret; |
247 | } |
248 |