/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2009 Teruaki Kawashima * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include PLUGIN_HEADER struct lrc_word { long time_start; unsigned char *word; }; struct lrc { long time_start; struct lrc *prev; /* pointer to previous entry */ struct lrc *next; /* pointer to next entry */ int nline[NB_SCREENS]; int nword; /* number of words */ struct lrc_word *words; }; struct preferences { #if LCD_DEPTH > 1 unsigned active_color; unsigned inactive_color; #endif char lrc_directry[64]; }; static struct preferences prefs; static unsigned char *lrc_buffer; static size_t lrc_buffer_used; static size_t lrc_buffer_end; static size_t lrc_buffer_size; static struct lrc *last_lrc; enum extention_types { LRC, LRC8, NUM_TYPES }; static const char *extentions[NUM_TYPES] = { "lrc", "lrc8" }; static struct lrc_info { struct mp3entry *id3; long elapsed; long length; int audio_status; char mp3_file[MAX_PATH]; char lrc_file[MAX_PATH]; enum extention_types type; long offset; /* msec */ enum { LOADING_LRC, NO_LRC, LOADED_LRC } status; } current; struct viewport vp_lyrics[NB_SCREENS]; /******************************* * misc stuff *******************************/ static void reset_current_data(void) { current.offset = 0; current.status = 0; lrc_buffer_used = 0; lrc_buffer_end = lrc_buffer_size; last_lrc = NULL; } /* check space and add str to lrc_buffer. * return NULL if there is not enough buffer. */ static char *lrcbufadd(const char*str) { int size = rb->strlen(str)+1; if(lrc_buffer_used + size > lrc_buffer_end) return NULL; /* not enough buffer */ lrc_buffer_end -= size; return rb->strcpy(&lrc_buffer[lrc_buffer_end], str); } static inline long get_time_start(struct lrc *lrc) { if(!lrc) return current.length + 20; long time = lrc->time_start + current.offset; return time < 0? 0: time; } static inline void set_time_start(struct lrc *lrc, long time_start) { time_start -= current.offset; time_start -= time_start%10; if(lrc->time_start != time_start) { lrc->time_start = time_start; } } /* calculate how many lines is need to display each line and store it. */ static void calc_pos(struct lrc *lrc) { struct lrc_word *lrc_word; int i, width, nword; FOR_NB_SCREENS(i) { lrc->nline[i] = 1; /* at least one line is needed :) */ lrc_word = lrc->words; nword = 0; width = 0; do { int w = rb->font_getstringsize(lrc_word->word, NULL, NULL, vp_lyrics[i].font); if(width && width + w > vp_lyrics[i].width) { lrc->nline[i]++; width = 0; continue; } width += w; lrc_word++; nword++; } while (nword < lrc->nword); } /* FOR_NB_SCREENS */ return; } /******************************* * Serch lrc file. *******************************/ /* search in same or parent directries of playing file. * assume playing file is /aaa/bbb/ccc/ddd.mp3, * this function searchs lrc file following order. * (base_dir)/aaa/bbb/ccc/ddd.lrc * (base_dir)/aaa/bbb/ddd.lrc * (base_dir)/aaa/ddd.lrc * (base_dir)/bbb/ccc/ddd.lrc * (base_dir)/bbb/ddd.lrc * (base_dir)/ccc/ddd.lrc * (base_dir)/ddd.lrc */ static bool find_lrc_file_helper(const char *base_dir) { char temp_path[MAX_PATH]; char *names[3] = {NULL, NULL, NULL}; char *p, *q, *dir; int i, len; /* /aaa/bbb/ccc/ddd.mp3 * dir p q names[0] */ /* assuming file name starts with '/' */ rb->strcpy(temp_path, current.mp3_file); /* get file name and remove extension */ names[0] = rb->strrchr(temp_path, '/')+1; if((p = rb->strrchr(names[0], '.')) != NULL) *p = 0; if(current.id3->title != NULL && rb->strcmp(names[0], current.id3->title)) names[1] = current.id3->title; dir = temp_path; do{ q = p = names[0]-1; do{ *p = '/'; *q = 0; p = q; DEBUGF("%s%s/%s.ext\n", base_dir, dir, names[0]); len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s", base_dir, dir); if(len == 0 /* root */ || rb->dir_exists(current.lrc_file)) { for(current.type=0; current.typesnprintf(¤t.lrc_file[len], MAX_PATH-len, "/%s.%s", names[i], extentions[current.type]); if(rb->file_exists(current.lrc_file)) return true; } } } }while((q = rb->strrchr(dir, '/')) != NULL && q != dir); *p = '/'; }while((dir = rb->strchr(dir+1, '/')) != NULL); return false; } /* return true if a lrc file is found */ static bool find_lrc_file(void) { reset_current_data(); DEBUGF("find lrc file for `%s'\n", current.mp3_file); /* find .lrc file */ if(find_lrc_file_helper("")) return true; if(rb->dir_exists(prefs.lrc_directry)) { if(find_lrc_file_helper(prefs.lrc_directry)) return true; } current.lrc_file[0] = 0; return false; } /******************************* * Load file. *******************************/ /* * check tag format and calculate value of the tag. * supported tag: ti, ar, offset * supported format of time tag: [mm:ss], [mm:ss.xx], [mm:ss:xx] */ static long get_time_value(char *tag, bool read_id_tags) { long time; int len = rb->strlen(tag); if(read_id_tags) { if(!rb->strncmp(tag, "offset:", 7)) { current.offset = rb->atoi(&tag[7]); return -1; } } if(len != 5 && len != 8) return -1; if(!isdigit(tag[0]) || !isdigit(tag[1]) || tag[2] != ':') return -1; if(!isdigit(tag[3]) || !isdigit(tag[4])) return -1; if(len == 8) { if((tag[5] != '.' && tag[5] != ':') || !isdigit(tag[6]) || !isdigit(tag[7])) return -1; } /* tag is [mm:ss...] */ time = rb->atoi(&tag[0])*60000 + rb->atoi(&tag[3])*1000; if(len == 8) { /* tag is [mm:ss.xx] or [mm:ss:xx] */ time += rb->atoi(&tag[6])*10; } return time; } /* format: * [time tag]line * [time tag]