#ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include "image.h" #include #if ! defined (__STDC_VERSION__) || __STDC_VERSION__ < 199901L # if __GNUC__ >= 2 # define inline __inline__ # else # define inline # endif #endif #ifdef __GNUC__ # define UNLIKELY(exp) __builtin_expect ((exp), 0) #else # define UNLIKELY(exp) (exp) #endif typedef struct context { int id; char* filename; struct id3_tag* tag; int refcount; struct context* next; } context; static context* id3_ctxs = NULL; static inline struct id3_frame* id3_tag_get_frame (struct id3_tag* tag, size_t index) { return tag->frames[index]; } static inline size_t id3_tag_get_numframes (struct id3_tag* tag) { return tag->nframes; } static inline char const* id3_frame_id (struct id3_frame* frame) { return frame->id; } static context* context_create (const char* filename) { context* node = (context*) malloc (sizeof (context)); context *ptr, *last; int last_id = INT_MAX; node->refcount = 1; { struct id3_file* file; struct id3_tag* tag; unsigned int i; file = id3_file_open (filename, ID3_FILE_MODE_READONLY); if (! file) { fprintf (stderr, "Unable to open tagged file %s: %s\n", filename, strerror (errno)); goto fail_free; } tag = id3_file_tag (file); if (! tag) { fprintf (stderr, "Unable to find ID3v2 tags in file %s\n", filename); id3_file_close (file); goto fail_free; } node->tag = id3_tag_new (); for (i = 0; i < id3_tag_get_numframes (tag); i ++) if (! strcmp (id3_frame_id (id3_tag_get_frame (tag, i)), "APIC")) id3_tag_attachframe (node->tag, id3_tag_get_frame (tag, i)); id3_file_close (file); } node->filename = strdup (filename); if (! id3_ctxs) { node->id = 1; node->next = NULL; id3_ctxs = node; return node; } ptr = id3_ctxs; last = NULL; while (UNLIKELY (ptr && (ptr->id + 1) >= last_id)) { last_id = ptr->id; last = ptr; ptr = ptr->next; } /* Paranoid! this can occur only if there are INT_MAX contexts :) */ if (UNLIKELY (ptr == NULL)) { fprintf (stderr, "Too many open ID3 contexts\n"); goto fail_close; } node->id = ptr->id + 1; if (UNLIKELY (last != NULL)) { node->next = last->next; last->next = node; } else { node->next = id3_ctxs; id3_ctxs = node; } return node; fail_close: free (node->filename); id3_tag_delete (node->tag); fail_free: free (node); return NULL; } static void context_destroy (context* ctx) { id3_tag_delete (ctx->tag); free (ctx->filename); free (ctx); } static inline void context_addref (context* ctx) { ctx->refcount ++; } static context* context_get (int id) { context* ptr = id3_ctxs; while (ptr) { if (ptr->id == id) { context_addref (ptr); return ptr; } ptr = ptr->next; } fprintf (stderr, "No context by handle %d found\n", id); return NULL; } static context* context_get_by_name (const char* name) { context* ptr = id3_ctxs; while (ptr) { if (! strcmp (name, ptr->filename)) { context_addref (ptr); return ptr; } ptr = ptr->next; } return NULL; } static void context_delref (context* ctx) { ctx->refcount --; if (ctx->refcount <= 0) { context *last = NULL, *ptr = id3_ctxs; while (ptr) { if (ptr == ctx) { if (last) last->next = ctx->next; else id3_ctxs = ctx->next; context_destroy (ctx); return; } last = ptr; ptr = ptr->next; } } } static int str2int (char* str, int old) { long index; errno = 0; index = strtol (str, NULL, 10); return ((errno || index > INT_MAX) ? old : (int)index); } static size_t str2uint (char* str, size_t old) { unsigned long index; errno = 0; index = strtoul (str, NULL, 10); return ((errno || index > UINT_MAX) ? old : (size_t)index); } static void destructor_data (ImlibImage* im, void* data) { free (data); } static void destructor_context (ImlibImage* im, void* data) { context_delref ((context*)data); } typedef struct lopt { context* ctx; size_t index; int traverse; char cache_level; } lopt; static char get_options (lopt* opt, ImlibImage* im) { size_t handle = 0, index = 0, traverse = 0; context* ctx; if (im->key) { char* key = strdup (im->key); char* tok = strtok (key, ","); traverse = 0; while (tok) { char* value = strchr (tok, '='); if (! value) { value = tok; tok = "index"; } else { *value = '\0'; value ++; } if (! strcasecmp (tok, "index")) index = str2uint (value, index); else if (! strcasecmp (tok, "context")) handle = str2uint (value, handle); else if (! strcasecmp (tok, "traverse")) traverse = str2int (value, traverse); tok = strtok (NULL, ","); } free (key); } else traverse = 1; if (! handle) { ImlibImageTag* htag = __imlib_GetTag (im, "context"); if (htag && htag->val) handle = htag->val; } if (handle) ctx = context_get (handle); else if (! (ctx = context_get_by_name (im->real_file)) && ! (ctx = context_create (im->real_file))) return 0; if (! index) { ImlibImageTag* htag = __imlib_GetTag (im, "index"); if (htag && htag->val) index = htag->val; } if (index < 0 || index > id3_tag_get_numframes (ctx->tag) || (index == 0 && id3_tag_get_numframes (ctx->tag) < 1)) { if (index) fprintf (stderr, "No picture frame # %d found\n", index); context_delref (ctx); return 0; } if (! index) index = 1; opt->ctx = ctx; opt->index = index; opt->traverse = traverse; opt->cache_level = (id3_tag_get_numframes (ctx->tag) > 1 ? 1 : 0); return 1; } static int extract_pic (struct id3_frame* frame, int dest) { union id3_field* field; unsigned char const * data; id3_length_t length; int done = 0; field = id3_frame_field (frame, 4); data = id3_field_getbinarydata (field, &length); if (! data) { fprintf (stderr, "No image data found for frame\n"); return 0; } while (length > 0) { ssize_t res; if ((res = write (dest, data + done, length)) < 0) { if (errno == EINTR) continue; perror ("Unable to write to file"); return 0; } length -= res; done += res; } return 1; } #define EXT_LEN 14 static char get_loader (lopt* opt, ImlibLoader** loader) { union id3_field* field; char const * data; char ext[EXT_LEN + 2]; ext[EXT_LEN + 1] = '\0'; ext[0] = '.'; field = id3_frame_field (id3_tag_get_frame (opt->ctx->tag, opt->index - 1), 1); data = (char const*) id3_field_getlatin1 (field); if (! data) { fprintf (stderr, "No mime type data found for image frame\n"); return 0; } if (strncasecmp (data, "image/", 6)) { if (! strcmp (data, "-->")) { *loader = NULL; return 1; } fprintf (stderr, "Picture frame with unknown mime-type \'%s\' found\n", data); return 0; } strncpy (ext + 1, data + 6, EXT_LEN); if (! (*loader = __imlib_FindBestLoaderForFile (ext, 0))) { fprintf (stderr, "No loader found for extension %s\n", ext); return 0; } return 1; } static char* id3_pic_types [] = { /* $00 */ "Other", /* $01 */ "32x32 pixels file icon", /* $02 */ "Other file icon", /* $03 */ "Cover (front)", /* $04 */ "Cover (back)", /* $05 */ "Leaflet page", /* $06 */ "Media", /* $07 */ "Lead artist/lead performer/soloist", /* $08 */ "Artist/performer", /* $09 */ "Conductor", /* $0A */ "Band/Orchestra", /* $0B */ "Composer", /* $0C */ "Lyricist/text writer", /* $0D */ "Recording Location", /* $0E */ "During recording", /* $0F */ "During performance", /* $10 */ "Movie/video screen capture", /* $11 */ "A bright coloured fish", /* $12 */ "Illustration", /* $13 */ "Band/artist logotype", /* $14 */ "Publisher/Studio logotype" }; static char* id3_text_encodings [] = { /* $00 */ "ISO-8859-1", /* $01 */ "UTF-16 encoded Unicode with BOM", /* $02 */ "UTF-16BE encoded Unicode without BOM", /* $03 */ "UTF-8 encoded Unicode" }; static void write_tags (ImlibImage* im, lopt* opt) { struct id3_frame* frame = id3_tag_get_frame (opt->ctx->tag, opt->index - 1); union id3_field* field; int num_data; char* data; if ((field = id3_frame_field (frame, 1)) && (data = (char*) id3_field_getlatin1 (field))) __imlib_AttachTag (im, "mime-type", 0, strdup (data), destructor_data); if ((field = id3_frame_field (frame, 3)) && (data = (char*) id3_field_getstring (field))) { size_t length; char* dup; id3_ucs4_t* ptr = (id3_ucs4_t*)data; while (*ptr) ptr ++; length = (ptr - (id3_ucs4_t*)data + 1) * sizeof (id3_ucs4_t); dup = (char*) malloc (length); memcpy (dup, data, length); __imlib_AttachTag (im, "id3-description", 0, dup, destructor_data); } if (field = id3_frame_field (frame, 0)) __imlib_AttachTag (im, "id3-description-text-encoding", (num_data = (int) id3_field_gettextencoding (field)), num_data < sizeof (id3_text_encodings) ? id3_text_encodings[num_data] : NULL, NULL); if (field = id3_frame_field (frame, 2)) __imlib_AttachTag (im, "id3-picture-type", (num_data = id3_field_getint (field)), num_data < sizeof (id3_pic_types) ? id3_pic_types[num_data] : NULL, NULL); __imlib_AttachTag (im, "count", id3_tag_get_numframes (opt->ctx->tag), NULL, NULL); if (opt->cache_level) { context_addref (opt->ctx); __imlib_AttachTag (im, "context", opt->ctx->id, opt->ctx, destructor_context); } __imlib_AttachTag (im, "index", opt->index, NULL, NULL); if (opt->traverse) { char* buf = NULL; if ((opt->index + opt->traverse) <= id3_tag_get_numframes (opt->ctx->tag) && (opt->index + opt->traverse) > 0) { buf = (char*) malloc ((strlen (im->real_file) + 50) * sizeof (char)); sprintf (buf, "%s:index=%d,traverse=%d", im->real_file, opt->index + opt->traverse, opt->traverse); } __imlib_AttachTag (im, "next", 0, buf, destructor_data); } } char load (ImlibImage *im, ImlibProgressFunction progress, char progress_granularity, char immediate_load) { ImlibLoader *loader; lopt opt; int res; struct stat st; assert (im); if (stat(im->real_file, &st) < 0) return 0; if (! get_options (&opt, im)) return 0; if (! get_loader (&opt, &loader)) goto fail_context; if (loader) { char *ofile, tmp[] = "/tmp/imlib2_loader_id3-XXXXXX"; int dest; if ((dest = mkstemp (tmp)) < 0) { fprintf (stderr, "Unable to create a temporary file\n"); goto fail_context; } res = extract_pic (id3_tag_get_frame (opt.ctx->tag, opt.index - 1), dest); close (dest); if (! res) { unlink (tmp); goto fail_context; } ofile = im->real_file; im->real_file = strdup (tmp); res = loader->load (im, progress, progress_granularity, immediate_load); free (im->real_file); im->real_file = ofile; unlink (tmp); } else { /* The tag actually provides a image url rather than image data. * Practically, dunno if such a tag exists on earth :) * Here's the code anyway... */ union id3_field* field; id3_length_t length; char const* data; char *url, *file, *ofile; field = id3_frame_field (id3_tag_get_frame (opt.ctx->tag, opt.index - 1), 4); data = (char const*) id3_field_getbinarydata (field, &length); if (! data || ! length) { fprintf (stderr, "No link image URL present\n"); goto fail_context; } url = (char*) malloc ((length + 1) * sizeof (char)); strncpy (url, data, length); url[length] = '\0'; file = (strncmp (url, "file://", 7) ? url : url + 7); if (! (loader = __imlib_FindBestLoaderForFile (file, 0))) { fprintf (stderr, "No loader found for file %s\n", file); free (url); goto fail_context; } ofile = im->real_file; im->real_file = file; res = loader->load (im, progress, progress_granularity, immediate_load); if (! im->loader) __imlib_AttachTag (im, "id3-link-url", 0, url, destructor_data); else free (url); im->real_file = ofile; } if (! im->loader) write_tags (im, &opt); #ifdef DEBUG if (! im->loader) { ImlibImageTag* cur = im->tags; fprintf (stderr, "Tags for file %s:\n", im->file); while (cur) { fprintf (stderr, "\t%s: (%d) %s\n", cur->key, cur->val, (char*) cur->data); cur = cur->next; } } #endif context_delref (opt.ctx); return res; fail_context: context_delref (opt.ctx); return 0; } void formats (ImlibLoader *l) { /* this is the only bit you have to change... */ char *list_formats[] = {"mp3"}; int i; /* don't bother changing any of this - it just reads this in * and sets the struct values and makes copies */ l->num_formats = sizeof (list_formats) / sizeof (char *); l->formats = (char**) malloc (sizeof (char *) * l->num_formats); for (i = 0; i < l->num_formats; i++) l->formats[i] = strdup (list_formats[i]); }