cannam@85
|
1 /*
|
cannam@85
|
2 * libid3tag - ID3 tag manipulation library
|
cannam@85
|
3 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
|
cannam@85
|
4 *
|
cannam@85
|
5 * This program is free software; you can redistribute it and/or modify
|
cannam@85
|
6 * it under the terms of the GNU General Public License as published by
|
cannam@85
|
7 * the Free Software Foundation; either version 2 of the License, or
|
cannam@85
|
8 * (at your option) any later version.
|
cannam@85
|
9 *
|
cannam@85
|
10 * This program is distributed in the hope that it will be useful,
|
cannam@85
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
cannam@85
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
cannam@85
|
13 * GNU General Public License for more details.
|
cannam@85
|
14 *
|
cannam@85
|
15 * You should have received a copy of the GNU General Public License
|
cannam@85
|
16 * along with this program; if not, write to the Free Software
|
cannam@85
|
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
cannam@85
|
18 *
|
cannam@85
|
19 * $Id: tag.c,v 1.20 2004/02/17 02:04:10 rob Exp $
|
cannam@85
|
20 */
|
cannam@85
|
21
|
cannam@85
|
22 # ifdef HAVE_CONFIG_H
|
cannam@85
|
23 # include "config.h"
|
cannam@85
|
24 # endif
|
cannam@85
|
25
|
cannam@85
|
26 # include "global.h"
|
cannam@85
|
27
|
cannam@85
|
28 # include <string.h>
|
cannam@85
|
29 # include <stdlib.h>
|
cannam@85
|
30
|
cannam@85
|
31 # ifdef HAVE_ASSERT_H
|
cannam@85
|
32 # include <assert.h>
|
cannam@85
|
33 # endif
|
cannam@85
|
34
|
cannam@85
|
35 # include "id3tag.h"
|
cannam@85
|
36 # include "tag.h"
|
cannam@85
|
37 # include "frame.h"
|
cannam@85
|
38 # include "compat.h"
|
cannam@85
|
39 # include "parse.h"
|
cannam@85
|
40 # include "render.h"
|
cannam@85
|
41 # include "latin1.h"
|
cannam@85
|
42 # include "ucs4.h"
|
cannam@85
|
43 # include "genre.h"
|
cannam@85
|
44 # include "crc.h"
|
cannam@85
|
45 # include "field.h"
|
cannam@85
|
46 # include "util.h"
|
cannam@85
|
47
|
cannam@85
|
48 /*
|
cannam@85
|
49 * NAME: tag->new()
|
cannam@85
|
50 * DESCRIPTION: allocate and return a new, empty tag
|
cannam@85
|
51 */
|
cannam@85
|
52 struct id3_tag *id3_tag_new(void)
|
cannam@85
|
53 {
|
cannam@85
|
54 struct id3_tag *tag;
|
cannam@85
|
55
|
cannam@85
|
56 tag = malloc(sizeof(*tag));
|
cannam@85
|
57 if (tag) {
|
cannam@85
|
58 tag->refcount = 0;
|
cannam@85
|
59 tag->version = ID3_TAG_VERSION;
|
cannam@85
|
60 tag->flags = 0;
|
cannam@85
|
61 tag->extendedflags = 0;
|
cannam@85
|
62 tag->restrictions = 0;
|
cannam@85
|
63 tag->options = /* ID3_TAG_OPTION_UNSYNCHRONISATION | */
|
cannam@85
|
64 ID3_TAG_OPTION_COMPRESSION | ID3_TAG_OPTION_CRC;
|
cannam@85
|
65 tag->nframes = 0;
|
cannam@85
|
66 tag->frames = 0;
|
cannam@85
|
67 tag->paddedsize = 0;
|
cannam@85
|
68 }
|
cannam@85
|
69
|
cannam@85
|
70 return tag;
|
cannam@85
|
71 }
|
cannam@85
|
72
|
cannam@85
|
73 /*
|
cannam@85
|
74 * NAME: tag->delete()
|
cannam@85
|
75 * DESCRIPTION: destroy a tag and deallocate all associated memory
|
cannam@85
|
76 */
|
cannam@85
|
77 void id3_tag_delete(struct id3_tag *tag)
|
cannam@85
|
78 {
|
cannam@85
|
79 assert(tag);
|
cannam@85
|
80
|
cannam@85
|
81 if (tag->refcount == 0) {
|
cannam@85
|
82 id3_tag_clearframes(tag);
|
cannam@85
|
83
|
cannam@85
|
84 if (tag->frames)
|
cannam@85
|
85 free(tag->frames);
|
cannam@85
|
86
|
cannam@85
|
87 free(tag);
|
cannam@85
|
88 }
|
cannam@85
|
89 }
|
cannam@85
|
90
|
cannam@85
|
91 /*
|
cannam@85
|
92 * NAME: tag->addref()
|
cannam@85
|
93 * DESCRIPTION: add an external reference to a tag
|
cannam@85
|
94 */
|
cannam@85
|
95 void id3_tag_addref(struct id3_tag *tag)
|
cannam@85
|
96 {
|
cannam@85
|
97 assert(tag);
|
cannam@85
|
98
|
cannam@85
|
99 ++tag->refcount;
|
cannam@85
|
100 }
|
cannam@85
|
101
|
cannam@85
|
102 /*
|
cannam@85
|
103 * NAME: tag->delref()
|
cannam@85
|
104 * DESCRIPTION: remove an external reference to a tag
|
cannam@85
|
105 */
|
cannam@85
|
106 void id3_tag_delref(struct id3_tag *tag)
|
cannam@85
|
107 {
|
cannam@85
|
108 assert(tag && tag->refcount > 0);
|
cannam@85
|
109
|
cannam@85
|
110 --tag->refcount;
|
cannam@85
|
111 }
|
cannam@85
|
112
|
cannam@85
|
113 /*
|
cannam@85
|
114 * NAME: tag->version()
|
cannam@85
|
115 * DESCRIPTION: return the tag's original ID3 version number
|
cannam@85
|
116 */
|
cannam@85
|
117 unsigned int id3_tag_version(struct id3_tag const *tag)
|
cannam@85
|
118 {
|
cannam@85
|
119 assert(tag);
|
cannam@85
|
120
|
cannam@85
|
121 return tag->version;
|
cannam@85
|
122 }
|
cannam@85
|
123
|
cannam@85
|
124 /*
|
cannam@85
|
125 * NAME: tag->options()
|
cannam@85
|
126 * DESCRIPTION: get or set tag options
|
cannam@85
|
127 */
|
cannam@85
|
128 int id3_tag_options(struct id3_tag *tag, int mask, int values)
|
cannam@85
|
129 {
|
cannam@85
|
130 assert(tag);
|
cannam@85
|
131
|
cannam@85
|
132 if (mask)
|
cannam@85
|
133 tag->options = (tag->options & ~mask) | (values & mask);
|
cannam@85
|
134
|
cannam@85
|
135 return tag->options;
|
cannam@85
|
136 }
|
cannam@85
|
137
|
cannam@85
|
138 /*
|
cannam@85
|
139 * NAME: tag->setlength()
|
cannam@85
|
140 * DESCRIPTION: set the minimum rendered tag size
|
cannam@85
|
141 */
|
cannam@85
|
142 void id3_tag_setlength(struct id3_tag *tag, id3_length_t length)
|
cannam@85
|
143 {
|
cannam@85
|
144 assert(tag);
|
cannam@85
|
145
|
cannam@85
|
146 tag->paddedsize = length;
|
cannam@85
|
147 }
|
cannam@85
|
148
|
cannam@85
|
149 /*
|
cannam@85
|
150 * NAME: tag->clearframes()
|
cannam@85
|
151 * DESCRIPTION: detach and delete all frames associated with a tag
|
cannam@85
|
152 */
|
cannam@85
|
153 void id3_tag_clearframes(struct id3_tag *tag)
|
cannam@85
|
154 {
|
cannam@85
|
155 unsigned int i;
|
cannam@85
|
156
|
cannam@85
|
157 assert(tag);
|
cannam@85
|
158
|
cannam@85
|
159 for (i = 0; i < tag->nframes; ++i) {
|
cannam@85
|
160 id3_frame_delref(tag->frames[i]);
|
cannam@85
|
161 id3_frame_delete(tag->frames[i]);
|
cannam@85
|
162 }
|
cannam@85
|
163
|
cannam@85
|
164 tag->nframes = 0;
|
cannam@85
|
165 }
|
cannam@85
|
166
|
cannam@85
|
167 /*
|
cannam@85
|
168 * NAME: tag->attachframe()
|
cannam@85
|
169 * DESCRIPTION: attach a frame to a tag
|
cannam@85
|
170 */
|
cannam@85
|
171 int id3_tag_attachframe(struct id3_tag *tag, struct id3_frame *frame)
|
cannam@85
|
172 {
|
cannam@85
|
173 struct id3_frame **frames;
|
cannam@85
|
174
|
cannam@85
|
175 assert(tag && frame);
|
cannam@85
|
176
|
cannam@85
|
177 frames = realloc(tag->frames, (tag->nframes + 1) * sizeof(*frames));
|
cannam@85
|
178 if (frames == 0)
|
cannam@85
|
179 return -1;
|
cannam@85
|
180
|
cannam@85
|
181 tag->frames = frames;
|
cannam@85
|
182 tag->frames[tag->nframes++] = frame;
|
cannam@85
|
183
|
cannam@85
|
184 id3_frame_addref(frame);
|
cannam@85
|
185
|
cannam@85
|
186 return 0;
|
cannam@85
|
187 }
|
cannam@85
|
188
|
cannam@85
|
189 /*
|
cannam@85
|
190 * NAME: tag->detachframe()
|
cannam@85
|
191 * DESCRIPTION: detach (but don't delete) a frame from a tag
|
cannam@85
|
192 */
|
cannam@85
|
193 int id3_tag_detachframe(struct id3_tag *tag, struct id3_frame *frame)
|
cannam@85
|
194 {
|
cannam@85
|
195 unsigned int i;
|
cannam@85
|
196
|
cannam@85
|
197 assert(tag && frame);
|
cannam@85
|
198
|
cannam@85
|
199 for (i = 0; i < tag->nframes; ++i) {
|
cannam@85
|
200 if (tag->frames[i] == frame)
|
cannam@85
|
201 break;
|
cannam@85
|
202 }
|
cannam@85
|
203
|
cannam@85
|
204 if (i == tag->nframes)
|
cannam@85
|
205 return -1;
|
cannam@85
|
206
|
cannam@85
|
207 --tag->nframes;
|
cannam@85
|
208 while (i++ < tag->nframes)
|
cannam@85
|
209 tag->frames[i - 1] = tag->frames[i];
|
cannam@85
|
210
|
cannam@85
|
211 id3_frame_delref(frame);
|
cannam@85
|
212
|
cannam@85
|
213 return 0;
|
cannam@85
|
214 }
|
cannam@85
|
215
|
cannam@85
|
216 /*
|
cannam@85
|
217 * NAME: tag->findframe()
|
cannam@85
|
218 * DESCRIPTION: find in a tag the nth (0-based) frame with the given frame ID
|
cannam@85
|
219 */
|
cannam@85
|
220 struct id3_frame *id3_tag_findframe(struct id3_tag const *tag,
|
cannam@85
|
221 char const *id, unsigned int index)
|
cannam@85
|
222 {
|
cannam@85
|
223 unsigned int len, i;
|
cannam@85
|
224
|
cannam@85
|
225 assert(tag);
|
cannam@85
|
226
|
cannam@85
|
227 if (id == 0 || *id == 0)
|
cannam@85
|
228 return (index < tag->nframes) ? tag->frames[index] : 0;
|
cannam@85
|
229
|
cannam@85
|
230 len = strlen(id);
|
cannam@85
|
231
|
cannam@85
|
232 if (len == 4) {
|
cannam@85
|
233 struct id3_compat const *compat;
|
cannam@85
|
234
|
cannam@85
|
235 compat = id3_compat_lookup(id, len);
|
cannam@85
|
236 if (compat && compat->equiv && !compat->translate) {
|
cannam@85
|
237 id = compat->equiv;
|
cannam@85
|
238 len = strlen(id);
|
cannam@85
|
239 }
|
cannam@85
|
240 }
|
cannam@85
|
241
|
cannam@85
|
242 for (i = 0; i < tag->nframes; ++i) {
|
cannam@85
|
243 if (strncmp(tag->frames[i]->id, id, len) == 0 && index-- == 0)
|
cannam@85
|
244 return tag->frames[i];
|
cannam@85
|
245 }
|
cannam@85
|
246
|
cannam@85
|
247 return 0;
|
cannam@85
|
248 }
|
cannam@85
|
249
|
cannam@85
|
250 enum tagtype {
|
cannam@85
|
251 TAGTYPE_NONE = 0,
|
cannam@85
|
252 TAGTYPE_ID3V1,
|
cannam@85
|
253 TAGTYPE_ID3V2,
|
cannam@85
|
254 TAGTYPE_ID3V2_FOOTER
|
cannam@85
|
255 };
|
cannam@85
|
256
|
cannam@85
|
257 static
|
cannam@85
|
258 enum tagtype tagtype(id3_byte_t const *data, id3_length_t length)
|
cannam@85
|
259 {
|
cannam@85
|
260 if (length >= 3 &&
|
cannam@85
|
261 data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
|
cannam@85
|
262 return TAGTYPE_ID3V1;
|
cannam@85
|
263
|
cannam@85
|
264 if (length >= 10 &&
|
cannam@85
|
265 ((data[0] == 'I' && data[1] == 'D' && data[2] == '3') ||
|
cannam@85
|
266 (data[0] == '3' && data[1] == 'D' && data[2] == 'I')) &&
|
cannam@85
|
267 data[3] < 0xff && data[4] < 0xff &&
|
cannam@85
|
268 data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
|
cannam@85
|
269 return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER;
|
cannam@85
|
270
|
cannam@85
|
271 return TAGTYPE_NONE;
|
cannam@85
|
272 }
|
cannam@85
|
273
|
cannam@85
|
274 static
|
cannam@85
|
275 void parse_header(id3_byte_t const **ptr,
|
cannam@85
|
276 unsigned int *version, int *flags, id3_length_t *size)
|
cannam@85
|
277 {
|
cannam@85
|
278 *ptr += 3;
|
cannam@85
|
279
|
cannam@85
|
280 *version = id3_parse_uint(ptr, 2);
|
cannam@85
|
281 *flags = id3_parse_uint(ptr, 1);
|
cannam@85
|
282 *size = id3_parse_syncsafe(ptr, 4);
|
cannam@85
|
283 }
|
cannam@85
|
284
|
cannam@85
|
285 /*
|
cannam@85
|
286 * NAME: tag->query()
|
cannam@85
|
287 * DESCRIPTION: if a tag begins at the given location, return its size
|
cannam@85
|
288 */
|
cannam@85
|
289 signed long id3_tag_query(id3_byte_t const *data, id3_length_t length)
|
cannam@85
|
290 {
|
cannam@85
|
291 unsigned int version;
|
cannam@85
|
292 int flags;
|
cannam@85
|
293 id3_length_t size;
|
cannam@85
|
294
|
cannam@85
|
295 assert(data);
|
cannam@85
|
296
|
cannam@85
|
297 switch (tagtype(data, length)) {
|
cannam@85
|
298 case TAGTYPE_ID3V1:
|
cannam@85
|
299 return 128;
|
cannam@85
|
300
|
cannam@85
|
301 case TAGTYPE_ID3V2:
|
cannam@85
|
302 parse_header(&data, &version, &flags, &size);
|
cannam@85
|
303
|
cannam@85
|
304 if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
|
cannam@85
|
305 size += 10;
|
cannam@85
|
306
|
cannam@85
|
307 return 10 + size;
|
cannam@85
|
308
|
cannam@85
|
309 case TAGTYPE_ID3V2_FOOTER:
|
cannam@85
|
310 parse_header(&data, &version, &flags, &size);
|
cannam@85
|
311 return -size - 10;
|
cannam@85
|
312
|
cannam@85
|
313 case TAGTYPE_NONE:
|
cannam@85
|
314 break;
|
cannam@85
|
315 }
|
cannam@85
|
316
|
cannam@85
|
317 return 0;
|
cannam@85
|
318 }
|
cannam@85
|
319
|
cannam@85
|
320 static
|
cannam@85
|
321 void trim(char *str)
|
cannam@85
|
322 {
|
cannam@85
|
323 char *ptr;
|
cannam@85
|
324
|
cannam@85
|
325 ptr = str + strlen(str);
|
cannam@85
|
326 while (ptr > str && ptr[-1] == ' ')
|
cannam@85
|
327 --ptr;
|
cannam@85
|
328
|
cannam@85
|
329 *ptr = 0;
|
cannam@85
|
330 }
|
cannam@85
|
331
|
cannam@85
|
332 static
|
cannam@85
|
333 int v1_attachstr(struct id3_tag *tag, char const *id,
|
cannam@85
|
334 char *text, unsigned long number)
|
cannam@85
|
335 {
|
cannam@85
|
336 struct id3_frame *frame;
|
cannam@85
|
337 id3_ucs4_t ucs4[31];
|
cannam@85
|
338
|
cannam@85
|
339 if (text) {
|
cannam@85
|
340 trim(text);
|
cannam@85
|
341 if (*text == 0)
|
cannam@85
|
342 return 0;
|
cannam@85
|
343 }
|
cannam@85
|
344
|
cannam@85
|
345 frame = id3_frame_new(id);
|
cannam@85
|
346 if (frame == 0)
|
cannam@85
|
347 return -1;
|
cannam@85
|
348
|
cannam@85
|
349 if (id3_field_settextencoding(&frame->fields[0],
|
cannam@85
|
350 ID3_FIELD_TEXTENCODING_ISO_8859_1) == -1)
|
cannam@85
|
351 goto fail;
|
cannam@85
|
352
|
cannam@85
|
353 if (text)
|
cannam@85
|
354 id3_latin1_decode(text, ucs4);
|
cannam@85
|
355 else
|
cannam@85
|
356 id3_ucs4_putnumber(ucs4, number);
|
cannam@85
|
357
|
cannam@85
|
358 if (strcmp(id, ID3_FRAME_COMMENT) == 0) {
|
cannam@85
|
359 if (id3_field_setlanguage(&frame->fields[1], "XXX") == -1 ||
|
cannam@85
|
360 id3_field_setstring(&frame->fields[2], id3_ucs4_empty) == -1 ||
|
cannam@85
|
361 id3_field_setfullstring(&frame->fields[3], ucs4) == -1)
|
cannam@85
|
362 goto fail;
|
cannam@85
|
363 }
|
cannam@85
|
364 else {
|
cannam@85
|
365 id3_ucs4_t *ptr = ucs4;
|
cannam@85
|
366
|
cannam@85
|
367 if (id3_field_setstrings(&frame->fields[1], 1, &ptr) == -1)
|
cannam@85
|
368 goto fail;
|
cannam@85
|
369 }
|
cannam@85
|
370
|
cannam@85
|
371 if (id3_tag_attachframe(tag, frame) == -1)
|
cannam@85
|
372 goto fail;
|
cannam@85
|
373
|
cannam@85
|
374 return 0;
|
cannam@85
|
375
|
cannam@85
|
376 fail:
|
cannam@85
|
377 id3_frame_delete(frame);
|
cannam@85
|
378 return -1;
|
cannam@85
|
379 }
|
cannam@85
|
380
|
cannam@85
|
381 static
|
cannam@85
|
382 struct id3_tag *v1_parse(id3_byte_t const *data)
|
cannam@85
|
383 {
|
cannam@85
|
384 struct id3_tag *tag;
|
cannam@85
|
385
|
cannam@85
|
386 tag = id3_tag_new();
|
cannam@85
|
387 if (tag) {
|
cannam@85
|
388 char title[31], artist[31], album[31], year[5], comment[31];
|
cannam@85
|
389 unsigned int genre, track;
|
cannam@85
|
390
|
cannam@85
|
391 tag->version = 0x0100;
|
cannam@85
|
392
|
cannam@85
|
393 tag->options |= ID3_TAG_OPTION_ID3V1;
|
cannam@85
|
394 tag->options &= ~ID3_TAG_OPTION_COMPRESSION;
|
cannam@85
|
395
|
cannam@85
|
396 tag->restrictions =
|
cannam@85
|
397 ID3_TAG_RESTRICTION_TEXTENCODING_LATIN1_UTF8 |
|
cannam@85
|
398 ID3_TAG_RESTRICTION_TEXTSIZE_30_CHARS;
|
cannam@85
|
399
|
cannam@85
|
400 title[30] = artist[30] = album[30] = year[4] = comment[30] = 0;
|
cannam@85
|
401
|
cannam@85
|
402 memcpy(title, &data[3], 30);
|
cannam@85
|
403 memcpy(artist, &data[33], 30);
|
cannam@85
|
404 memcpy(album, &data[63], 30);
|
cannam@85
|
405 memcpy(year, &data[93], 4);
|
cannam@85
|
406 memcpy(comment, &data[97], 30);
|
cannam@85
|
407
|
cannam@85
|
408 genre = data[127];
|
cannam@85
|
409
|
cannam@85
|
410 track = 0;
|
cannam@85
|
411 if (comment[28] == 0 && comment[29] != 0) {
|
cannam@85
|
412 track = comment[29];
|
cannam@85
|
413 tag->version = 0x0101;
|
cannam@85
|
414 }
|
cannam@85
|
415
|
cannam@85
|
416 /* populate tag frames */
|
cannam@85
|
417
|
cannam@85
|
418 if (v1_attachstr(tag, ID3_FRAME_TITLE, title, 0) == -1 ||
|
cannam@85
|
419 v1_attachstr(tag, ID3_FRAME_ARTIST, artist, 0) == -1 ||
|
cannam@85
|
420 v1_attachstr(tag, ID3_FRAME_ALBUM, album, 0) == -1 ||
|
cannam@85
|
421 v1_attachstr(tag, ID3_FRAME_YEAR, year, 0) == -1 ||
|
cannam@85
|
422 (track && v1_attachstr(tag, ID3_FRAME_TRACK, 0, track) == -1) ||
|
cannam@85
|
423 (genre < 0xff && v1_attachstr(tag, ID3_FRAME_GENRE, 0, genre) == -1) ||
|
cannam@85
|
424 v1_attachstr(tag, ID3_FRAME_COMMENT, comment, 0) == -1) {
|
cannam@85
|
425 id3_tag_delete(tag);
|
cannam@85
|
426 tag = 0;
|
cannam@85
|
427 }
|
cannam@85
|
428 }
|
cannam@85
|
429
|
cannam@85
|
430 return tag;
|
cannam@85
|
431 }
|
cannam@85
|
432
|
cannam@85
|
433 static
|
cannam@85
|
434 struct id3_tag *v2_parse(id3_byte_t const *ptr)
|
cannam@85
|
435 {
|
cannam@85
|
436 struct id3_tag *tag;
|
cannam@85
|
437 id3_byte_t *mem = 0;
|
cannam@85
|
438
|
cannam@85
|
439 tag = id3_tag_new();
|
cannam@85
|
440 if (tag) {
|
cannam@85
|
441 id3_byte_t const *end;
|
cannam@85
|
442 id3_length_t size;
|
cannam@85
|
443
|
cannam@85
|
444 parse_header(&ptr, &tag->version, &tag->flags, &size);
|
cannam@85
|
445
|
cannam@85
|
446 tag->paddedsize = 10 + size;
|
cannam@85
|
447
|
cannam@85
|
448 if ((tag->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) &&
|
cannam@85
|
449 ID3_TAG_VERSION_MAJOR(tag->version) < 4) {
|
cannam@85
|
450 mem = malloc(size);
|
cannam@85
|
451 if (mem == 0)
|
cannam@85
|
452 goto fail;
|
cannam@85
|
453
|
cannam@85
|
454 memcpy(mem, ptr, size);
|
cannam@85
|
455
|
cannam@85
|
456 size = id3_util_deunsynchronise(mem, size);
|
cannam@85
|
457 ptr = mem;
|
cannam@85
|
458 }
|
cannam@85
|
459
|
cannam@85
|
460 end = ptr + size;
|
cannam@85
|
461
|
cannam@85
|
462 if (tag->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
|
cannam@85
|
463 switch (ID3_TAG_VERSION_MAJOR(tag->version)) {
|
cannam@85
|
464 case 2:
|
cannam@85
|
465 goto fail;
|
cannam@85
|
466
|
cannam@85
|
467 case 3:
|
cannam@85
|
468 {
|
cannam@85
|
469 id3_byte_t const *ehptr, *ehend;
|
cannam@85
|
470 id3_length_t ehsize;
|
cannam@85
|
471
|
cannam@85
|
472 enum {
|
cannam@85
|
473 EH_FLAG_CRC = 0x8000 /* CRC data present */
|
cannam@85
|
474 };
|
cannam@85
|
475
|
cannam@85
|
476 if (end - ptr < 4)
|
cannam@85
|
477 goto fail;
|
cannam@85
|
478
|
cannam@85
|
479 ehsize = id3_parse_uint(&ptr, 4);
|
cannam@85
|
480
|
cannam@85
|
481 if (ehsize > end - ptr)
|
cannam@85
|
482 goto fail;
|
cannam@85
|
483
|
cannam@85
|
484 ehptr = ptr;
|
cannam@85
|
485 ehend = ptr + ehsize;
|
cannam@85
|
486
|
cannam@85
|
487 ptr = ehend;
|
cannam@85
|
488
|
cannam@85
|
489 if (ehend - ehptr >= 6) {
|
cannam@85
|
490 int ehflags;
|
cannam@85
|
491 id3_length_t padsize;
|
cannam@85
|
492
|
cannam@85
|
493 ehflags = id3_parse_uint(&ehptr, 2);
|
cannam@85
|
494 padsize = id3_parse_uint(&ehptr, 4);
|
cannam@85
|
495
|
cannam@85
|
496 if (padsize > end - ptr)
|
cannam@85
|
497 goto fail;
|
cannam@85
|
498
|
cannam@85
|
499 end -= padsize;
|
cannam@85
|
500
|
cannam@85
|
501 if (ehflags & EH_FLAG_CRC) {
|
cannam@85
|
502 unsigned long crc;
|
cannam@85
|
503
|
cannam@85
|
504 if (ehend - ehptr < 4)
|
cannam@85
|
505 goto fail;
|
cannam@85
|
506
|
cannam@85
|
507 crc = id3_parse_uint(&ehptr, 4);
|
cannam@85
|
508
|
cannam@85
|
509 if (crc != id3_crc_compute(ptr, end - ptr))
|
cannam@85
|
510 goto fail;
|
cannam@85
|
511
|
cannam@85
|
512 tag->extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
|
cannam@85
|
513 }
|
cannam@85
|
514 }
|
cannam@85
|
515 }
|
cannam@85
|
516 break;
|
cannam@85
|
517
|
cannam@85
|
518 case 4:
|
cannam@85
|
519 {
|
cannam@85
|
520 id3_byte_t const *ehptr, *ehend;
|
cannam@85
|
521 id3_length_t ehsize;
|
cannam@85
|
522 unsigned int bytes;
|
cannam@85
|
523
|
cannam@85
|
524 if (end - ptr < 4)
|
cannam@85
|
525 goto fail;
|
cannam@85
|
526
|
cannam@85
|
527 ehptr = ptr;
|
cannam@85
|
528 ehsize = id3_parse_syncsafe(&ptr, 4);
|
cannam@85
|
529
|
cannam@85
|
530 if (ehsize < 6 || ehsize > end - ehptr)
|
cannam@85
|
531 goto fail;
|
cannam@85
|
532
|
cannam@85
|
533 ehend = ehptr + ehsize;
|
cannam@85
|
534
|
cannam@85
|
535 bytes = id3_parse_uint(&ptr, 1);
|
cannam@85
|
536
|
cannam@85
|
537 if (bytes < 1 || bytes > ehend - ptr)
|
cannam@85
|
538 goto fail;
|
cannam@85
|
539
|
cannam@85
|
540 ehptr = ptr + bytes;
|
cannam@85
|
541
|
cannam@85
|
542 /* verify extended header size */
|
cannam@85
|
543 {
|
cannam@85
|
544 id3_byte_t const *flagsptr = ptr, *dataptr = ehptr;
|
cannam@85
|
545 unsigned int datalen;
|
cannam@85
|
546 int ehflags;
|
cannam@85
|
547
|
cannam@85
|
548 while (bytes--) {
|
cannam@85
|
549 for (ehflags = id3_parse_uint(&flagsptr, 1); ehflags;
|
cannam@85
|
550 ehflags = (ehflags << 1) & 0xff) {
|
cannam@85
|
551 if (ehflags & 0x80) {
|
cannam@85
|
552 if (dataptr == ehend)
|
cannam@85
|
553 goto fail;
|
cannam@85
|
554 datalen = id3_parse_uint(&dataptr, 1);
|
cannam@85
|
555 if (datalen > 0x7f || datalen > ehend - dataptr)
|
cannam@85
|
556 goto fail;
|
cannam@85
|
557 dataptr += datalen;
|
cannam@85
|
558 }
|
cannam@85
|
559 }
|
cannam@85
|
560 }
|
cannam@85
|
561 }
|
cannam@85
|
562
|
cannam@85
|
563 tag->extendedflags = id3_parse_uint(&ptr, 1);
|
cannam@85
|
564
|
cannam@85
|
565 ptr = ehend;
|
cannam@85
|
566
|
cannam@85
|
567 if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) {
|
cannam@85
|
568 bytes = id3_parse_uint(&ehptr, 1);
|
cannam@85
|
569 ehptr += bytes;
|
cannam@85
|
570 }
|
cannam@85
|
571
|
cannam@85
|
572 if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
|
cannam@85
|
573 unsigned long crc;
|
cannam@85
|
574
|
cannam@85
|
575 bytes = id3_parse_uint(&ehptr, 1);
|
cannam@85
|
576 if (bytes < 5)
|
cannam@85
|
577 goto fail;
|
cannam@85
|
578
|
cannam@85
|
579 crc = id3_parse_syncsafe(&ehptr, 5);
|
cannam@85
|
580 ehptr += bytes - 5;
|
cannam@85
|
581
|
cannam@85
|
582 if (crc != id3_crc_compute(ptr, end - ptr))
|
cannam@85
|
583 goto fail;
|
cannam@85
|
584 }
|
cannam@85
|
585
|
cannam@85
|
586 if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
|
cannam@85
|
587 bytes = id3_parse_uint(&ehptr, 1);
|
cannam@85
|
588 if (bytes < 1)
|
cannam@85
|
589 goto fail;
|
cannam@85
|
590
|
cannam@85
|
591 tag->restrictions = id3_parse_uint(&ehptr, 1);
|
cannam@85
|
592 ehptr += bytes - 1;
|
cannam@85
|
593 }
|
cannam@85
|
594 }
|
cannam@85
|
595 break;
|
cannam@85
|
596 }
|
cannam@85
|
597 }
|
cannam@85
|
598
|
cannam@85
|
599 /* frames */
|
cannam@85
|
600
|
cannam@85
|
601 while (ptr < end) {
|
cannam@85
|
602 struct id3_frame *frame;
|
cannam@85
|
603
|
cannam@85
|
604 if (*ptr == 0)
|
cannam@85
|
605 break; /* padding */
|
cannam@85
|
606
|
cannam@85
|
607 frame = id3_frame_parse(&ptr, end - ptr, tag->version);
|
cannam@85
|
608 if (frame == 0 || id3_tag_attachframe(tag, frame) == -1)
|
cannam@85
|
609 goto fail;
|
cannam@85
|
610 }
|
cannam@85
|
611
|
cannam@85
|
612 if (ID3_TAG_VERSION_MAJOR(tag->version) < 4 &&
|
cannam@85
|
613 id3_compat_fixup(tag) == -1)
|
cannam@85
|
614 goto fail;
|
cannam@85
|
615 }
|
cannam@85
|
616
|
cannam@85
|
617 if (0) {
|
cannam@85
|
618 fail:
|
cannam@85
|
619 id3_tag_delete(tag);
|
cannam@85
|
620 tag = 0;
|
cannam@85
|
621 }
|
cannam@85
|
622
|
cannam@85
|
623 if (mem)
|
cannam@85
|
624 free(mem);
|
cannam@85
|
625
|
cannam@85
|
626 return tag;
|
cannam@85
|
627 }
|
cannam@85
|
628
|
cannam@85
|
629 /*
|
cannam@85
|
630 * NAME: tag->parse()
|
cannam@85
|
631 * DESCRIPTION: parse a complete ID3 tag
|
cannam@85
|
632 */
|
cannam@85
|
633 struct id3_tag *id3_tag_parse(id3_byte_t const *data, id3_length_t length)
|
cannam@85
|
634 {
|
cannam@85
|
635 id3_byte_t const *ptr;
|
cannam@85
|
636 unsigned int version;
|
cannam@85
|
637 int flags;
|
cannam@85
|
638 id3_length_t size;
|
cannam@85
|
639
|
cannam@85
|
640 assert(data);
|
cannam@85
|
641
|
cannam@85
|
642 switch (tagtype(data, length)) {
|
cannam@85
|
643 case TAGTYPE_ID3V1:
|
cannam@85
|
644 return (length < 128) ? 0 : v1_parse(data);
|
cannam@85
|
645
|
cannam@85
|
646 case TAGTYPE_ID3V2:
|
cannam@85
|
647 break;
|
cannam@85
|
648
|
cannam@85
|
649 case TAGTYPE_ID3V2_FOOTER:
|
cannam@85
|
650 case TAGTYPE_NONE:
|
cannam@85
|
651 return 0;
|
cannam@85
|
652 }
|
cannam@85
|
653
|
cannam@85
|
654 /* ID3v2.x */
|
cannam@85
|
655
|
cannam@85
|
656 ptr = data;
|
cannam@85
|
657 parse_header(&ptr, &version, &flags, &size);
|
cannam@85
|
658
|
cannam@85
|
659 switch (ID3_TAG_VERSION_MAJOR(version)) {
|
cannam@85
|
660 case 4:
|
cannam@85
|
661 if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
|
cannam@85
|
662 size += 10;
|
cannam@85
|
663 case 2:
|
cannam@85
|
664 case 3:
|
cannam@85
|
665 return (length < 10 + size) ? 0 : v2_parse(data);
|
cannam@85
|
666 }
|
cannam@85
|
667
|
cannam@85
|
668 return 0;
|
cannam@85
|
669 }
|
cannam@85
|
670
|
cannam@85
|
671 static
|
cannam@85
|
672 void v1_renderstr(struct id3_tag const *tag, char const *frameid,
|
cannam@85
|
673 id3_byte_t **buffer, id3_length_t length)
|
cannam@85
|
674 {
|
cannam@85
|
675 struct id3_frame *frame;
|
cannam@85
|
676 id3_ucs4_t const *string;
|
cannam@85
|
677
|
cannam@85
|
678 frame = id3_tag_findframe(tag, frameid, 0);
|
cannam@85
|
679 if (frame == 0)
|
cannam@85
|
680 string = id3_ucs4_empty;
|
cannam@85
|
681 else {
|
cannam@85
|
682 if (strcmp(frameid, ID3_FRAME_COMMENT) == 0)
|
cannam@85
|
683 string = id3_field_getfullstring(&frame->fields[3]);
|
cannam@85
|
684 else
|
cannam@85
|
685 string = id3_field_getstrings(&frame->fields[1], 0);
|
cannam@85
|
686 }
|
cannam@85
|
687
|
cannam@85
|
688 id3_render_paddedstring(buffer, string, length);
|
cannam@85
|
689 }
|
cannam@85
|
690
|
cannam@85
|
691 /*
|
cannam@85
|
692 * NAME: v1->render()
|
cannam@85
|
693 * DESCRIPTION: render an ID3v1 (or ID3v1.1) tag
|
cannam@85
|
694 */
|
cannam@85
|
695 static
|
cannam@85
|
696 id3_length_t v1_render(struct id3_tag const *tag, id3_byte_t *buffer)
|
cannam@85
|
697 {
|
cannam@85
|
698 id3_byte_t data[128], *ptr;
|
cannam@85
|
699 struct id3_frame *frame;
|
cannam@85
|
700 unsigned int i;
|
cannam@85
|
701 int genre = -1;
|
cannam@85
|
702
|
cannam@85
|
703 ptr = data;
|
cannam@85
|
704
|
cannam@85
|
705 id3_render_immediate(&ptr, "TAG", 3);
|
cannam@85
|
706
|
cannam@85
|
707 v1_renderstr(tag, ID3_FRAME_TITLE, &ptr, 30);
|
cannam@85
|
708 v1_renderstr(tag, ID3_FRAME_ARTIST, &ptr, 30);
|
cannam@85
|
709 v1_renderstr(tag, ID3_FRAME_ALBUM, &ptr, 30);
|
cannam@85
|
710 v1_renderstr(tag, ID3_FRAME_YEAR, &ptr, 4);
|
cannam@85
|
711 v1_renderstr(tag, ID3_FRAME_COMMENT, &ptr, 30);
|
cannam@85
|
712
|
cannam@85
|
713 /* ID3v1.1 track number */
|
cannam@85
|
714
|
cannam@85
|
715 frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0);
|
cannam@85
|
716 if (frame) {
|
cannam@85
|
717 unsigned int track;
|
cannam@85
|
718
|
cannam@85
|
719 track = id3_ucs4_getnumber(id3_field_getstrings(&frame->fields[1], 0));
|
cannam@85
|
720 if (track > 0 && track <= 0xff) {
|
cannam@85
|
721 ptr[-2] = 0;
|
cannam@85
|
722 ptr[-1] = track;
|
cannam@85
|
723 }
|
cannam@85
|
724 }
|
cannam@85
|
725
|
cannam@85
|
726 /* ID3v1 genre number */
|
cannam@85
|
727
|
cannam@85
|
728 frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0);
|
cannam@85
|
729 if (frame) {
|
cannam@85
|
730 unsigned int nstrings;
|
cannam@85
|
731
|
cannam@85
|
732 nstrings = id3_field_getnstrings(&frame->fields[1]);
|
cannam@85
|
733
|
cannam@85
|
734 for (i = 0; i < nstrings; ++i) {
|
cannam@85
|
735 genre = id3_genre_number(id3_field_getstrings(&frame->fields[1], i));
|
cannam@85
|
736 if (genre != -1)
|
cannam@85
|
737 break;
|
cannam@85
|
738 }
|
cannam@85
|
739
|
cannam@85
|
740 if (i == nstrings && nstrings > 0)
|
cannam@85
|
741 genre = ID3_GENRE_OTHER;
|
cannam@85
|
742 }
|
cannam@85
|
743
|
cannam@85
|
744 id3_render_int(&ptr, genre, 1);
|
cannam@85
|
745
|
cannam@85
|
746 /* make sure the tag is not empty */
|
cannam@85
|
747
|
cannam@85
|
748 if (genre == -1) {
|
cannam@85
|
749 for (i = 3; i < 127; ++i) {
|
cannam@85
|
750 if (data[i] != ' ')
|
cannam@85
|
751 break;
|
cannam@85
|
752 }
|
cannam@85
|
753
|
cannam@85
|
754 if (i == 127)
|
cannam@85
|
755 return 0;
|
cannam@85
|
756 }
|
cannam@85
|
757
|
cannam@85
|
758 if (buffer)
|
cannam@85
|
759 memcpy(buffer, data, 128);
|
cannam@85
|
760
|
cannam@85
|
761 return 128;
|
cannam@85
|
762 }
|
cannam@85
|
763
|
cannam@85
|
764 /*
|
cannam@85
|
765 * NAME: tag->render()
|
cannam@85
|
766 * DESCRIPTION: render a complete ID3 tag
|
cannam@85
|
767 */
|
cannam@85
|
768 id3_length_t id3_tag_render(struct id3_tag const *tag, id3_byte_t *buffer)
|
cannam@85
|
769 {
|
cannam@85
|
770 id3_length_t size = 0;
|
cannam@85
|
771 id3_byte_t **ptr,
|
cannam@85
|
772 *header_ptr = 0, *tagsize_ptr = 0, *crc_ptr = 0, *frames_ptr = 0;
|
cannam@85
|
773 int flags, extendedflags;
|
cannam@85
|
774 unsigned int i;
|
cannam@85
|
775
|
cannam@85
|
776 assert(tag);
|
cannam@85
|
777
|
cannam@85
|
778 if (tag->options & ID3_TAG_OPTION_ID3V1)
|
cannam@85
|
779 return v1_render(tag, buffer);
|
cannam@85
|
780
|
cannam@85
|
781 /* a tag must contain at least one (renderable) frame */
|
cannam@85
|
782
|
cannam@85
|
783 for (i = 0; i < tag->nframes; ++i) {
|
cannam@85
|
784 if (id3_frame_render(tag->frames[i], 0, 0) > 0)
|
cannam@85
|
785 break;
|
cannam@85
|
786 }
|
cannam@85
|
787
|
cannam@85
|
788 if (i == tag->nframes)
|
cannam@85
|
789 return 0;
|
cannam@85
|
790
|
cannam@85
|
791 ptr = buffer ? &buffer : 0;
|
cannam@85
|
792
|
cannam@85
|
793 /* get flags */
|
cannam@85
|
794
|
cannam@85
|
795 flags = tag->flags & ID3_TAG_FLAG_KNOWNFLAGS;
|
cannam@85
|
796 extendedflags = tag->extendedflags & ID3_TAG_EXTENDEDFLAG_KNOWNFLAGS;
|
cannam@85
|
797
|
cannam@85
|
798 extendedflags &= ~ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
|
cannam@85
|
799 if (tag->options & ID3_TAG_OPTION_CRC)
|
cannam@85
|
800 extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
|
cannam@85
|
801
|
cannam@85
|
802 extendedflags &= ~ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
|
cannam@85
|
803 if (tag->restrictions)
|
cannam@85
|
804 extendedflags |= ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
|
cannam@85
|
805
|
cannam@85
|
806 flags &= ~ID3_TAG_FLAG_UNSYNCHRONISATION;
|
cannam@85
|
807 if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION)
|
cannam@85
|
808 flags |= ID3_TAG_FLAG_UNSYNCHRONISATION;
|
cannam@85
|
809
|
cannam@85
|
810 flags &= ~ID3_TAG_FLAG_EXTENDEDHEADER;
|
cannam@85
|
811 if (extendedflags)
|
cannam@85
|
812 flags |= ID3_TAG_FLAG_EXTENDEDHEADER;
|
cannam@85
|
813
|
cannam@85
|
814 flags &= ~ID3_TAG_FLAG_FOOTERPRESENT;
|
cannam@85
|
815 if (tag->options & ID3_TAG_OPTION_APPENDEDTAG)
|
cannam@85
|
816 flags |= ID3_TAG_FLAG_FOOTERPRESENT;
|
cannam@85
|
817
|
cannam@85
|
818 /* header */
|
cannam@85
|
819
|
cannam@85
|
820 if (ptr)
|
cannam@85
|
821 header_ptr = *ptr;
|
cannam@85
|
822
|
cannam@85
|
823 size += id3_render_immediate(ptr, "ID3", 3);
|
cannam@85
|
824 size += id3_render_int(ptr, ID3_TAG_VERSION, 2);
|
cannam@85
|
825 size += id3_render_int(ptr, flags, 1);
|
cannam@85
|
826
|
cannam@85
|
827 if (ptr)
|
cannam@85
|
828 tagsize_ptr = *ptr;
|
cannam@85
|
829
|
cannam@85
|
830 size += id3_render_syncsafe(ptr, 0, 4);
|
cannam@85
|
831
|
cannam@85
|
832 /* extended header */
|
cannam@85
|
833
|
cannam@85
|
834 if (flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
|
cannam@85
|
835 id3_length_t ehsize = 0;
|
cannam@85
|
836 id3_byte_t *ehsize_ptr = 0;
|
cannam@85
|
837
|
cannam@85
|
838 if (ptr)
|
cannam@85
|
839 ehsize_ptr = *ptr;
|
cannam@85
|
840
|
cannam@85
|
841 ehsize += id3_render_syncsafe(ptr, 0, 4);
|
cannam@85
|
842 ehsize += id3_render_int(ptr, 1, 1);
|
cannam@85
|
843 ehsize += id3_render_int(ptr, extendedflags, 1);
|
cannam@85
|
844
|
cannam@85
|
845 if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)
|
cannam@85
|
846 ehsize += id3_render_int(ptr, 0, 1);
|
cannam@85
|
847
|
cannam@85
|
848 if (extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
|
cannam@85
|
849 ehsize += id3_render_int(ptr, 5, 1);
|
cannam@85
|
850
|
cannam@85
|
851 if (ptr)
|
cannam@85
|
852 crc_ptr = *ptr;
|
cannam@85
|
853
|
cannam@85
|
854 ehsize += id3_render_syncsafe(ptr, 0, 5);
|
cannam@85
|
855 }
|
cannam@85
|
856
|
cannam@85
|
857 if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
|
cannam@85
|
858 ehsize += id3_render_int(ptr, 1, 1);
|
cannam@85
|
859 ehsize += id3_render_int(ptr, tag->restrictions, 1);
|
cannam@85
|
860 }
|
cannam@85
|
861
|
cannam@85
|
862 if (ehsize_ptr)
|
cannam@85
|
863 id3_render_syncsafe(&ehsize_ptr, ehsize, 4);
|
cannam@85
|
864
|
cannam@85
|
865 size += ehsize;
|
cannam@85
|
866 }
|
cannam@85
|
867
|
cannam@85
|
868 /* frames */
|
cannam@85
|
869
|
cannam@85
|
870 if (ptr)
|
cannam@85
|
871 frames_ptr = *ptr;
|
cannam@85
|
872
|
cannam@85
|
873 for (i = 0; i < tag->nframes; ++i)
|
cannam@85
|
874 size += id3_frame_render(tag->frames[i], ptr, tag->options);
|
cannam@85
|
875
|
cannam@85
|
876 /* padding */
|
cannam@85
|
877
|
cannam@85
|
878 if (!(flags & ID3_TAG_FLAG_FOOTERPRESENT)) {
|
cannam@85
|
879 if (size < tag->paddedsize)
|
cannam@85
|
880 size += id3_render_padding(ptr, 0, tag->paddedsize - size);
|
cannam@85
|
881 else if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) {
|
cannam@85
|
882 if (ptr == 0)
|
cannam@85
|
883 size += 1;
|
cannam@85
|
884 else {
|
cannam@85
|
885 if ((*ptr)[-1] == 0xff)
|
cannam@85
|
886 size += id3_render_padding(ptr, 0, 1);
|
cannam@85
|
887 }
|
cannam@85
|
888 }
|
cannam@85
|
889 }
|
cannam@85
|
890
|
cannam@85
|
891 /* patch tag size and CRC */
|
cannam@85
|
892
|
cannam@85
|
893 if (tagsize_ptr)
|
cannam@85
|
894 id3_render_syncsafe(&tagsize_ptr, size - 10, 4);
|
cannam@85
|
895
|
cannam@85
|
896 if (crc_ptr) {
|
cannam@85
|
897 id3_render_syncsafe(&crc_ptr,
|
cannam@85
|
898 id3_crc_compute(frames_ptr, *ptr - frames_ptr), 5);
|
cannam@85
|
899 }
|
cannam@85
|
900
|
cannam@85
|
901 /* footer */
|
cannam@85
|
902
|
cannam@85
|
903 if (flags & ID3_TAG_FLAG_FOOTERPRESENT) {
|
cannam@85
|
904 size += id3_render_immediate(ptr, "3DI", 3);
|
cannam@85
|
905 size += id3_render_binary(ptr, header_ptr + 3, 7);
|
cannam@85
|
906 }
|
cannam@85
|
907
|
cannam@85
|
908 return size;
|
cannam@85
|
909 }
|