1 #include "loader_common.h"
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include <id3tag.h>
8
9 #if ! defined (__STDC_VERSION__) || __STDC_VERSION__ < 199901L
10 #if __GNUC__ >= 2
11 #define inline __inline__
12 #else
13 #define inline
14 #endif
15 #endif
16
17 #ifdef __GNUC__
18 #define UNLIKELY(exp) __builtin_expect ((exp), 0)
19 #else
20 #define UNLIKELY(exp) (exp)
21 #endif
22
23 typedef struct context {
24 int id;
25 char *filename;
26 struct id3_tag *tag;
27 int refcount;
28 struct context *next;
29 } context;
30
31 static context *id3_ctxs = NULL;
32
33 static inline struct id3_frame *
id3_tag_get_frame(struct id3_tag * tag,size_t index)34 id3_tag_get_frame(struct id3_tag *tag, size_t index)
35 {
36 return tag->frames[index];
37 }
38
39 static inline unsigned int
id3_tag_get_numframes(struct id3_tag * tag)40 id3_tag_get_numframes(struct id3_tag *tag)
41 {
42 return tag->nframes;
43 }
44
45 static inline char const *
id3_frame_id(struct id3_frame * frame)46 id3_frame_id(struct id3_frame *frame)
47 {
48 return frame->id;
49 }
50
51 static context *
context_create(const char * filename,FILE * f)52 context_create(const char *filename, FILE * f)
53 {
54 context *node = (context *) malloc(sizeof(context));
55 context *ptr, *last;
56 int last_id = INT_MAX;
57
58 node->refcount = 1;
59 {
60 int fd;
61 struct id3_file *file;
62 struct id3_tag *tag;
63 unsigned int i;
64
65 fd = dup(fileno(f));
66 file = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY);
67 if (!file)
68 {
69 close(fd);
70 fprintf(stderr, "Unable to open tagged file %s: %s\n",
71 filename, strerror(errno));
72 goto fail_free;
73 }
74 tag = id3_file_tag(file);
75 if (!tag)
76 {
77 fprintf(stderr, "Unable to find ID3v2 tags in file %s\n", filename);
78 id3_file_close(file);
79 goto fail_free;
80 }
81 node->tag = id3_tag_new();
82 for (i = 0; i < id3_tag_get_numframes(tag); i++)
83 if (!strcmp(id3_frame_id(id3_tag_get_frame(tag, i)), "APIC"))
84 id3_tag_attachframe(node->tag, id3_tag_get_frame(tag, i));
85 id3_file_close(file);
86 }
87
88 node->filename = strdup(filename);
89
90 if (!id3_ctxs)
91 {
92 node->id = 1;
93 node->next = NULL;
94 id3_ctxs = node;
95 return node;
96 }
97 ptr = id3_ctxs;
98
99 last = NULL;
100 while (UNLIKELY(ptr && (ptr->id + 1) >= last_id))
101 {
102 last_id = ptr->id;
103 last = ptr;
104 ptr = ptr->next;
105 }
106
107 /* Paranoid! this can occur only if there are INT_MAX contexts :) */
108 if (UNLIKELY(!ptr))
109 {
110 fprintf(stderr, "Too many open ID3 contexts\n");
111 goto fail_close;
112 }
113
114 node->id = ptr->id + 1;
115
116 if (UNLIKELY(!!last))
117 {
118 node->next = last->next;
119 last->next = node;
120 }
121 else
122 {
123 node->next = id3_ctxs;
124 id3_ctxs = node;
125 }
126 return node;
127
128 fail_close:
129 free(node->filename);
130 id3_tag_delete(node->tag);
131 fail_free:
132 free(node);
133 return NULL;
134 }
135
136 static void
context_destroy(context * ctx)137 context_destroy(context * ctx)
138 {
139 id3_tag_delete(ctx->tag);
140 free(ctx->filename);
141 free(ctx);
142 }
143
144 static inline void
context_addref(context * ctx)145 context_addref(context * ctx)
146 {
147 ctx->refcount++;
148 }
149
150 static context *
context_get(int id)151 context_get(int id)
152 {
153 context *ptr = id3_ctxs;
154
155 while (ptr)
156 {
157 if (ptr->id == id)
158 {
159 context_addref(ptr);
160 return ptr;
161 }
162 ptr = ptr->next;
163 }
164 fprintf(stderr, "No context by handle %d found\n", id);
165 return NULL;
166 }
167
168 static context *
context_get_by_name(const char * name)169 context_get_by_name(const char *name)
170 {
171 context *ptr = id3_ctxs;
172
173 while (ptr)
174 {
175 if (!strcmp(name, ptr->filename))
176 {
177 context_addref(ptr);
178 return ptr;
179 }
180 ptr = ptr->next;
181 }
182 return NULL;
183 }
184
185 static void
context_delref(context * ctx)186 context_delref(context * ctx)
187 {
188 ctx->refcount--;
189 if (ctx->refcount <= 0)
190 {
191 context *last = NULL, *ptr = id3_ctxs;
192
193 while (ptr)
194 {
195 if (ptr == ctx)
196 {
197 if (last)
198 last->next = ctx->next;
199 else
200 id3_ctxs = ctx->next;
201 context_destroy(ctx);
202 return;
203 }
204 last = ptr;
205 ptr = ptr->next;
206 }
207 }
208 }
209
210 static int
str2int(const char * str,int old)211 str2int(const char *str, int old)
212 {
213 long index;
214
215 errno = 0;
216 index = strtol(str, NULL, 10);
217 return ((errno || index > INT_MAX) ? old : (int)index);
218 }
219
220 static unsigned int
str2uint(const char * str,unsigned int old)221 str2uint(const char *str, unsigned int old)
222 {
223 unsigned long index;
224
225 errno = 0;
226 index = strtoul(str, NULL, 10);
227 return ((errno || index > UINT_MAX) ? old : index);
228 }
229
230 static void
destructor_data(ImlibImage * im,void * data)231 destructor_data(ImlibImage * im, void *data)
232 {
233 free(data);
234 }
235
236 static void
destructor_context(ImlibImage * im,void * data)237 destructor_context(ImlibImage * im, void *data)
238 {
239 context_delref((context *) data);
240 }
241
242 typedef struct lopt {
243 context *ctx;
244 unsigned int index;
245 int traverse;
246 char cache_level;
247 } lopt;
248
249 static char
get_options(lopt * opt,const ImlibImage * im)250 get_options(lopt * opt, const ImlibImage * im)
251 {
252 unsigned int handle = 0, index = 0, traverse = 0;
253 context *ctx;
254
255 if (im->key)
256 {
257 char *key = strdup(im->key);
258 char *tok = strtok(key, ",");
259
260 traverse = 0;
261 while (tok)
262 {
263 char *value = strchr(tok, '=');
264
265 if (!value)
266 {
267 value = tok;
268 tok = (char *)"index";
269 }
270 else
271 {
272 *value = '\0';
273 value++;
274 }
275 if (!strcasecmp(tok, "index"))
276 index = str2uint(value, index);
277 else if (!strcasecmp(tok, "context"))
278 handle = str2uint(value, handle);
279 else if (!strcasecmp(tok, "traverse"))
280 traverse = str2int(value, traverse);
281 tok = strtok(NULL, ",");
282 }
283 free(key);
284 }
285 else
286 traverse = 1;
287
288 if (!handle)
289 {
290 ImlibImageTag *htag = __imlib_GetTag(im, "context");
291
292 if (htag && htag->val)
293 handle = htag->val;
294 }
295 if (handle)
296 ctx = context_get(handle);
297 else if (!(ctx = context_get_by_name(im->real_file)) &&
298 !(ctx = context_create(im->real_file, im->fp)))
299 return 0;
300
301 if (!index)
302 {
303 ImlibImageTag *htag = __imlib_GetTag(im, "index");
304
305 if (htag && htag->val)
306 index = htag->val;
307 }
308 if (index > id3_tag_get_numframes(ctx->tag) ||
309 (index == 0 && id3_tag_get_numframes(ctx->tag) < 1))
310 {
311 if (index)
312 fprintf(stderr, "No picture frame # %d found\n", index);
313 context_delref(ctx);
314 return 0;
315 }
316 if (!index)
317 index = 1;
318
319 opt->ctx = ctx;
320 opt->index = index;
321 opt->traverse = traverse;
322 opt->cache_level = (id3_tag_get_numframes(ctx->tag) > 1 ? 1 : 0);
323 return 1;
324 }
325
326 static int
extract_pic(struct id3_frame * frame,int dest)327 extract_pic(struct id3_frame *frame, int dest)
328 {
329 union id3_field *field;
330 unsigned char const *data;
331 id3_length_t length;
332 int done = 0;
333
334 field = id3_frame_field(frame, 4);
335 data = id3_field_getbinarydata(field, &length);
336 if (!data)
337 {
338 fprintf(stderr, "No image data found for frame\n");
339 return 0;
340 }
341 while (length > 0)
342 {
343 ssize_t res;
344
345 if ((res = write(dest, data + done, length)) < 0)
346 {
347 if (errno == EINTR)
348 continue;
349 perror("Unable to write to file");
350 return 0;
351 }
352 length -= res;
353 done += res;
354 }
355 return 1;
356 }
357
358 #define EXT_LEN 14
359
360 static char
get_loader(lopt * opt,ImlibLoader ** loader)361 get_loader(lopt * opt, ImlibLoader ** loader)
362 {
363 union id3_field *field;
364 char const *data;
365 char ext[EXT_LEN + 2];
366
367 ext[EXT_LEN + 1] = '\0';
368 ext[0] = '.';
369
370 field = id3_frame_field(id3_tag_get_frame(opt->ctx->tag, opt->index - 1), 1);
371 data = (char const *)id3_field_getlatin1(field);
372 if (!data)
373 {
374 fprintf(stderr, "No mime type data found for image frame\n");
375 return 0;
376 }
377 if (strncasecmp(data, "image/", 6))
378 {
379 if (!strcmp(data, "-->"))
380 {
381 *loader = NULL;
382 return 1;
383 }
384 fprintf(stderr,
385 "Picture frame with unknown mime-type \'%s\' found\n", data);
386 return 0;
387 }
388 strncpy(ext + 1, data + 6, EXT_LEN);
389 if (!(*loader = __imlib_FindBestLoaderForFile(ext, 0)))
390 {
391 fprintf(stderr, "No loader found for extension %s\n", ext);
392 return 0;
393 }
394 return 1;
395 }
396
397 static const char *const id3_pic_types[] = {
398 /* $00 */ "Other",
399 /* $01 */ "32x32 pixels file icon",
400 /* $02 */ "Other file icon",
401 /* $03 */ "Cover (front)",
402 /* $04 */ "Cover (back)",
403 /* $05 */ "Leaflet page",
404 /* $06 */ "Media",
405 /* $07 */ "Lead artist/lead performer/soloist",
406 /* $08 */ "Artist/performer",
407 /* $09 */ "Conductor",
408 /* $0A */ "Band/Orchestra",
409 /* $0B */ "Composer",
410 /* $0C */ "Lyricist/text writer",
411 /* $0D */ "Recording Location",
412 /* $0E */ "During recording",
413 /* $0F */ "During performance",
414 /* $10 */ "Movie/video screen capture",
415 /* $11 */ "A bright coloured fish",
416 /* $12 */ "Illustration",
417 /* $13 */ "Band/artist logotype",
418 /* $14 */ "Publisher/Studio logotype"
419 };
420
421 #define NUM_OF_ID3_PIC_TYPES \
422 (sizeof(id3_pic_types) / sizeof(id3_pic_types[0]))
423
424 static const char *const id3_text_encodings[] = {
425 /* $00 */ "ISO-8859-1",
426 /* $01 */ "UTF-16 encoded Unicode with BOM",
427 /* $02 */ "UTF-16BE encoded Unicode without BOM",
428 /* $03 */ "UTF-8 encoded Unicode"
429 };
430
431 #define NUM_OF_ID3_TEXT_ENCODINGS \
432 (sizeof(id3_text_encodings) / sizeof(id3_text_encodings[0]))
433
434 static void
write_tags(ImlibImage * im,lopt * opt)435 write_tags(ImlibImage * im, lopt * opt)
436 {
437 struct id3_frame *frame = id3_tag_get_frame(opt->ctx->tag, opt->index - 1);
438 union id3_field *field;
439 unsigned int num_data;
440 char *data;
441
442 if ((field = id3_frame_field(frame, 1)) &&
443 (data = (char *)id3_field_getlatin1(field)))
444 __imlib_AttachTag(im, "mime-type", 0, strdup(data), destructor_data);
445 if ((field = id3_frame_field(frame, 3)) &&
446 (data = (char *)id3_field_getstring(field)))
447 {
448 size_t length;
449 char *dup;
450 id3_ucs4_t *ptr = (id3_ucs4_t *) data;
451
452 while (*ptr)
453 ptr++;
454 length = (ptr - (id3_ucs4_t *) data + 1) * sizeof(id3_ucs4_t);
455 dup = (char *)malloc(length);
456 memcpy(dup, data, length);
457 __imlib_AttachTag(im, "id3-description", 0, dup, destructor_data);
458 }
459 if ((field = id3_frame_field(frame, 0)))
460 {
461 num_data = id3_field_gettextencoding(field);
462 __imlib_AttachTag(im, "id3-description-text-encoding", num_data,
463 num_data < NUM_OF_ID3_TEXT_ENCODINGS ?
464 (char *)id3_text_encodings[num_data] : NULL, NULL);
465 }
466 if ((field = id3_frame_field(frame, 2)))
467 {
468 num_data = id3_field_getint(field);
469 __imlib_AttachTag(im, "id3-picture-type", num_data,
470 num_data < NUM_OF_ID3_PIC_TYPES ?
471 (char *)id3_pic_types[num_data] : NULL, NULL);
472 }
473 __imlib_AttachTag(im, "count", id3_tag_get_numframes(opt->ctx->tag),
474 NULL, NULL);
475 if (opt->cache_level)
476 {
477 context_addref(opt->ctx);
478 __imlib_AttachTag(im, "context", opt->ctx->id,
479 opt->ctx, destructor_context);
480 }
481 __imlib_AttachTag(im, "index", opt->index, NULL, NULL);
482 if (opt->traverse)
483 {
484 char *buf = NULL;
485
486 if ((opt->index + opt->traverse)
487 <= id3_tag_get_numframes(opt->ctx->tag)
488 && (opt->index + opt->traverse) > 0)
489 {
490 buf = (char *)malloc((strlen(im->real_file) + 50) * sizeof(char));
491 sprintf(buf, "%s:index=%d,traverse=%d", im->real_file,
492 opt->index + opt->traverse, opt->traverse);
493 }
494 __imlib_AttachTag(im, "next", 0, buf, destructor_data);
495 }
496 }
497
498 int
load2(ImlibImage * im,int load_data)499 load2(ImlibImage * im, int load_data)
500 {
501 ImlibLoader *loader;
502 lopt opt;
503 int res;
504
505 res = LOAD_FAIL;
506 opt.ctx = NULL;
507
508 if (!get_options(&opt, im))
509 goto fail_context;
510
511 if (!get_loader(&opt, &loader))
512 goto fail_context;
513
514 if (loader)
515 {
516 char tmp[] = "/tmp/imlib2_loader_id3-XXXXXX";
517 int dest;
518
519 if ((dest = mkstemp(tmp)) < 0)
520 {
521 fprintf(stderr, "Unable to create a temporary file\n");
522 goto fail_context;
523 }
524
525 res = extract_pic(id3_tag_get_frame(opt.ctx->tag, opt.index - 1), dest);
526 close(dest);
527
528 if (!res)
529 {
530 unlink(tmp);
531 goto fail_context;
532 }
533
534 res = __imlib_LoadEmbedded(loader, im, tmp, load_data);
535
536 unlink(tmp);
537 }
538 else
539 {
540 /* The tag actually provides a image url rather than image data.
541 * Practically, dunno if such a tag exists on earth :)
542 * Here's the code anyway...
543 */
544 union id3_field *field;
545 id3_length_t length;
546 char const *data;
547 char *url, *file;
548
549 field = id3_frame_field
550 (id3_tag_get_frame(opt.ctx->tag, opt.index - 1), 4);
551 data = (char const *)id3_field_getbinarydata(field, &length);
552 if (!data || !length)
553 {
554 fprintf(stderr, "No link image URL present\n");
555 goto fail_context;
556 }
557 url = (char *)malloc((length + 1) * sizeof(char));
558 strncpy(url, data, length);
559 url[length] = '\0';
560 file = (strncmp(url, "file://", 7) ? url : url + 7);
561 if (!(loader = __imlib_FindBestLoaderForFile(file, 0)))
562 {
563 fprintf(stderr, "No loader found for file %s\n", file);
564 free(url);
565 goto fail_context;
566 }
567
568 res = __imlib_LoadEmbedded(loader, im, file, load_data);
569
570 if (!im->loader)
571 __imlib_AttachTag(im, "id3-link-url", 0, url, destructor_data);
572 else
573 free(url);
574 }
575
576 if (!im->loader)
577 write_tags(im, &opt);
578
579 #ifdef DEBUG
580 if (!im->loader)
581 {
582 ImlibImageTag *cur = im->tags;
583
584 fprintf(stderr, "Tags for file %s:\n", im->file);
585 while (cur)
586 {
587 fprintf(stderr, "\t%s: (%d) %s\n", cur->key,
588 cur->val, (char *)cur->data);
589 cur = cur->next;
590 }
591 }
592 #endif
593
594 res = LOAD_SUCCESS;
595
596 fail_context:
597 if (opt.ctx)
598 context_delref(opt.ctx);
599
600 return res;
601 }
602
603 void
formats(ImlibLoader * l)604 formats(ImlibLoader * l)
605 {
606 static const char *const list_formats[] = { "mp3" };
607 __imlib_LoaderSetFormats(l, list_formats,
608 sizeof(list_formats) / sizeof(char *));
609 }
610