ramkumar's id3 updates

SVN revision: 16083
This commit is contained in:
Carsten Haitzler 2005-08-09 09:11:02 +00:00
parent 90fdea7686
commit cfc76f12ed
2 changed files with 596 additions and 75 deletions

122
README.ID3 Normal file
View File

@ -0,0 +1,122 @@
A loader for ID3v2 picture tags in audio files -*- text -*-
----------------------------------------------
INTRODUCTION
------------
ID3v2 is a format used to embed metadata within popular audio formats like
mp3. ID3v2 officially describes a wide range of metadata that could possibly be
associated with some audio/music, like Artist, Album, Composer etc. (apart from
being extensible to allow nonstandard arbitrary metadata as well) These pieces
of data are held as `frames' in an ID3v2 tag in the audio file. One such type of
frame is the `Associated Picture' (APIC) frame used to store pictures associated
with the audio (like say, the picture of the lead artist). There could be
multiple such frames in a file. Visit http://www.id3.org for more information.
Well, I suppose that makes the purpose of this image loader clear. This loader
depends on the libid3tag library (atleast version 0.15). This is a helper
library for the MAD, a GPL'd high quality MPEG audio decoder, and can be found
in the same place as MAD. (http://www.underbit.com/products/mad)
KEY SYNTAX
----------
The filename to be passed to the loader, is as in imlib2 convention, of the form
`filename.mp3:key'. The key is a comma separated list of `parameter=value'
pairs, spaces are significant. Any item without an `=' is treated as the value
for the parameter `index'. In case of repetition, the last occurrence is
used. The supported parameters are:
+----------+--------------------------------------------------------------------+
| index |Numeric value. 1-based index of picture frame to be extracted. |
| |Defaults to 1, the first frame. |
+----------+--------------------------------------------------------------------+
| |An image loaded by the loader can be made to contain a `next' image |
| |tag containing the full filename:key string of some other image in |
| |the file, when this parameter in non-zero. In such a case, the next |
| traverse |tag points to `index+N'th image of the file, where N is the value |
| |given here. Common values include `1', for the next image, and `-1' |
| |for the previous image. Default is `1' if there is no key for the |
| |image to be loaded, and `0' otherwise. |
+----------+--------------------------------------------------------------------+
| |An opaque integer got from the `context' tag of an existing |
| |image. See the section below on the tags provided with each |
| |image. `tag' here refers to data which can be attached to any imlib |
| |image, and is not to be confused with ID3 tags. The context of an |
| |existing image passed as a parameter indicates that both images come|
| |from the same file. This saves some work for the loader in such a |
| context |case. This parameter is normally not necessary, as such a case |
| |usually occurs when the filename of both the images are the same, |
| |and only the key differs, and this case is taken care of by the |
| |loader automatically. Obscure cases of the same tag for different |
| |filenames (like, symlinks for example) can be taken care by this |
| |parameter. A context is valid as long as any of the images using it |
| |are valid. |
+----------+--------------------------------------------------------------------+
TAGS OUTPUT
-----------
+---------------+---------------------------------------------------------------+
| index |1-based index of picture frame in the file which the image |
| |refers to. |
+---------------+---------------------------------------------------------------+
| count |The total number of picture frames present in the file to which|
| |this picture frame belongs. |
+---------------+---------------------------------------------------------------+
| |An opaque id corresponding to the ID3v2 tag from which the |
| context |picture came. Images from the same tag have the same |
| |context. See the context parameter above for a possible use. |
+---------------+---------------------------------------------------------------+
| |A filename:key string of a related image in the same ID3v2 tag,|
| |as required by the `traverse' argument passed for the |
| next |file. This imlib tag is absent when traverse is 0, and it's |
| |value is NULL when no image frame satisfying the given traverse|
| |value is present (like traverse=1 called with the last image |
| |frame). |
+---------------+---------------------------------------------------------------+
| mime-type |The mime type of the picture rendered. |
+---------------+---------------------------------------------------------------+
| id3-picture |A string describing the picture type, amongst a set of |
| -type |predefined types described by the ID3v2 standard. |
+---------------+---------------------------------------------------------------+
| |An integer between 0-3 describing the text encoding used by the|
| |id3-description imlib tag. The string in the tag gives the |
| |corresponding encoding name. The possible values are: |
| +---+-----------------------------------------------------------+
|id3-description| 0 | ISO-8859-1 |
|-text-encoding +---+-----------------------------------------------------------+
| | 1 | UTF-16 encoded Unicode with BOM |
| +---+-----------------------------------------------------------+
| | 2 | UTF-16BE encoded Unicode without BOM |
| +---+-----------------------------------------------------------+
| | 3 | UTF-8 encoded Unicode |
+---------------+---+-----------------------------------------------------------+
| |A string describing the image, as given in the ID3 tag. Note |
| |that this is different from any comment that might be present |
|id3-description|in the image itself, as allowed by the image format. The |
| |encoding used for the string is given by the imlib tag |
| |id3-description-text-encoding. |
+---------------+---------------------------------------------------------------+
Eg: to process all images in a file, the following pseudocode should work:
loadname = `foo.mp3' // with no key
do {
image = load-image (loadname)
// do whatever you want with the image
loadname = imlib-tag (image, `next')
} while (loadname != NULL)
PERFORMANCE ISSUES
------------------
The loader parses picture data from audio files and caches them, as long as any
of the images using the data exist. This is useful when there are many images in
the same file, as loading subsequent images use this cache. So, caching is
turned off when there is only one image in the file, and turned on
otherwise. When memory consumption is of importance, developers likely to use
this loader frequently need to keep this in mind when deciding on the imlib
cache size - with caching on, as long as the image remains in use or in cache,
this parsed data stays along.

View File

@ -9,31 +9,286 @@
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include "image.h"
#include <id3tag.h>
int extract_pic (struct id3_frame* frame, int dest, char* ext, int extlen)
#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));
id3_file_close (file);
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;
long length;
int done = 0;
field = id3_frame_field (frame, 1);
data = id3_field_getlatin1 (field);
if (! data) {
fprintf (stderr, "No mime type data found for image frame\n");
return 0;
}
if (strncmp (data, "image/", 6)) {
fprintf (stderr,
"Picture frame with unknown mime-type %s found\n",
data);
return 0;
}
strncpy (ext, data + 6, extlen);
field = id3_frame_field (frame, 4);
data = id3_field_getbinarydata (field, &length);
if (! data) {
@ -54,85 +309,229 @@ int extract_pic (struct id3_frame* frame, int dest, char* ext, int extlen)
return 1;
}
/* Loader for ID3v2 tags in audio files.
* ID3v2 allows for 'Attached Picture' frames to be embedded in the file.
* A numeric key is supported, and indicates the zero-based frame index
* to be extracted, in case more than one such frame is found.
* Defaults to 0, or the first picture frame.
*/
#define EXT_LEN 14
static char get_loader (lopt* opt, ImlibLoader** loader)
{
union id3_field* field;
unsigned 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 = 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;
char *file, tmp[] = "/tmp/imlib2_loader_id3-XXXXXX", *p;
char real_ext[16];
int res, dest, pic_index = 0;
struct id3_file* tagfile;
struct id3_tag* tag;
struct id3_frame* frame;
lopt opt;
int res;
assert (im);
p = strrchr(im->real_file, '.');
if (! (p && p != im->real_file && !strcmp (p + 1, "mp3"))) {
if (! get_options (&opt, im))
return 0;
}
if (im->key)
pic_index = atoi (im->key);
if (! get_loader (&opt, &loader))
goto fail_context;
tagfile = id3_file_open (im->real_file, ID3_FILE_MODE_READONLY);
if (! tagfile) {
fprintf (stderr, "Unable to open tagged file %s: %s",
im->real_file, strerror (errno));
return 0;
}
tag = id3_file_tag (tagfile);
if (! tag) {
fprintf (stderr, "Unable to find ID3v2 tags in file\n");
return 0;
}
frame = id3_tag_findframe (tag, "APIC", pic_index);
if (! frame) {
fprintf (stderr, "No picture frame # %d found\n", pic_index);
return 0;
}
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");
id3_file_close (tagfile);
return 0;
}
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);
real_ext[15] = '\0';
real_ext[0] = '.';
res = extract_pic (frame, dest, real_ext + 1, 14);
close (dest);
id3_file_close (tagfile);
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;
if (!res) {
unlink (tmp);
return 0;
} 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;
long 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 (!(loader = __imlib_FindBestLoaderForFile (real_ext, 0))) {
fprintf (stderr, "No loader found for extension %s\n", real_ext);
unlink (tmp);
return 0;
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
/* remember the original filename */
file = strdup (im->real_file);
context_delref (opt.ctx);
return res;
free (im->real_file);
im->real_file = strdup (tmp);
loader->load (im, progress, progress_granularity, immediate_load);
free (im->real_file);
im->real_file = file;
unlink (tmp);
return 1;
fail_context:
context_delref (opt.ctx);
return 0;
}
void formats (ImlibLoader *l)