Embedded album art support in MP3/ID3v2 tags.
- Support is limited to non-desync jpeg in id3v2 tags. Other formats (hopefully) follow in the future. - Embedded album art takes precedence over files in album art files. - No additional buffers are used, the jpeg is read directly from the audio file. Flyspray: FS#11216 Author: Yoshihisa Uchida and I git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29259 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
0d902c8c54
commit
f577a6a22c
|
@ -52,6 +52,7 @@
|
|||
#include "albumart.h"
|
||||
#include "jpeg_load.h"
|
||||
#include "bmp.h"
|
||||
#include "playback.h"
|
||||
#endif
|
||||
|
||||
#define GUARD_BUFSIZE (32*1024)
|
||||
|
@ -908,10 +909,12 @@ static bool fill_buffer(void)
|
|||
/* Given a file descriptor to a bitmap file, write the bitmap data to the
|
||||
buffer, with a struct bitmap and the actual data immediately following.
|
||||
Return value is the total size (struct + data). */
|
||||
static int load_image(int fd, const char *path, struct dim *dim)
|
||||
static int load_image(int fd, const char *path, struct bufopen_bitmap_data *data)
|
||||
{
|
||||
int rc;
|
||||
struct bitmap *bmp = (struct bitmap *)&buffer[buf_widx];
|
||||
struct dim *dim = data->dim;
|
||||
struct mp3_albumart *aa = data->embedded_albumart;
|
||||
|
||||
/* get the desired image size */
|
||||
bmp->width = dim->width, bmp->height = dim->height;
|
||||
|
@ -928,8 +931,13 @@ static int load_image(int fd, const char *path, struct dim *dim)
|
|||
- sizeof(struct bitmap);
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
int pathlen = strlen(path);
|
||||
if (strcmp(path + pathlen - 4, ".bmp"))
|
||||
if (aa != NULL)
|
||||
{
|
||||
lseek(fd, aa->pos, SEEK_SET);
|
||||
rc = clip_jpeg_fd(fd, aa->size, bmp, free, FORMAT_NATIVE|FORMAT_DITHER|
|
||||
FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL);
|
||||
}
|
||||
else if (strcmp(path + strlen(path) - 4, ".bmp"))
|
||||
rc = read_jpeg_fd(fd, bmp, free, FORMAT_NATIVE|FORMAT_DITHER|
|
||||
FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL);
|
||||
else
|
||||
|
@ -1010,7 +1018,18 @@ int bufopen(const char *file, size_t offset, enum data_type type,
|
|||
if (fd < 0)
|
||||
return ERR_FILE_ERROR;
|
||||
|
||||
size_t size = filesize(fd);
|
||||
size_t size = 0;
|
||||
#ifdef HAVE_ALBUMART
|
||||
if (type == TYPE_BITMAP)
|
||||
{ /* if albumart is embedded, the complete file is not buffered,
|
||||
* but only the jpeg part; filesize() would be wrong */
|
||||
struct bufopen_bitmap_data *aa = (struct bufopen_bitmap_data*)user_data;
|
||||
if (aa->embedded_albumart)
|
||||
size = aa->embedded_albumart->size;
|
||||
}
|
||||
#endif
|
||||
if (size == 0)
|
||||
size = filesize(fd);
|
||||
bool can_wrap = type==TYPE_PACKET_AUDIO || type==TYPE_CODEC;
|
||||
|
||||
size_t adjusted_offset = offset;
|
||||
|
@ -1058,7 +1077,7 @@ int bufopen(const char *file, size_t offset, enum data_type type,
|
|||
/* Bitmap file: we load the data instead of the file */
|
||||
int rc;
|
||||
mutex_lock(&llist_mod_mutex); /* Lock because load_bitmap yields */
|
||||
rc = load_image(fd, file, (struct dim*)user_data);
|
||||
rc = load_image(fd, file, (struct bufopen_bitmap_data*)user_data);
|
||||
mutex_unlock(&llist_mod_mutex);
|
||||
if (rc <= 0)
|
||||
{
|
||||
|
|
|
@ -190,6 +190,22 @@ enum {
|
|||
ID3_VER_2_4
|
||||
};
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
enum mp3_aa_type {
|
||||
AA_TYPE_UNSYNC = -1,
|
||||
AA_TYPE_UNKNOWN,
|
||||
AA_TYPE_BMP,
|
||||
AA_TYPE_PNG,
|
||||
AA_TYPE_JPG,
|
||||
};
|
||||
|
||||
struct mp3_albumart {
|
||||
enum mp3_aa_type type;
|
||||
int size;
|
||||
off_t pos;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct mp3entry {
|
||||
char path[MAX_PATH];
|
||||
char* title;
|
||||
|
@ -277,6 +293,11 @@ struct mp3entry {
|
|||
long album_peak;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
bool embed_albumart;
|
||||
struct mp3_albumart albumart;
|
||||
#endif
|
||||
|
||||
/* Cuesheet support */
|
||||
struct cuesheet *cuesheet;
|
||||
|
||||
|
|
|
@ -290,6 +290,63 @@ static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
/* parse embed albumart */
|
||||
static int parsealbumart( struct mp3entry* entry, char* tag, int bufferpos )
|
||||
{
|
||||
entry->embed_albumart = false;
|
||||
|
||||
/* we currently don't support unsynchronizing albumart */
|
||||
if (entry->albumart.type == AA_TYPE_UNSYNC)
|
||||
return bufferpos;
|
||||
|
||||
entry->albumart.type = AA_TYPE_UNKNOWN;
|
||||
|
||||
char *start = tag;
|
||||
/* skip text encoding */
|
||||
tag += 1;
|
||||
|
||||
if (memcmp(tag, "image/", 6) == 0)
|
||||
{
|
||||
/* ID3 v2.3+ */
|
||||
tag += 6;
|
||||
if (strcmp(tag, "jpeg") == 0)
|
||||
{
|
||||
entry->albumart.type = AA_TYPE_JPG;
|
||||
tag += 5;
|
||||
}
|
||||
else if (strcmp(tag, "png") == 0)
|
||||
{
|
||||
entry->albumart.type = AA_TYPE_PNG;
|
||||
tag += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* ID3 v2.2 */
|
||||
if (memcmp(tag, "JPG", 3) == 0)
|
||||
entry->albumart.type = AA_TYPE_JPG;
|
||||
else if (memcmp(tag, "PNG", 3) == 0)
|
||||
entry->albumart.type = AA_TYPE_PNG;
|
||||
tag += 3;
|
||||
}
|
||||
|
||||
if (entry->albumart.type != AA_TYPE_UNKNOWN)
|
||||
{
|
||||
/* skip picture type */
|
||||
tag += 1;
|
||||
/* skip description */
|
||||
tag = strchr(tag, '\0') + 1;
|
||||
/* fixup offset&size for image data */
|
||||
entry->albumart.pos += tag - start;
|
||||
entry->albumart.size -= tag - start;
|
||||
entry->embed_albumart = true;
|
||||
}
|
||||
/* return bufferpos as we didn't store anything in id3v2buf */
|
||||
return bufferpos;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* parse user defined text, looking for album artist and replaygain
|
||||
* information.
|
||||
*/
|
||||
|
@ -439,6 +496,10 @@ static const struct tag_resolver taglist[] = {
|
|||
{ "COM", 3, offsetof(struct mp3entry, comment), NULL, false },
|
||||
{ "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false },
|
||||
{ "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false },
|
||||
#ifdef HAVE_ALBUMART
|
||||
{ "APIC", 4, 0, &parsealbumart, true },
|
||||
{ "PIC", 3, 0, &parsealbumart, true },
|
||||
#endif
|
||||
{ "TXXX", 4, 0, &parseuser, false },
|
||||
#if CONFIG_CODEC == SWCODEC
|
||||
{ "RVA2", 4, 0, &parserva2, true },
|
||||
|
@ -961,6 +1022,21 @@ void setid3v2title(int fd, struct mp3entry *entry)
|
|||
if (ptag && !*ptag)
|
||||
*ptag = tag;
|
||||
|
||||
/* albumart */
|
||||
if ((!entry->embed_albumart) &&
|
||||
((tr->tag_length == 4 && !memcmp( header, "APIC", 4)) ||
|
||||
(tr->tag_length == 3 && !memcmp( header, "PIC" , 3))))
|
||||
{
|
||||
if (unsynch || (global_unsynch && version <= ID3_VER_2_3))
|
||||
entry->albumart.type = AA_TYPE_UNSYNC;
|
||||
else
|
||||
{
|
||||
entry->albumart.pos = lseek(fd, 0, SEEK_CUR) - framelen;
|
||||
entry->albumart.size = totframelen;
|
||||
entry->albumart.type = AA_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if( tr->ppFunc )
|
||||
bufferpos = tr->ppFunc(entry, tag, bufferpos);
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ static struct cuesheet *curr_cue = NULL;
|
|||
#define MAX_MULTIPLE_AA SKINNABLE_SCREENS_COUNT
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
|
||||
static struct albumart_slot {
|
||||
struct dim dim; /* holds width, height of the albumart */
|
||||
int used; /* counter, increments if something uses it */
|
||||
|
@ -228,7 +229,6 @@ static bool audio_have_tracks(void);
|
|||
static void audio_reset_buffer(void);
|
||||
static void audio_stop_playback(void);
|
||||
|
||||
|
||||
/**************************************/
|
||||
|
||||
|
||||
|
@ -647,6 +647,7 @@ bool audio_peek_track(struct mp3entry** id3, int offset)
|
|||
}
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
|
||||
int playback_current_aa_hid(int slot)
|
||||
{
|
||||
if (slot < 0)
|
||||
|
@ -656,13 +657,13 @@ int playback_current_aa_hid(int slot)
|
|||
|
||||
cur_idx = track_ridx + offset;
|
||||
cur_idx &= MAX_TRACK_MASK;
|
||||
|
||||
return tracks[cur_idx].aa_hid[slot];
|
||||
}
|
||||
|
||||
int playback_claim_aa_slot(struct dim *dim)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* first try to find a slot already having the size to reuse it
|
||||
* since we don't want albumart of the same size buffered multiple times */
|
||||
FOREACH_ALBUMART(i)
|
||||
|
@ -693,6 +694,7 @@ void playback_release_aa_slot(int slot)
|
|||
{
|
||||
/* invalidate the albumart_slot */
|
||||
struct albumart_slot *aa_slot = &albumart_slots[slot];
|
||||
|
||||
if (aa_slot->used > 0)
|
||||
aa_slot->used--;
|
||||
}
|
||||
|
@ -1315,19 +1317,37 @@ static void audio_finish_load_track(void)
|
|||
{
|
||||
int i;
|
||||
char aa_path[MAX_PATH];
|
||||
|
||||
FOREACH_ALBUMART(i)
|
||||
{
|
||||
/* albumart_slots may change during a yield of bufopen,
|
||||
* but that's no problem */
|
||||
if (tracks[track_widx].aa_hid[i] >= 0 || !albumart_slots[i].used)
|
||||
continue;
|
||||
/* find_albumart will error out if the wps doesn't have AA */
|
||||
if (find_albumart(track_id3, aa_path, sizeof(aa_path),
|
||||
&(albumart_slots[i].dim)))
|
||||
{
|
||||
int aa_hid = bufopen(aa_path, 0, TYPE_BITMAP,
|
||||
&(albumart_slots[i].dim));
|
||||
|
||||
/* we can only decode jpeg for embedded AA */
|
||||
bool embedded_albumart =
|
||||
track_id3->embed_albumart && track_id3->albumart.type == AA_TYPE_JPG;
|
||||
/* find_albumart will error out if the wps doesn't have AA */
|
||||
if (embedded_albumart || find_albumart(track_id3, aa_path,
|
||||
sizeof(aa_path), &(albumart_slots[i].dim)))
|
||||
{
|
||||
int aa_hid;
|
||||
struct bufopen_bitmap_data user_data = {
|
||||
.dim = &(albumart_slots[i].dim),
|
||||
.embedded_albumart = NULL,
|
||||
};
|
||||
if (embedded_albumart)
|
||||
{
|
||||
user_data.embedded_albumart = &(track_id3->albumart);
|
||||
aa_hid = bufopen(track_id3->path, 0,
|
||||
TYPE_BITMAP, &user_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
aa_hid = bufopen(aa_path, 0, TYPE_BITMAP,
|
||||
&user_data);
|
||||
}
|
||||
if(aa_hid == ERR_BUFFER_FULL)
|
||||
{
|
||||
filling = STATE_FULL;
|
||||
|
@ -1342,7 +1362,6 @@ static void audio_finish_load_track(void)
|
|||
tracks[track_widx].aa_hid[i] = aa_hid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#ifdef HAVE_ALBUMART
|
||||
|
||||
#include "bmp.h"
|
||||
#include "metadata.h"
|
||||
/*
|
||||
* Returns the handle id of the buffered albumart for the given slot id
|
||||
**/
|
||||
|
@ -50,6 +51,12 @@ int playback_claim_aa_slot(struct dim *dim);
|
|||
*
|
||||
* Save to call from other threads */
|
||||
void playback_release_aa_slot(int slot);
|
||||
|
||||
struct bufopen_bitmap_data {
|
||||
struct dim *dim;
|
||||
struct mp3_albumart *embedded_albumart;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/* Functions */
|
||||
|
|
|
@ -75,12 +75,12 @@ struct jpeg
|
|||
{
|
||||
#ifdef JPEG_FROM_MEM
|
||||
unsigned char *data;
|
||||
unsigned long len;
|
||||
#else
|
||||
int fd;
|
||||
int buf_left;
|
||||
int buf_index;
|
||||
#endif
|
||||
unsigned long len;
|
||||
unsigned long int bitbuf;
|
||||
int bitbuf_bits;
|
||||
int marker_ind;
|
||||
|
@ -888,8 +888,12 @@ INLINE void jpeg_putc(struct jpeg* p_jpeg)
|
|||
#else
|
||||
INLINE void fill_buf(struct jpeg* p_jpeg)
|
||||
{
|
||||
p_jpeg->buf_left = read(p_jpeg->fd, p_jpeg->buf, JPEG_READ_BUF_SIZE);
|
||||
p_jpeg->buf_left = read(p_jpeg->fd, p_jpeg->buf,
|
||||
(p_jpeg->len >= JPEG_READ_BUF_SIZE)?
|
||||
JPEG_READ_BUF_SIZE : p_jpeg->len);
|
||||
p_jpeg->buf_index = 0;
|
||||
if (p_jpeg->buf_left > 0)
|
||||
p_jpeg->len -= p_jpeg->buf_left;
|
||||
}
|
||||
|
||||
static unsigned char *jpeg_getc(struct jpeg* p_jpeg)
|
||||
|
@ -1960,7 +1964,9 @@ block_end:
|
|||
*
|
||||
*****************************************************************************/
|
||||
#ifndef JPEG_FROM_MEM
|
||||
int read_jpeg_file(const char* filename,
|
||||
int clip_jpeg_file(const char* filename,
|
||||
int offset,
|
||||
unsigned long jpeg_size,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
|
@ -1975,11 +1981,20 @@ int read_jpeg_file(const char* filename,
|
|||
DEBUGF("read_jpeg_file: can't open '%s', rc: %d\n", filename, fd);
|
||||
return fd * 10 - 1;
|
||||
}
|
||||
|
||||
ret = read_jpeg_fd(fd, bm, maxsize, format, cformat);
|
||||
lseek(fd, offset, SEEK_SET);
|
||||
ret = clip_jpeg_fd(fd, jpeg_size, bm, maxsize, format, cformat);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int read_jpeg_file(const char* filename,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
const struct custom_format *cformat)
|
||||
{
|
||||
return clip_jpeg_file(filename, 0, 0, bm, maxsize, format, cformat);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int calc_scale(int in_size, int out_size)
|
||||
|
@ -2014,10 +2029,11 @@ int get_jpeg_dim_mem(unsigned char *data, unsigned long len,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int decode_jpeg_mem(unsigned char *data, unsigned long len,
|
||||
int decode_jpeg_mem(unsigned char *data,
|
||||
#else
|
||||
int read_jpeg_fd(int fd,
|
||||
int clip_jpeg_fd(int fd,
|
||||
#endif
|
||||
unsigned long len,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
|
@ -2039,11 +2055,13 @@ int read_jpeg_fd(int fd,
|
|||
return -1;
|
||||
#endif
|
||||
memset(p_jpeg, 0, sizeof(struct jpeg));
|
||||
p_jpeg->len = len;
|
||||
#ifdef JPEG_FROM_MEM
|
||||
p_jpeg->data = data;
|
||||
p_jpeg->len = len;
|
||||
#else
|
||||
p_jpeg->fd = fd;
|
||||
if (p_jpeg->len == 0)
|
||||
p_jpeg->len = filesize(p_jpeg->fd);
|
||||
#endif
|
||||
status = process_markers(p_jpeg);
|
||||
#ifndef JPEG_FROM_MEM
|
||||
|
@ -2212,4 +2230,13 @@ int read_jpeg_fd(int fd,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int read_jpeg_fd(int fd,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
const struct custom_format *cformat)
|
||||
{
|
||||
return clip_jpeg_fd(fd, 0, bm, maxsize, format, cformat);
|
||||
}
|
||||
|
||||
/**************** end JPEG code ********************/
|
||||
|
|
|
@ -44,4 +44,27 @@ int read_jpeg_fd(int fd,
|
|||
int format,
|
||||
const struct custom_format *cformat);
|
||||
|
||||
/**
|
||||
* read embedded jpeg files as above. Needs an offset and length into
|
||||
* the file
|
||||
**/
|
||||
int clip_jpeg_file(const char* filename,
|
||||
int offset,
|
||||
unsigned long jpeg_size,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
const struct custom_format *cformat);
|
||||
|
||||
/**
|
||||
* read embedded jpeg files as above. Needs an open file descripter, and
|
||||
* assumes the caller has lseek()'d to the start of the jpeg blob
|
||||
**/
|
||||
int clip_jpeg_fd(int fd,
|
||||
unsigned long jpeg_size,
|
||||
struct bitmap *bm,
|
||||
int maxsize,
|
||||
int format,
|
||||
const struct custom_format *cformat);
|
||||
|
||||
#endif /* _JPEG_JPEG_DECODER_H */
|
||||
|
|
Loading…
Reference in New Issue