From 9d4bed7ff06818b098926932db824a8d6532bfee Mon Sep 17 00:00:00 2001 From: Nicolas Pennequin Date: Sun, 11 Nov 2007 12:29:37 +0000 Subject: Album art support. Based on FS#3045, but heavily modified to adapt to MoB and for cleanness. The cover pictures are loaded from external bitmaps. JPEG and embedded art are not supported. The pictures will only be drawn on the main display. There is no resizing but it is possible to specify the WPS bitmap size in the bitmap names (e.g. cover.100x100.bmp). The bitmaps are stored in the main buffer and read directly from there. Currently, duplicate bitmaps will simply be present several times in the buffer, but this will be improved. To enable for a target, #define HAVE_ALBUMART in its config file. For more information, see the wiki page: http://www.rockbox.org/wiki/AlbumArt. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15572 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 3 + apps/buffering.c | 47 +++++- apps/buffering.h | 2 +- apps/gui/gwps-common.c | 14 ++ apps/gui/gwps.c | 15 ++ apps/gui/gwps.h | 42 ++++++ apps/gui/wps_parser.c | 225 ++++++++++++++++++++++++++++ apps/playback.c | 33 +++++ apps/recorder/albumart.c | 285 ++++++++++++++++++++++++++++++++++++ apps/recorder/albumart.h | 39 +++++ firmware/export/audio.h | 3 + firmware/export/config-c200.h | 3 + firmware/export/config-e200.h | 3 + firmware/export/config-gigabeat.h | 3 + firmware/export/config-h10.h | 3 + firmware/export/config-h100.h | 3 + firmware/export/config-h10_5gb.h | 3 + firmware/export/config-h120.h | 3 + firmware/export/config-h300.h | 3 + firmware/export/config-iaudiom5.h | 3 + firmware/export/config-iaudiox5.h | 3 + firmware/export/config-ipod1g2g.h | 3 + firmware/export/config-ipod3g.h | 3 + firmware/export/config-ipod4g.h | 3 + firmware/export/config-ipodcolor.h | 3 + firmware/export/config-ipodmini.h | 3 + firmware/export/config-ipodmini2g.h | 3 + firmware/export/config-ipodnano.h | 3 + firmware/export/config-ipodvideo.h | 3 + 29 files changed, 757 insertions(+), 5 deletions(-) create mode 100644 apps/recorder/albumart.c create mode 100644 apps/recorder/albumart.h diff --git a/apps/SOURCES b/apps/SOURCES index d50da979ad..5e097049c2 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -75,6 +75,9 @@ recorder/bmp.c recorder/icons.c recorder/keyboard.c recorder/peakmeter.c +#ifdef HAVE_ALBUMART +recorder/albumart.c +#endif #ifdef HAVE_LCD_COLOR gui/color_picker.c #endif diff --git a/apps/buffering.c b/apps/buffering.c index edfb8e758a..3a412680ea 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -48,6 +48,7 @@ #include "playback.h" #include "pcmbuf.h" #include "buffer.h" +#include "bmp.h" #ifdef SIMULATOR #define ata_disk_is_active() 1 @@ -745,7 +746,7 @@ static void shrink_handle(struct memory_handle *h) if (h->next && h->filerem == 0 && (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET || - h->type == TYPE_IMAGE || h->type == TYPE_CODEC || + h->type == TYPE_BITMAP || h->type == TYPE_CODEC || h->type == TYPE_ATOMIC_AUDIO)) { /* metadata handle: we can move all of it */ @@ -762,11 +763,15 @@ static void shrink_handle(struct memory_handle *h) h->ridx = RINGBUF_ADD(h->ridx, delta); h->widx = RINGBUF_ADD(h->widx, delta); - /* when moving a struct mp3entry we need to readjust its pointers. */ if (h->type == TYPE_ID3 && h->filesize == sizeof(struct mp3entry)) { + /* when moving an mp3entry we need to readjust its pointers. */ adjust_mp3entry((struct mp3entry *)&buffer[h->data], (void *)&buffer[h->data], (void *)&buffer[olddata]); + } else if (h->type == TYPE_BITMAP) { + /* adjust the bitmap's pointer */ + struct bitmap *bmp = (struct bitmap *)&buffer[h->data]; + bmp->data = &buffer[h->data + sizeof(struct bitmap)]; } } else @@ -814,6 +819,23 @@ static bool fill_buffer(void) } } +#ifdef HAVE_LCD_BITMAP +/* 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_bitmap(const int fd) +{ + int rc; + struct bitmap *bmp = (struct bitmap *)&buffer[buf_widx]; + /* FIXME: alignment may be needed for the data buffer. */ + bmp->data = &buffer[buf_widx + sizeof(struct bitmap)]; + bmp->maskdata = NULL; + int free = (int)MIN(buffer_len - BUF_USED, buffer_len - buf_widx); + rc = read_bmp_fd(fd, bmp, free, FORMAT_ANY|FORMAT_DITHER); + return rc + (rc > 0 ? sizeof(struct bitmap) : 0); +} +#endif + /* MAIN BUFFERING API CALLS @@ -858,7 +880,6 @@ int bufopen(const char *file, size_t offset, enum data_type type) } strncpy(h->path, file, MAX_PATH); - h->filesize = size; h->filerem = size - offset; h->offset = offset; h->ridx = buf_widx; @@ -867,7 +888,25 @@ int bufopen(const char *file, size_t offset, enum data_type type) h->available = 0; h->type = type; - if (type == TYPE_CUESHEET || type == TYPE_IMAGE) { +#ifdef HAVE_LCD_BITMAP + if (type == TYPE_BITMAP) { + /* Bitmap file: we load the data instead of the file */ + mutex_lock(&llist_mutex); /* Lock because load_bitmap yields */ + size = load_bitmap(fd); + if (size <= 0) + return ERR_FILE_ERROR; + + h->filerem = 0; + h->available = size; + h->widx = buf_widx + size; /* safe because the data doesn't wrap */ + buf_widx += size; /* safe too */ + mutex_unlock(&llist_mutex); + } +#endif + + h->filesize = size; + + if (type == TYPE_CUESHEET) { h->fd = fd; /* Immediately start buffering those */ LOGFQUEUE("buffering >| Q_BUFFER_HANDLE"); diff --git a/apps/buffering.h b/apps/buffering.h index 1d69df2084..06895e7e51 100644 --- a/apps/buffering.h +++ b/apps/buffering.h @@ -30,7 +30,7 @@ enum data_type { TYPE_ATOMIC_AUDIO, TYPE_ID3, TYPE_CUESHEET, - TYPE_IMAGE, + TYPE_BITMAP, TYPE_BUFFER, TYPE_UNKNOWN, }; diff --git a/apps/gui/gwps-common.c b/apps/gui/gwps-common.c index fec13d564f..3c29884260 100644 --- a/apps/gui/gwps-common.c +++ b/apps/gui/gwps-common.c @@ -48,6 +48,7 @@ /* Image stuff */ #include "bmp.h" #include "atoi.h" +#include "albumart.h" #endif #include "dsp.h" #include "action.h" @@ -929,6 +930,19 @@ static char *get_token_value(struct gui_wps *gwps, case WPS_TOKEN_METADATA_COMMENT: return id3->comment; +#ifdef HAVE_ALBUMART + case WPS_TOKEN_ALBUMART_DISPLAY: + draw_album_art(gwps, audio_current_aa_hid()); + return NULL; + + case WPS_TOKEN_ALBUMART_FOUND: + if (audio_current_aa_hid() >= 0) { + snprintf(buf, buf_size, "C"); + return buf; + } + return NULL; +#endif + case WPS_TOKEN_FILE_BITRATE: if(id3->bitrate) snprintf(buf, buf_size, "%d", id3->bitrate); diff --git a/apps/gui/gwps.c b/apps/gui/gwps.c index b7707cdd0d..ad9fce7a8f 100644 --- a/apps/gui/gwps.c +++ b/apps/gui/gwps.c @@ -796,3 +796,18 @@ void gui_sync_wps_init(void) unload_remote_wps_backdrop(); #endif } + +#ifdef HAVE_ALBUMART +/* Returns true if at least one of the gui_wps screens has an album art + tag in its wps structure */ +bool gui_sync_wps_uses_albumart(void) +{ + int i; + FOR_NB_SCREENS(i) { + struct gui_wps *gwps = &gui_wps[i]; + if (gwps->data && (gwps->data->wps_uses_albumart != WPS_ALBUMART_NONE)) + return true; + } + return false; +} +#endif diff --git a/apps/gui/gwps.h b/apps/gui/gwps.h index 83ff14b80b..391fc72943 100644 --- a/apps/gui/gwps.h +++ b/apps/gui/gwps.h @@ -39,6 +39,23 @@ #define WPS_ALIGN_CENTER 64 #define WPS_ALIGN_LEFT 128 +#ifdef HAVE_ALBUMART + +/* albumart definitions */ +#define WPS_ALBUMART_NONE 0 /* WPS does not contain AA tag */ +#define WPS_ALBUMART_CHECK 1 /* WPS contains AA conditional tag */ +#define WPS_ALBUMART_LOAD 2 /* WPS contains AA tag */ + +#define WPS_ALBUMART_ALIGN_RIGHT WPS_ALIGN_RIGHT /* x align: right */ +#define WPS_ALBUMART_ALIGN_CENTER WPS_ALIGN_CENTER /* x/y align: center */ +#define WPS_ALBUMART_ALIGN_LEFT WPS_ALIGN_LEFT /* x align: left */ +#define WPS_ALBUMART_ALIGN_TOP WPS_ALIGN_RIGHT /* y align: top */ +#define WPS_ALBUMART_ALIGN_BOTTOM WPS_ALIGN_LEFT /* y align: bottom */ +#define WPS_ALBUMART_INCREASE 8 /* increase if smaller */ +#define WPS_ALBUMART_DECREASE 16 /* decrease if larger */ + +#endif /* HAVE_ALBUMART */ + /* wps_data*/ #ifdef HAVE_LCD_BITMAP @@ -187,6 +204,12 @@ enum wps_token_type { WPS_TOKEN_IMAGE_DISPLAY, #endif +#ifdef HAVE_ALBUMART + /* Albumart */ + WPS_TOKEN_ALBUMART_DISPLAY, + WPS_TOKEN_ALBUMART_FOUND, +#endif + /* Metadata */ WPS_TOKEN_METADATA_ARTIST, WPS_TOKEN_METADATA_COMPOSER, @@ -309,6 +332,20 @@ struct wps_data short progress_start; short progress_end; bool peak_meter_enabled; + +#ifdef HAVE_ALBUMART + /* Album art support */ + unsigned char wps_uses_albumart; /* WPS_ALBUMART_NONE, _CHECK, _LOAD */ + short albumart_x; + short albumart_y; + unsigned short albumart_xalign; /* WPS_ALBUMART_ALIGN_LEFT, _CENTER, _RIGHT, + + .._INCREASE, + .._DECREASE */ + unsigned short albumart_yalign; /* WPS_ALBUMART_ALIGN_TOP, _CENTER, _BOTTOM, + + .._INCREASE, + .._DECREASE */ + short albumart_max_width; + short albumart_max_height; +#endif + #else /*HAVE_LCD_CHARCELLS */ unsigned short wps_progress_pat[8]; bool full_line_progressbar; @@ -417,4 +454,9 @@ extern struct gui_wps gui_wps[NB_SCREENS]; void gui_sync_wps_init(void); void gui_sync_wps_screen_init(void); +#ifdef HAVE_ALBUMART +/* gives back if WPS contains an albumart tag */ +bool gui_sync_wps_uses_albumart(void); +#endif + #endif diff --git a/apps/gui/wps_parser.c b/apps/gui/wps_parser.c index 8471bff7d8..097a60c90f 100644 --- a/apps/gui/wps_parser.c +++ b/apps/gui/wps_parser.c @@ -113,6 +113,7 @@ static int parse_progressbar(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); static int parse_dir_level(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); + #ifdef HAVE_LCD_BITMAP static int parse_image_special(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); @@ -126,6 +127,13 @@ static int parse_image_load(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); #endif /*HAVE_LCD_BITMAP */ +#ifdef HAVE_ALBUMART +static int parse_albumart_load(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +static int parse_albumart_conditional(const char *wps_bufptr, + struct wps_token *token, struct wps_data *wps_data); +#endif /* HAVE_ALBUMART */ + #ifdef CONFIG_RTC #define WPS_RTC_REFRESH WPS_REFRESH_DYNAMIC #else @@ -283,6 +291,11 @@ static const struct wps_tag all_tags[] = { { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load }, { WPS_TOKEN_IMAGE_PROGRESS_BAR, "P", 0, parse_image_special }, +#ifdef HAVE_ALBUMART + { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load }, + { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_DYNAMIC, + parse_albumart_conditional }, +#endif #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1)) { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special }, #endif @@ -606,6 +619,215 @@ static int parse_progressbar(const char *wps_bufptr, #endif } +#ifdef HAVE_ALBUMART +static int parse_albumart_load(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + const char* _pos; + bool parsing; + const short xalign_mask = WPS_ALBUMART_ALIGN_LEFT | + WPS_ALBUMART_ALIGN_CENTER | + WPS_ALBUMART_ALIGN_RIGHT; + const short yalign_mask = WPS_ALBUMART_ALIGN_TOP | + WPS_ALBUMART_ALIGN_CENTER | + WPS_ALBUMART_ALIGN_BOTTOM; + + (void)token; /* silence warning */ + + /* reset albumart info in wps */ + wps_data->wps_uses_albumart = WPS_ALBUMART_NONE; + wps_data->albumart_max_width = -1; + wps_data->albumart_max_height = -1; + wps_data->albumart_xalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ + wps_data->albumart_yalign = WPS_ALBUMART_ALIGN_CENTER; /* default */ + + /* format: %Cl|x|y|[[l|c|r][d|i|s]mwidth]|[[t|c|b][d|i|s]mheight]| */ + + /* initial validation and parsing of x and y components */ + if (*wps_bufptr != '|') + return 0; /* malformed token: e.g. %Cl7 */ + + _pos = wps_bufptr + 1; + if (!isdigit(*_pos)) + return 0; /* malformed token: e.g. %Cl|@ */ + wps_data->albumart_x = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos || !isdigit(*(++_pos))) + return 0; /* malformed token: e.g. %Cl|7\n or %Cl|7|@ */ + + wps_data->albumart_y = atoi(_pos); + + _pos = strchr(_pos, '|'); + if (!_pos) + return 0; /* malformed token: no | after y coordinate + e.g. %Cl|7|59\n */ + + /* parsing width field */ + parsing = true; + while (parsing) + { + /* apply each modifier in turn */ + ++_pos; + switch (*_pos) + { + case 'l': + case 'L': + case '+': + wps_data->albumart_xalign = + (wps_data->albumart_xalign & xalign_mask) | + WPS_ALBUMART_ALIGN_LEFT; + break; + case 'c': + case 'C': + wps_data->albumart_xalign = + (wps_data->albumart_xalign & xalign_mask) | + WPS_ALBUMART_ALIGN_CENTER; + break; + case 'r': + case 'R': + case '-': + wps_data->albumart_xalign = + (wps_data->albumart_xalign & xalign_mask) | + WPS_ALBUMART_ALIGN_RIGHT; + break; + case 'd': + case 'D': + wps_data->albumart_xalign |= WPS_ALBUMART_DECREASE; + break; + case 'i': + case 'I': + wps_data->albumart_xalign |= WPS_ALBUMART_INCREASE; + break; + case 's': + case 'S': + wps_data->albumart_xalign |= + (WPS_ALBUMART_DECREASE | WPS_ALBUMART_INCREASE); + break; + default: + parsing = false; + break; + } + } + /* extract max width data */ + if (*_pos != '|') + { + if (!isdigit(*_pos)) + return 0; /* malformed token: e.g. %Cl|7|59|# */ + wps_data->albumart_max_width = atoi(_pos); + _pos = strchr(_pos, '|'); + if (!_pos) + return 0; /* malformed token: no | after width field + e.g. %Cl|7|59|200\n */ + } + + /* parsing height field */ + parsing = true; + while (parsing) + { + /* apply each modifier in turn */ + ++_pos; + switch (*_pos) + { + case 't': + case 'T': + case '-': + wps_data->albumart_yalign = + (wps_data->albumart_yalign & yalign_mask) | + WPS_ALBUMART_ALIGN_TOP; + break; + case 'c': + case 'C': + wps_data->albumart_yalign = + (wps_data->albumart_yalign & yalign_mask) | + WPS_ALBUMART_ALIGN_CENTER; + break; + case 'b': + case 'B': + case '+': + wps_data->albumart_yalign = + (wps_data->albumart_yalign & yalign_mask) | + WPS_ALBUMART_ALIGN_BOTTOM; + break; + case 'd': + case 'D': + wps_data->albumart_yalign |= WPS_ALBUMART_DECREASE; + break; + case 'i': + case 'I': + wps_data->albumart_yalign |= WPS_ALBUMART_INCREASE; + break; + case 's': + case 'S': + wps_data->albumart_yalign |= + (WPS_ALBUMART_DECREASE | WPS_ALBUMART_INCREASE); + break; + default: + parsing = false; + break; + } + } + /* extract max height data */ + if (*_pos != '|') + { + if (!isdigit(*_pos)) + return 0; /* malformed token e.g. %Cl|7|59|200|@ */ + wps_data->albumart_max_height = atoi(_pos); + _pos = strchr(_pos, '|'); + if (!_pos) + return 0; /* malformed token: no closing | + e.g. %Cl|7|59|200|200\n */ + } + + /* if we got here, we parsed everything ok .. ! */ + if (wps_data->albumart_max_width < 0) + wps_data->albumart_max_width = 0; + else if (wps_data->albumart_max_width > LCD_WIDTH) + wps_data->albumart_max_width = LCD_WIDTH; + + if (wps_data->albumart_max_height < 0) + wps_data->albumart_max_height = 0; + else if (wps_data->albumart_max_height > LCD_HEIGHT) + wps_data->albumart_max_height = LCD_HEIGHT; + + wps_data->wps_uses_albumart = WPS_ALBUMART_LOAD; + + /* Skip the rest of the line */ + return skip_end_of_line(wps_bufptr); +} + +static int parse_albumart_conditional(const char *wps_bufptr, + struct wps_token *token, + struct wps_data *wps_data) +{ + struct wps_token *prevtoken = token; + --prevtoken; + if (wps_data->num_tokens >= 1 && prevtoken->type == WPS_TOKEN_CONDITIONAL) + { + /* This %C is part of a %?C construct. + It's either %?C or %?Cn */ + token->type = WPS_TOKEN_ALBUMART_FOUND; + if (*wps_bufptr == 'n' && *(wps_bufptr + 1) == '<') + { + token->next = true; + return 1; + } + else if (*wps_bufptr == '<') + { + return 0; + } + else + { + token->type = WPS_NO_TOKEN; + return 0; + } + } + else + return 0; +}; +#endif /* HAVE_ALBUMART */ + /* Parse a generic token from the given string. Return the length read */ static int parse_token(const char *wps_bufptr, struct wps_data *wps_data) { @@ -915,6 +1137,9 @@ static void wps_reset(struct wps_data *data) bool rwps = data->remote_wps; /* remember whether the data is for a RWPS */ #endif memset(data, 0, sizeof(*data)); +#ifdef HAVE_ALBUMART + data->wps_uses_albumart = WPS_ALBUMART_NONE; +#endif wps_data_init(data); #ifdef HAVE_REMOTE_LCD data->remote_wps = rwps; diff --git a/apps/playback.c b/apps/playback.c index 0b2c9bb76c..5c526f94ed 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -66,6 +66,7 @@ #include "icons.h" #include "peakmeter.h" #include "action.h" +#include "albumart.h" #endif #include "lang.h" #include "bookmark.h" @@ -217,6 +218,9 @@ struct track_info { int audio_hid; /* The ID for the track's buffer handle */ int id3_hid; /* The ID for the track's metadata handle */ int codec_hid; /* The ID for the track's codec handle */ +#ifdef HAVE_ALBUMART + int aa_hid; /* The ID for the track's album art handle */ +#endif size_t filesize; /* File total length */ @@ -391,6 +395,15 @@ static bool clear_track_info(struct track_info *track) return false; } +#ifdef HAVE_ALBUMART + if (track->aa_hid >= 0) { + if (bufclose(track->aa_hid)) + track->aa_hid = -1; + else + return false; +#endif + } + track->filesize = 0; track->taginfo_ready = false; track->event_sent = false; @@ -635,6 +648,13 @@ void audio_remove_encoder(void) #endif /* HAVE_RECORDING */ +#ifdef HAVE_ALBUMART +int audio_current_aa_hid(void) +{ + return CUR_TI->aa_hid; +} +#endif + struct mp3entry* audio_current_track(void) { const char *filename; @@ -2381,6 +2401,16 @@ static bool audio_load_track(int offset, bool start_play) else track_id3 = bufgetid3(tracks[track_widx].id3_hid); + +#ifdef HAVE_ALBUMART + if (gui_sync_wps_uses_albumart()) + { + char aa_path[MAX_PATH]; + if (find_albumart(track_id3, aa_path, sizeof(aa_path))) + tracks[track_widx].aa_hid = bufopen(aa_path, 0, TYPE_BITMAP); + } +#endif + track_id3->elapsed = 0; enum data_type type = TYPE_PACKET_AUDIO; @@ -3286,6 +3316,9 @@ void audio_init(void) tracks[i].audio_hid = -1; tracks[i].id3_hid = -1; tracks[i].codec_hid = -1; +#ifdef HAVE_ALBUMART + tracks[i].aa_hid = -1; +#endif } /* Probably safe to say */ diff --git a/apps/recorder/albumart.c b/apps/recorder/albumart.c new file mode 100644 index 0000000000..abae8c1afc --- /dev/null +++ b/apps/recorder/albumart.c @@ -0,0 +1,285 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include "sprintf.h" +#include "system.h" +#include "albumart.h" +#include "id3.h" +#include "gwps.h" +#include "buffering.h" +#include "dircache.h" +#include "debug.h" + + +/* Strip filename from a full path + * + * buf - buffer to extract directory to. + * buf_size - size of buffer. + * fullpath - fullpath to extract from. + * + * Split the directory part of the given fullpath and store it in buf + * (including last '/'). + * The function return parameter is a pointer to the filename + * inside the given fullpath. + */ +static char* strip_filename(char* buf, int buf_size, const char* fullpath) +{ + char* sep; + int len; + + if (!buf || buf_size <= 0 || !fullpath) + return NULL; + + /* if 'fullpath' is only a filename return immediately */ + sep = strrchr(fullpath, '/'); + if (sep == NULL) + { + buf[0] = 0; + return (char*)fullpath; + } + + len = MIN(sep - fullpath + 1, buf_size - 1); + strncpy(buf, fullpath, len); + buf[len] = 0; + return (sep + 1); +} + +/* Strip extension from a filename. + * + * buf - buffer to output the result to. + * buf_size - size of the output buffer buffer. + * file - filename to strip extension from. + * + * Return value is a pointer to buf, which contains the result. + */ +static char* strip_extension(char* buf, int buf_size, const char* file) +{ + char* sep; + int len; + + if (!buf || buf_size <= 0 || !file) + return NULL; + + buf[0] = 0; + + sep = strrchr(file,'.'); + if (sep == NULL) + return NULL; + + len = MIN(sep - file, buf_size - 1); + strncpy(buf, file, len); + buf[len] = 0; + return buf; +} + +/* Test file existence, using dircache of possible */ +static bool file_exists(const char *file) +{ + int fd; + + if (!file || strlen(file) <= 0) + return false; + +#ifdef HAVE_DIRCACHE + if (dircache_is_enabled()) + return (dircache_get_entry_ptr(file) != NULL); +#endif + + fd = open(file, O_RDONLY); + if (fd < 0) + return false; + close(fd); + return true; +} + +/* Look for the first matching album art bitmap in the following list: + * ./.bmp + * ./.bmp + * ./cover.bmp + * ../.bmp + * ../cover.bmp + * is the value of the size_string parameter, and + * are read from the ID3 metadata. + * If a matching bitmap is found, its filename is stored in buf. + * Return value is true if a bitmap was found, false otherwise. + */ +static bool search_files(const struct mp3entry *id3, const char *size_string, + char *buf, int buflen) +{ + char path[MAX_PATH + 1]; + char dir[MAX_PATH + 1]; + bool found = false; + const char *trackname; + + if (!id3 || !buf) + return false; + + trackname = id3->path; + strip_filename(dir, sizeof(dir), trackname); + + /* the first file we look for is one specific to the track playing */ + strip_extension(path, sizeof(path) - strlen(size_string) - 4, trackname); + strcat(path, size_string); + strcat(path, ".bmp"); + found = file_exists(path); + if (!found && id3->album && strlen(id3->album) > 0) + { + /* if it doesn't exist, + * we look for a file specific to the track's album name */ + snprintf(path, sizeof(path) - 1, + "%s%s%s.bmp", + (strlen(dir) >= 1) ? dir : "", + id3->album, size_string); + path[sizeof(path) - 1] = 0; + found = file_exists(path); + } + + if (!found) + { + /* if it still doesn't exist, we look for a generic file */ + snprintf(path, sizeof(path)-1, + "%scover%s.bmp", + (strlen(dir) >= 1) ? dir : "", size_string); + path[sizeof(path)-1] = 0; + found = file_exists(path); + } + + if (!found) + { + /* if it still doesn't exist, + * we continue to search in the parent directory */ + char temp[MAX_PATH + 1]; + strncpy(temp, dir, strlen(dir) - 1); + temp[strlen(dir) - 1] = 0; + + strip_filename(dir, sizeof(dir), temp); + } + + if (!found && id3->album && strlen(id3->album) > 0) + { + /* we look in the parent directory + * for a file specific to the track's album name */ + snprintf(path, sizeof(path)-1, + "%s%s%s.bmp", + (strlen(dir) >= 1) ? dir : "", + id3->album, size_string); + found = file_exists(path); + } + + if (!found) + { + /* if it still doesn't exist, we look in the parent directory + * for a generic file */ + snprintf(path, sizeof(path)-1, + "%scover%s.bmp", + (strlen(dir) >= 1) ? dir : "", size_string); + path[sizeof(path)-1] = 0; + found = file_exists(path); + } + + if (!found) + return false; + + strncpy(buf, path, buflen); + DEBUGF("Album art found: %s\n", path); + return true; +} + +/* Look for albumart bitmap in the same dir as the track and in its parent dir. + * Stores the found filename in the buf parameter. + * Returns true if a bitmap was found, false otherwise */ +bool find_albumart(const struct mp3entry *id3, char *buf, int buflen) +{ + if (!id3 || !buf) + return false; + + char size_string[9]; + struct wps_data *data = gui_wps[0].data; + + if (!data) + return false; + + DEBUGF("Looking for album art for %s\n", id3->path); + + /* Write the size string, e.g. ".100x100". */ + snprintf(size_string, sizeof(size_string), ".%dx%d", + data->albumart_max_width, data->albumart_max_height); + + /* First we look for a bitmap of the right size */ + if (search_files(id3, size_string, buf, buflen)) + return true; + + /* Then we look for generic bitmaps */ + *size_string = 0; + return search_files(id3, size_string, buf, buflen); +} + +/* Draw the album art bitmap from the given handle ID onto the given WPS. */ +void draw_album_art(struct gui_wps *gwps, int handle_id) +{ + if (!gwps || !gwps->data || !gwps->display || handle_id < 0) + return; + + struct wps_data *data = gwps->data; + +#ifdef HAVE_REMOTE_LCD + /* No album art on RWPS */ + if (data->remote_wps) + return; +#endif + + struct bitmap *bmp; + bufgetdata(handle_id, 0, (void *)&bmp); + + short x = data->albumart_x; + short y = data->albumart_y; + short width = bmp->width; + short height = bmp->height; + + if (data->albumart_max_width > 0) + { + /* Crop if the bitmap is too wide */ + width = MIN(bmp->width, data->albumart_max_width); + + /* Align */ + if (data->albumart_xalign & WPS_ALBUMART_ALIGN_RIGHT) + x += data->albumart_max_width - width; + else if (data->albumart_xalign & WPS_ALBUMART_ALIGN_CENTER) + x += (data->albumart_max_width - width) / 2; + } + + if (data->albumart_max_height > 0) + { + /* Crop if the bitmap is too high */ + height = MIN(bmp->height, data->albumart_max_height); + + /* Align */ + if (data->albumart_yalign & WPS_ALBUMART_ALIGN_BOTTOM) + y += data->albumart_max_height - height; + else if (data->albumart_yalign & WPS_ALBUMART_ALIGN_CENTER) + y += (data->albumart_max_height - height) / 2; + } + + /* Draw the bitmap */ + gwps->display->set_drawmode(DRMODE_FG); + gwps->display->bitmap_part((fb_data*)bmp->data, 0, 0, bmp->width, + x, y, width, height); + gwps->display->set_drawmode(DRMODE_SOLID); +} diff --git a/apps/recorder/albumart.h b/apps/recorder/albumart.h new file mode 100644 index 0000000000..21ae50edb9 --- /dev/null +++ b/apps/recorder/albumart.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef _ALBUMART_H_ +#define _ALBUMART_H_ + +#ifdef HAVE_ALBUMART + +#include +#include "id3.h" +#include "gwps.h" + +/* Look for albumart bitmap in the same dir as the track and in its parent dir. + * Stores the found filename in the buf parameter. + * Returns true if a bitmap was found, false otherwise */ +bool find_albumart(const struct mp3entry *id3, char *buf, int buflen); + +/* Draw the album art bitmap from the given handle ID onto the given WPS. */ +void draw_album_art(struct gui_wps *gwps, int handle_id); + +#endif /* HAVE_ALBUMART */ + +#endif /* _ALBUMART_H_ */ diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 84275cca2a..468856a87a 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -91,6 +91,9 @@ void audio_pre_ff_rewind(void); /* SWCODEC only */ #endif /* CONFIG_CODEC == SWCODEC */ void audio_ff_rewind(long newtime); void audio_flush_and_reload_tracks(void); +#ifdef HAVE_ALBUMART +int audio_current_aa_hid(void); +#endif struct mp3entry* audio_current_track(void); struct mp3entry* audio_next_track(void); bool audio_has_changed_track(void); diff --git a/firmware/export/config-c200.h b/firmware/export/config-c200.h index 2e3423c79f..8a1d6a37fa 100644 --- a/firmware/export/config-c200.h +++ b/firmware/export/config-c200.h @@ -26,6 +26,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have a light associated with the buttons */ #define HAVE_BUTTON_LIGHT diff --git a/firmware/export/config-e200.h b/firmware/export/config-e200.h index 5e9103b002..e2274bc956 100644 --- a/firmware/export/config-e200.h +++ b/firmware/export/config-e200.h @@ -26,6 +26,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have a light associated with the buttons */ #define HAVE_BUTTON_LIGHT diff --git a/firmware/export/config-gigabeat.h b/firmware/export/config-gigabeat.h index ba6b714a17..2e3ea07b36 100644 --- a/firmware/export/config-gigabeat.h +++ b/firmware/export/config-gigabeat.h @@ -14,6 +14,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN diff --git a/firmware/export/config-h10.h b/firmware/export/config-h10.h index 8054152c77..039ebc588f 100644 --- a/firmware/export/config-h10.h +++ b/firmware/export/config-h10.h @@ -26,6 +26,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN diff --git a/firmware/export/config-h100.h b/firmware/export/config-h100.h index c661e1df14..f6f53f64d0 100644 --- a/firmware/export/config-h100.h +++ b/firmware/export/config-h100.h @@ -15,6 +15,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can flip your LCD */ #define HAVE_LCD_FLIP diff --git a/firmware/export/config-h10_5gb.h b/firmware/export/config-h10_5gb.h index cb5d4ecae0..3d10af26cf 100644 --- a/firmware/export/config-h10_5gb.h +++ b/firmware/export/config-h10_5gb.h @@ -26,6 +26,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN diff --git a/firmware/export/config-h120.h b/firmware/export/config-h120.h index 6fc9aa2406..c347cd6ae0 100644 --- a/firmware/export/config-h120.h +++ b/firmware/export/config-h120.h @@ -10,6 +10,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can flip your LCD */ #define HAVE_LCD_FLIP diff --git a/firmware/export/config-h300.h b/firmware/export/config-h300.h index 483582080e..de478aa163 100644 --- a/firmware/export/config-h300.h +++ b/firmware/export/config-h300.h @@ -13,6 +13,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can flip your LCD */ #define HAVE_LCD_FLIP diff --git a/firmware/export/config-iaudiom5.h b/firmware/export/config-iaudiom5.h index 3e98f4e748..9330315305 100644 --- a/firmware/export/config-iaudiom5.h +++ b/firmware/export/config-iaudiom5.h @@ -22,6 +22,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can flip your LCD */ #define HAVE_LCD_FLIP diff --git a/firmware/export/config-iaudiox5.h b/firmware/export/config-iaudiox5.h index c78137c721..d6fe68b1ac 100644 --- a/firmware/export/config-iaudiox5.h +++ b/firmware/export/config-iaudiox5.h @@ -28,6 +28,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipod1g2g.h b/firmware/export/config-ipod1g2g.h index 138ef8dc77..c003805986 100644 --- a/firmware/export/config-ipod1g2g.h +++ b/firmware/export/config-ipod1g2g.h @@ -14,6 +14,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipod3g.h b/firmware/export/config-ipod3g.h index e3c2a922d9..c3c6114d16 100644 --- a/firmware/export/config-ipod3g.h +++ b/firmware/export/config-ipod3g.h @@ -14,6 +14,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipod4g.h b/firmware/export/config-ipod4g.h index 7d58992ee8..8bdc053563 100644 --- a/firmware/export/config-ipod4g.h +++ b/firmware/export/config-ipod4g.h @@ -24,6 +24,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipodcolor.h b/firmware/export/config-ipodcolor.h index a5850904fa..0aba8c34d6 100644 --- a/firmware/export/config-ipodcolor.h +++ b/firmware/export/config-ipodcolor.h @@ -27,6 +27,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN /* define this if you have access to the pitchscreen */ diff --git a/firmware/export/config-ipodmini.h b/firmware/export/config-ipodmini.h index 5cab1767e3..4a3edbfb5f 100644 --- a/firmware/export/config-ipodmini.h +++ b/firmware/export/config-ipodmini.h @@ -14,6 +14,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipodmini2g.h b/firmware/export/config-ipodmini2g.h index 77299dda01..0f82d099b9 100644 --- a/firmware/export/config-ipodmini2g.h +++ b/firmware/export/config-ipodmini2g.h @@ -14,6 +14,9 @@ /* define this if you have a bitmap LCD display */ #define HAVE_LCD_BITMAP +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you can invert the colours on your LCD */ #define HAVE_LCD_INVERT diff --git a/firmware/export/config-ipodnano.h b/firmware/export/config-ipodnano.h index fcba445bce..4fcb8f69dc 100644 --- a/firmware/export/config-ipodnano.h +++ b/firmware/export/config-ipodnano.h @@ -27,6 +27,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN /* define this if you have access to the pitchscreen */ diff --git a/firmware/export/config-ipodvideo.h b/firmware/export/config-ipodvideo.h index 9ba479246b..eecb65c809 100644 --- a/firmware/export/config-ipodvideo.h +++ b/firmware/export/config-ipodvideo.h @@ -27,6 +27,9 @@ /* define this if you have a colour LCD */ #define HAVE_LCD_COLOR +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + /* define this if you have access to the quickscreen */ #define HAVE_QUICKSCREEN /* define this if you have access to the pitchscreen */ -- cgit v1.2.3