forked from old/legacy-imlib2
546 lines
12 KiB
C
546 lines
12 KiB
C
#include "loader_common.h"
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <id3tag.h>
|
|
|
|
#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]);
|
|
}
|