Index: apps/gui/gwps-common.c =================================================================== --- apps/gui/gwps-common.c (revision 13687) +++ apps/gui/gwps-common.c (working copy) @@ -73,6 +73,8 @@ if (draw) gui_statusbar_draw(wps->statusbar, force); } + +static bool wps_data_albumart_load(struct gui_wps *gwps); #else #define gui_wps_statusbar_draw(wps, force) \ gui_statusbar_draw((wps)->statusbar, (force)) @@ -365,6 +367,9 @@ gwps->display->stop_scroll(); gwps->state->id3 = audio_current_track(); + /* track has changed : update the album art information */ + wps_data_albumart_load(gwps); + if (cuesheet_is_enabled() && gwps->state->id3->cuesheet_type && strcmp(gwps->state->id3->path, curr_cue->audio_filename)) { @@ -560,6 +565,85 @@ display->set_drawmode(DRMODE_SOLID); } +static bool wps_data_albumart_load(struct gui_wps *gwps) +{ + if(!gwps || !gwps->data) + return false; + + if(gwps->data->wps_uses_albumart == WPS_ALBUMART_NONE) + return false; + + if(!gwps->state->id3->albumart_found) + return false; + + struct wps_data * data = gwps->data; + + if(0 == strcasecmp(gwps->state->id3->albumart_path, data->cur_displayed_albumart_path)) + { + /* new bitmap is same as old bitmap - trivially succeed */ + gwps->state->id3->albumart_data = (fb_data *)data->img_buf_ptr; + gwps->state->id3->albumart_width = data->cur_displayed_albumart_width; + gwps->state->id3->albumart_height = data->cur_displayed_albumart_height; + + return true; + } + + int rc; + struct bitmap temp_bitmap; + temp_bitmap.data = data->img_buf_ptr; + rc = read_bmp_file(gwps->state->id3->albumart_path, &temp_bitmap, data->img_buf_free, FORMAT_ANY|FORMAT_TRANSPARENT); + if(rc <= 0) + { + /* load failed */ + gwps->state->id3->albumart_data = NULL; + data->cur_displayed_albumart_path[0] = '\0'; + return false; + } + + data->cur_displayed_albumart_width = gwps->state->id3->albumart_width = temp_bitmap.width; + data->cur_displayed_albumart_height = gwps->state->id3->albumart_height = temp_bitmap.height; + gwps->state->id3->albumart_data = (fb_data *)temp_bitmap.data; + strcpy(data->cur_displayed_albumart_path, gwps->state->id3->albumart_path); + return true; +} + +static void draw_album_art(struct gui_wps *gwps) +{ + if(!gwps || !gwps->data || !gwps->display) + return; + + struct wps_data * data = gwps->data; + if(data->wps_uses_albumart == WPS_ALBUMART_NONE) + return; + + if(!gwps->state->id3->albumart_found || gwps->state->id3->albumart_data == NULL) + return; + + short x = data->albumart_x; + short y = data->albumart_y; + if(data->albumart_max_width>0) + { + if(data->albumart_xalign & WPS_ALBUMART_ALIGN_RIGHT) + x += data->albumart_max_width - gwps->state->id3->albumart_width; + else if(data->albumart_xalign & WPS_ALBUMART_ALIGN_CENTER) + x += (data->albumart_max_width - gwps->state->id3->albumart_width)/2; + } + if(data->albumart_max_height>0) + { + if(data->albumart_yalign & WPS_ALBUMART_ALIGN_BOTTOM) + y += data->albumart_max_height - gwps->state->id3->albumart_height; + else if(data->albumart_yalign & WPS_ALBUMART_ALIGN_CENTER) + y += (data->albumart_max_height - gwps->state->id3->albumart_height)/2; + } + + gwps->display->set_drawmode(DRMODE_FG); + gwps->display->bitmap((fb_data*)gwps->state->id3->albumart_data, + x, y, + gwps->state->id3->albumart_width, + gwps->state->id3->albumart_height ); + gwps->display->set_drawmode(DRMODE_SOLID); +} + #else /* HAVE_LCD_CHARCELL */ static bool draw_player_progress(struct gui_wps *gwps) @@ -909,7 +993,18 @@ case WPS_TOKEN_METADATA_COMMENT: return id3->comment; - + + case WPS_TOKEN_ALBUMART_DISPLAY: + draw_album_art(gwps); + return NULL; + + case WPS_TOKEN_ALBUMART_FOUND: + if(id3->albumart_found) { + snprintf(buf, buf_size, "C"); + return buf; + } + return NULL; + case WPS_TOKEN_FILE_BITRATE: if(id3->bitrate) snprintf(buf, buf_size, "%d", id3->bitrate); Index: apps/gui/gwps.c =================================================================== --- apps/gui/gwps.c (revision 13687) +++ apps/gui/gwps.c (working copy) @@ -789,3 +789,20 @@ unload_remote_wps_backdrop(); #endif } + +/* +** returns true if at least one of the gui_wps screens +** has an albumart 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; +} + +/* vi: set ts=4 sts=4 sw=4 et ai: */ Index: apps/gui/gwps.h =================================================================== --- apps/gui/gwps.h (revision 13687) +++ apps/gui/gwps.h (working copy) @@ -39,6 +39,19 @@ #define WPS_ALIGN_CENTER 64 #define WPS_ALIGN_LEFT 128 +/* 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 */ + /* wps_data*/ #ifdef HAVE_LCD_BITMAP @@ -185,6 +198,10 @@ WPS_TOKEN_IMAGE_PRELOAD, WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, WPS_TOKEN_IMAGE_DISPLAY, + + /* Albumart */ + WPS_TOKEN_ALBUMART_DISPLAY, + WPS_TOKEN_ALBUMART_FOUND, #endif /* Metadata */ @@ -307,6 +324,19 @@ short progress_start; short progress_end; bool peak_meter_enabled; + + /* Album art additions */ + unsigned char wps_uses_albumart; /* WPS_ALBUMART_NONE, WPS_ALBUMART_CHECK, WPS_ALBUMART_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; + char cur_displayed_albumart_path[MAX_PATH]; /* Information about the */ + short cur_displayed_albumart_width; /* currently displayed */ + short cur_displayed_albumart_height; /* album art bitmap */ + #else /*HAVE_LCD_CHARCELLS */ unsigned short wps_progress_pat[8]; bool full_line_progressbar; @@ -415,4 +445,7 @@ void gui_sync_wps_init(void); void gui_sync_wps_screen_init(void); +/* gives back if WPS contains an albumart tag */ +bool gui_sync_wps_uses_albumart(void); + #endif Index: apps/gui/wps_parser.c =================================================================== --- apps/gui/wps_parser.c (revision 13687) +++ apps/gui/wps_parser.c (working copy) @@ -124,6 +124,10 @@ struct wps_token *token, struct wps_data *wps_data); static int parse_image_load(const char *wps_bufptr, struct wps_token *token, struct wps_data *wps_data); +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_LCD_BITMAP */ #ifdef CONFIG_RTC @@ -269,6 +273,8 @@ { WPS_TOKEN_SUBLINE_TIMEOUT, "t", 0, parse_subline_timeout }, #ifdef HAVE_LCD_BITMAP + { WPS_NO_TOKEN, "Cl", 0, parse_albumart_load }, + { WPS_TOKEN_ALBUMART_DISPLAY, "C", WPS_REFRESH_DYNAMIC, parse_albumart_conditional }, { WPS_NO_TOKEN, "we", 0, parse_statusbar_enable }, { WPS_NO_TOKEN, "wd", 0, parse_statusbar_disable }, @@ -602,6 +608,191 @@ #endif } +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 = -1; + wps_data->albumart_yalign = -1; + + /* 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: e.g. %Cl|7|59\n (no | after y coordinate) */ + + /* 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_CENTER; + 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: e.g. %Cl|7|59|200\n (no | after width field) */ + } + + /* parsing height field */ + parsing=true; + while(parsing) + { + /* apply each modifier in turn */ + ++_pos; + switch(*_pos) + { + case 't': + case 'T': + case 'r': /* yuck */ + case 'R': + 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 'l': /* yuck */ + case 'L': + 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_xalign |= (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 e.g. %Cl|7|59|200|200\n (no closing |) */ + } + + /* 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; + + return( _pos - wps_bufptr + 1 ); +} + +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; +}; + + /* 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) { @@ -894,6 +1085,9 @@ wps_data->img_buf_ptr = wps_data->img_buf; /* where in image buffer */ wps_data->img_buf_free = IMG_BUFSIZE; /* free space in image buffer */ wps_data->peak_meter_enabled = false; + wps_data->cur_displayed_albumart_path[0] = '\0'; + wps_data->cur_displayed_albumart_width = -1; + wps_data->cur_displayed_albumart_height = -1; #else /* HAVE_LCD_CHARCELLS */ int i; for (i = 0; i < 8; i++) Index: apps/metadata.c =================================================================== --- apps/metadata.c (revision 13687) +++ apps/metadata.c (working copy) @@ -28,6 +28,10 @@ #include "logf.h" #include "cuesheet.h" +#ifdef HAVE_LCD_BITMAP +#include "gwps.h" +#endif + #if CONFIG_CODEC == SWCODEC #include "metadata/metadata_common.h" @@ -96,11 +100,164 @@ return AFMT_UNKNOWN; } +#ifdef HAVE_LCD_BITMAP +/* 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); +} + +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; +} + +static bool file_exists(char *file) +{ + int fd; + + if (!file || strlen(file) <= 0) + return false; + + fd = open(file, O_RDONLY); + if (fd<0) + return false; + close(fd); + return true; +} + +/* Look for albumart bitmap in the same dir as the track and in its parent dir; + * stores the found filename in the track->albumart_path. + * Returns true if a bitmap was found, false otherwise */ +static bool find_albumart(struct track_info* track, const char* trackname) +{ + char path[MAX_PATH + 1]; + char dir[MAX_PATH + 1]; + bool found = false; + + if (!track || !trackname) + return false; + + strcpy(track->id3.albumart_path, ""); + + strip_filename(dir, sizeof(dir), trackname); + + DEBUGF("Looking for album art for %s\n", trackname); + + /* the first file we look for is one specific to the track playing */ + strip_extension(path, sizeof(path) - 4, trackname); + strcat(path, ".bmp"); + found = file_exists(path); + if (!found && track->id3.album && strlen(track->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.bmp", + (strlen(dir) >= 1) ? dir : "", + track->id3.album); + 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.bmp", + (strlen(dir) >= 1) ? dir : ""); + 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 && track->id3.album && strlen(track->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.bmp", + (strlen(dir) >= 1) ? dir : "", + track->id3.album); + 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.bmp", + (strlen(dir) >= 1) ? dir : ""); + path[sizeof(path)-1] = 0; + found = file_exists(path); + } + + if (!found) + return false; + + strcpy(track->id3.albumart_path, path); + DEBUGF("Album art found for %s : %s\n", trackname, path); + return true; +} +#endif + /* Get metadata for track - return false if parsing showed problems with the * file that would prevent playback. */ bool get_metadata(struct track_info* track, int fd, const char* trackname, - bool v1first) + bool v1first, bool search_album_art) { #if CONFIG_CODEC == SWCODEC unsigned char* buf; @@ -364,6 +521,11 @@ } #endif +#ifdef HAVE_LCD_BITMAP + if (search_album_art && gui_sync_wps_uses_albumart()) + track->id3.albumart_found = find_albumart(track, trackname); +#endif + lseek(fd, 0, SEEK_SET); strncpy(track->id3.path, trackname, sizeof(track->id3.path)); track->taginfo_ready = true; Index: apps/metadata.h =================================================================== --- apps/metadata.h (revision 13687) +++ apps/metadata.h (working copy) @@ -24,7 +24,7 @@ unsigned int probe_file_format(const char *filename); bool get_metadata(struct track_info* track, int fd, const char* trackname, - bool v1first); + bool v1first, bool search_album_art); #endif Index: apps/playback.c =================================================================== --- apps/playback.c (revision 13687) +++ apps/playback.c (working copy) @@ -2733,7 +2733,7 @@ /* Get track metadata if we don't already have it. */ if (!tracks[track_widx].taginfo_ready) { - if (get_metadata(&tracks[track_widx],current_fd,trackname,v1first)) + if (get_metadata(&tracks[track_widx],current_fd,trackname,v1first,true)) { if (start_play) { @@ -2874,7 +2874,7 @@ if (fd < 0) return false; - status = get_metadata(&tracks[next_idx],fd,trackname,v1first); + status = get_metadata(&tracks[next_idx],fd,trackname,v1first,true); /* Preload the glyphs in the tags */ if (status) { Index: apps/plugin.h =================================================================== --- apps/plugin.h (revision 13687) +++ apps/plugin.h (working copy) @@ -620,7 +620,7 @@ int (*codec_load_file)(const char* codec, struct codec_api *api); const char *(*get_codec_filename)(int cod_spec); bool (*get_metadata)(struct track_info* track, int fd, const char* trackname, - bool v1first); + bool v1first, bool search_album_art); #endif }; Index: apps/plugins/test_codec.c =================================================================== --- apps/plugins/test_codec.c (revision 13687) +++ apps/plugins/test_codec.c (working copy) @@ -537,7 +537,7 @@ rb->memset(&track.id3, 0, sizeof(struct mp3entry)); if (!rb->get_metadata(&track, fd, filename, - rb->global_settings->id3_v1_first)) + rb->global_settings->id3_v1_first, false)) { log_text("Cannot read metadata",true); return PLUGIN_ERROR; Index: apps/tagcache.c =================================================================== --- apps/tagcache.c (revision 13687) +++ apps/tagcache.c (working copy) @@ -1638,7 +1638,7 @@ memset(&track, 0, sizeof(struct track_info)); memset(&entry, 0, sizeof(struct temp_file_entry)); memset(&tracknumfix, 0, sizeof(tracknumfix)); - ret = get_metadata(&track, fd, path, false); + ret = get_metadata(&track, fd, path, false, false); close(fd); if (!ret) Index: firmware/export/id3.h =================================================================== --- firmware/export/id3.h (revision 13687) +++ firmware/export/id3.h (working copy) @@ -22,6 +22,8 @@ #include #include "config.h" #include "file.h" +#include "lcd.h" +#include "system.h" /* Audio file types. */ /* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS @@ -217,6 +219,15 @@ /* Cuesheet support */ int cuesheet_type; /* 0: none, 1: external, 2: embedded */ + +#ifdef HAVE_LCD_BITMAP + /* album art support */ + fb_data* albumart_data; + unsigned int albumart_width; + unsigned int albumart_height; + bool albumart_found; + char albumart_path[MAX_PATH]; +#endif }; enum {