A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 2838 lines 85 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2008-2009 Teruaki Kawashima 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21#include "plugin.h" 22#include "lib/playback_control.h" 23#include "lib/configfile.h" 24#include "lib/helper.h" 25#include <ctype.h> 26 27 28 29#define MAX_LINE_LEN 256 30#define LRC_BUFFER_SIZE 0x3000 /* 12 kiB */ 31#if PLUGIN_BUFFER_SIZE >= 0x10000 /* no id3 support for low mem targets */ 32/* define this to read lyrics in id3 tag */ 33#define LRC_SUPPORT_ID3 34#endif 35/* define this to show debug info in menu */ 36/* #define LRC_DEBUG */ 37 38enum lrc_screen { 39 PLUGIN_OTHER = 0x200, 40 LRC_GOTO_MAIN, 41 LRC_GOTO_MENU, 42 LRC_GOTO_EDITOR, 43}; 44 45struct lrc_word { 46 long time_start; 47 short count; 48 short width; 49 unsigned char *word; 50}; 51 52struct lrc_brpos { 53 short count; 54 short width; 55}; 56 57struct lrc_line { 58 long time_start; 59 long old_time_start; 60 off_t file_offset; /* offset of time tag in file */ 61 short nword; 62 short width; 63 short nline[NB_SCREENS]; 64 struct lrc_line *next; 65 struct lrc_word *words; 66}; 67 68struct preferences { 69 /* display settings */ 70#if LCD_DEPTH > 1 71 unsigned active_color; 72 unsigned inactive_color; 73#endif 74 bool wrap; 75 bool wipe; 76 bool active_one_line; 77 int align; /* 0: left, 1: center, 2: right */ 78 bool statusbar_on; 79 bool display_title; 80 bool display_time; 81 bool backlight_on; 82 83 /* file settings */ 84 char lrc_directory[64]; 85 int encoding; 86#ifdef LRC_SUPPORT_ID3 87 bool read_id3; 88#endif 89}; 90 91static struct preferences prefs, old_prefs; 92static unsigned char *lrc_buffer; 93static size_t lrc_buffer_size; 94static size_t lrc_buffer_used, lrc_buffer_end; 95enum extention_types {LRC, LRC8, SNC, TXT, NUM_TYPES, ID3_SYLT, ID3_USLT}; 96static const char *extentions[NUM_TYPES] = { 97 ".lrc", ".lrc8", ".snc", ".txt", 98}; 99static struct lrc_info { 100 struct mp3entry *id3; 101 long elapsed; 102 long length; 103 long ff_rewind; 104 int audio_status; 105 char mp3_file[MAX_PATH]; 106 char lrc_file[MAX_PATH]; 107 char *title; /* use lrc_buffer */ 108 char *artist; /* use lrc_buffer */ 109 enum extention_types type; 110 long offset; /* msec */ 111 off_t offset_file_offset; /* offset of offset tag in file */ 112 int nlrcbrpos; 113 int nlrcline; 114 struct lrc_line *ll_head, **ll_tail; 115 bool found_lrc; 116 bool loaded_lrc; 117 bool changed_lrc; 118 bool too_many_lines; /* true if nlrcline >= max_lrclines after calc pos */ 119 bool wipe; /* false if lyrics is unsynched */ 120} current; 121static char temp_buf[MAX(MAX_LINE_LEN,MAX_PATH)]; 122static int uifont = -1; 123static int font_ui_height = 1; 124static struct viewport vp_info[NB_SCREENS]; 125static struct viewport vp_lyrics[NB_SCREENS]; 126 127#define AUDIO_PAUSE (current.audio_status & AUDIO_STATUS_PAUSE) 128#define AUDIO_PLAY (current.audio_status & AUDIO_STATUS_PLAY) 129#define AUDIO_STOP (!(current.audio_status & AUDIO_STATUS_PLAY)) 130 131/******************************* 132 * lrc_set_time 133 *******************************/ 134#define LST_SET_MSEC 0x00010000 135#define LST_SET_SEC 0x00020000 136#define LST_SET_MIN 0x00040000 137#define LST_SET_HOUR 0x00080000 138 139#include "lib/pluginlib_actions.h" 140#define LST_SET_TIME (LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN|LST_SET_HOUR) 141#define LST_OFF_Y 1 142static int lrc_set_time(const char *title, const char *unit, long *pval, 143 int step, int min, int max, int flags) 144{ 145 const struct button_mapping *lst_contexts[] = { 146 pla_main_ctx, 147#ifdef HAVE_REMOTE_LCD 148 pla_remote_ctx, 149#endif 150 }; 151 /* how many */ 152 const unsigned char formats[4][8] = {"%03ld.", "%02ld.", "%02ld:", "%02ld:"}; 153 const unsigned int maxs[4] = {1000, 60, 60, 24}; 154 const unsigned int scls[4] = {1, 1000, 60*1000, 60*60*1000}; 155 char buffer[32]; 156 long value = *pval, scl_step = step, i = 0; 157 int pos = 0, last_pos = 0, pos_min = 3, pos_max = 0; 158 int x = 0, y = 0, p_start = 0, p_end = 0; 159 int ret = 10; 160 161 if (!(flags&LST_SET_TIME)) 162 return -1; 163 164 for (i = 0; i < 4; i++) 165 { 166 if (flags&(LST_SET_MSEC<<i)) 167 { 168 if (pos_min > i) pos_min = i; 169 if (pos_max < i) pos_max = i; 170 } 171 } 172 pos = pos_min; 173 174 rb->button_clear_queue(); 175 rb->lcd_clear_display(); 176 rb->lcd_puts_scroll(0, LST_OFF_Y, title); 177 while (ret == 10) 178 { 179 int len = 0; 180 long abs_val = value; 181 long segvals[4] = {-1, -1, -1, -1}; 182 /* show negative value like -00:01 but 00:-1 */ 183 if (value < 0) 184 { 185 buffer[len++] = '-'; 186 abs_val = -value; 187 } 188 buffer[len] = 0; 189 /* calc value of each segments */ 190 for (i = pos_min; i <= pos_max; i++) 191 { 192 segvals[i] = abs_val % maxs[i]; 193 abs_val /= maxs[i]; 194 } 195 segvals[i-1] += abs_val * maxs[i-1]; 196 for (i = pos_max; i >= pos_min; i--) 197 { 198 if (pos == i) 199 { 200 rb->lcd_getstringsize(buffer, &x, &y); 201 p_start = len; 202 } 203 rb->snprintf(&buffer[len], 32-len, formats[i], segvals[i]); 204 len += rb->strlen(&buffer[len]); 205 if (pos == i) 206 p_end = len; 207 } 208 buffer[len-1] = 0; /* remove last separater */ 209 if (unit != NULL) 210 { 211 rb->snprintf(&buffer[len], 32-len, " (%s)", unit); 212 } 213 rb->lcd_puts(0, LST_OFF_Y+1, buffer); 214 if (pos_min != pos_max) 215 { 216 /* draw cursor */ 217 buffer[p_end-1] = 0; 218 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); 219 rb->lcd_putsxy(x, y*(1+LST_OFF_Y), &buffer[p_start]); 220 rb->lcd_set_drawmode(DRMODE_SOLID); 221 } 222 rb->lcd_update(); 223 int button = pluginlib_getaction(TIMEOUT_BLOCK, lst_contexts, ARRAYLEN(lst_contexts)); 224 int mult = 1; 225 switch (button) 226 { 227 case PLA_UP_REPEAT: 228 case PLA_DOWN_REPEAT: 229 mult *= 10; 230 /* fallthrough */ 231 case PLA_DOWN: 232 case PLA_UP: 233 if (button == PLA_DOWN_REPEAT || button == PLA_DOWN) 234 mult *= -1; 235 if (pos != last_pos) 236 { 237 scl_step = ((scls[pos]/scls[pos_min]+step-1)/step) * step; 238 last_pos = pos; 239 } 240 value += scl_step * mult; 241 if (value > max) 242 value = max; 243 if (value < min) 244 value = min; 245 break; 246 case PLA_LEFT: 247 case PLA_LEFT_REPEAT: 248 if (++pos > pos_max) 249 pos = pos_min; 250 break; 251 case PLA_RIGHT: 252 case PLA_RIGHT_REPEAT: 253 if (--pos < pos_min) 254 pos = pos_max; 255 break; 256 case PLA_SELECT: 257 *pval = value; 258 ret = 0; 259 break; 260 case PLA_CANCEL: 261 case PLA_EXIT: 262 rb->splash(HZ, "Cancelled"); 263 ret = -1; 264 break; 265 default: 266 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 267 ret = 1; 268 break; 269 } 270 } 271 rb->lcd_clear_display(); 272 rb->lcd_update(); 273 return ret; 274} 275 276/******************************* 277 * misc stuff 278 *******************************/ 279static void reset_current_data(void) 280{ 281 current.title = NULL; 282 current.artist = NULL; 283 current.offset = 0; 284 current.offset_file_offset = -1; 285 current.nlrcbrpos = 0; 286 current.nlrcline = 0; 287 current.ll_head = NULL; 288 current.ll_tail = &current.ll_head; 289 current.loaded_lrc = false; 290 current.changed_lrc = false; 291 current.too_many_lines = false; 292 lrc_buffer_used = 0; 293 lrc_buffer_end = lrc_buffer_size; 294} 295 296/* check space and add str to lrc_buffer. 297 * return NULL if there is not enough buffer. */ 298static char *lrcbufadd(const char*str, bool join) 299{ 300 if (join) lrc_buffer_used--; 301 size_t siz = rb->strlen(str)+1; 302 char *pos = &lrc_buffer[lrc_buffer_used]; 303 if (lrc_buffer_used + siz > lrc_buffer_end) 304 return NULL; 305 rb->strcpy(pos, str); 306 lrc_buffer_used += siz; 307 return pos; 308} 309static void *alloc_buf(size_t siz) 310{ 311 siz = (siz+3) & ~3; 312 if (lrc_buffer_used + siz > lrc_buffer_end) 313 return NULL; 314 lrc_buffer_end -= siz; 315 return &lrc_buffer[lrc_buffer_end]; 316} 317static void *new_lrc_word(long time_start, char *word, bool join) 318{ 319 struct lrc_word *lrc_word; 320 if ((lrc_word = alloc_buf(sizeof(struct lrc_word))) == NULL) 321 return NULL; 322 if ((lrc_word->word = lrcbufadd(word, join)) == NULL) 323 return NULL; 324 lrc_word->time_start = time_start; 325 return lrc_word; 326} 327static bool add_lrc_line(struct lrc_line *lrc_line, char *word) 328{ 329 lrc_line->nword = 0; 330 lrc_line->next = NULL; 331 lrc_line->words = NULL; 332 if (word) 333 { 334 if ((lrc_line->words = new_lrc_word(-1, word, false)) == NULL) 335 return false; 336 lrc_line->nword++; 337 } 338 *current.ll_tail = lrc_line; 339 current.ll_tail = &(lrc_line->next); 340 current.nlrcline++; 341 return true; 342} 343static struct lrc_line *get_lrc_line(int idx) 344{ 345 static struct lrc_line *lrc_line = NULL; 346 static int n = 0; 347 if (idx < n) 348 { 349 lrc_line = current.ll_head; 350 n = 0; 351 } 352 while (n < idx && lrc_line) 353 { 354 lrc_line = lrc_line->next; 355 n++; 356 } 357 return lrc_line; 358} 359static char *get_lrc_str(struct lrc_line *lrc_line) 360{ 361 return lrc_line->words[lrc_line->nword-1].word; 362} 363static long get_time_start(struct lrc_line *lrc_line) 364{ 365 if (!lrc_line) return current.length+20; 366 long time = lrc_line->time_start + current.offset; 367 return time < 0? 0: time; 368} 369static void set_time_start(struct lrc_line *lrc_line, long time_start) 370{ 371 time_start -= current.offset; 372 time_start -= time_start%10; 373 if (lrc_line->time_start != time_start) 374 { 375 lrc_line->time_start = time_start; 376 current.changed_lrc = true; 377 } 378} 379#define get_word_time_start(x) get_time_start((struct lrc_line *)(x)) 380#define set_word_time_start(x, t) set_time_start((struct lrc_line *)(x), (t)) 381 382static int format_time_tag(char *buf, long t) 383{ 384 return rb->snprintf(buf, 16, "%02ld:%02ld.%02ld", 385 t/60000, (t/1000)%60, (t/10)%100); 386} 387/* find start of next line */ 388static const char *lrc_skip_space(const char *str) 389{ 390 if (prefs.wrap) 391 { 392 while (*str && *str != '\n' && isspace(*str)) 393 str++; 394 } 395 if (*str == '\n') 396 str++; 397 return str; 398} 399 400static bool isbrchr(const unsigned char *str, int len) 401{ 402 const unsigned char *p = "!,-.:;? 、。!,.:;?―"; 403 if (isspace(*str)) 404 return true; 405 406 while(*p) 407 { 408 int n = rb->utf8seek(p, 1); 409 if (len == n && !rb->strncmp(p, str, len)) 410 return true; 411 p += n; 412 } 413 return false; 414} 415 416/* calculate how many lines is needed to display and store it. 417 * create cache if there is enough space in lrc_buffer. */ 418static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i) 419{ 420 struct lrc_brpos *lrc_brpos; 421 struct lrc_word *lrc_word; 422 int nlrcbrpos = 0, max_lrcbrpos; 423 uifont = rb->screens[0]->getuifont(); 424 struct font* pf = rb->font_get(uifont); 425 ucschar_t ch; 426 struct snap { 427 int count, width; 428 int nword; 429 int word_count, word_width; 430 const unsigned char *str; 431 } 432 sp, 433 cr; 434 435 lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */ 436 lrc_brpos = (struct lrc_brpos *) &lrc_buffer[lrc_buffer_used]; 437 max_lrcbrpos = (lrc_buffer_end-lrc_buffer_used) / sizeof(struct lrc_brpos); 438 439 if (!lrc_line) 440 { 441 /* calc info for all lrcs and store them if possible */ 442 size_t buffer_used = lrc_buffer_used; 443 bool too_many_lines = false; 444 current.too_many_lines = true; 445 for (lrc_line = current.ll_head; lrc_line; lrc_line = lrc_line->next) 446 { 447 FOR_NB_SCREENS(i) 448 { 449 lrc_brpos = calc_brpos(lrc_line, i); 450 if (!too_many_lines) 451 { 452 lrc_buffer_used += lrc_line->nline[i]*sizeof(struct lrc_brpos); 453 if (nlrcbrpos + lrc_line->nline[i] >= max_lrcbrpos) 454 { 455 too_many_lines = true; 456 lrc_buffer_used = buffer_used; 457 calc_brpos(lrc_line, i); 458 } 459 } 460 nlrcbrpos += lrc_line->nline[i]; 461 } 462 } 463 current.too_many_lines = too_many_lines; 464 lrc_buffer_used = buffer_used; 465 current.nlrcbrpos = nlrcbrpos; 466 return NULL; 467 } 468 469 if (!current.too_many_lines) 470 { 471 /* use stored infos. */ 472 struct lrc_line *temp_lrc = current.ll_head; 473 for (; temp_lrc != lrc_line; temp_lrc = temp_lrc->next) 474 { 475 lrc_brpos += temp_lrc->nline[SCREEN_MAIN]; 476#ifdef HAVE_REMOTE_LCD 477 lrc_brpos += temp_lrc->nline[SCREEN_REMOTE]; 478#endif 479 } 480#if NB_SCREENS >= 2 481 while (i) 482 lrc_brpos += lrc_line->nline[--i]; 483#endif 484 return lrc_brpos; 485 } 486 487 /* calculate number of lines, line width and char count for each line. */ 488 lrc_line->width = 0; 489 cr.nword = lrc_line->nword; 490 lrc_word = lrc_line->words+cr.nword; 491 cr.str = (lrc_word-1)->word; 492 sp.word_count = 0; 493 sp.word_width = 0; 494 sp.nword = 0; 495 sp.count = 0; 496 sp.width = 0; 497 do { 498 cr.count = 0; 499 cr.width = 0; 500 sp.str = NULL; 501 502 while (1) 503 { 504 while(cr.nword > 0 && cr.str >= (lrc_word-1)->word) 505 { 506 cr.nword--; 507 lrc_word--; 508 lrc_word->count = 0; 509 lrc_word->width = 0; 510 } 511 if (*cr.str == 0 || *cr.str == '\n') 512 break; 513 514 int c, w; 515 c = ((intptr_t)rb->utf8decode(cr.str, &ch) - (intptr_t)cr.str); 516 if (rb->is_diacritic(ch, NULL)) 517 w = 0; 518 else 519 w = rb->font_get_width(pf, ch); 520 if (cr.count && prefs.wrap && isbrchr(cr.str, c)) 521 { 522 /* remember position of last space */ 523 rb->memcpy(&sp, &cr, sizeof(struct snap)); 524 sp.word_count = lrc_word->count; 525 sp.word_width = lrc_word->width; 526 if (!isspace(*cr.str) && cr.width+w <= vp_lyrics[i].width) 527 { 528 sp.count += c; 529 sp.width += w; 530 sp.word_count += c; 531 sp.word_width += w; 532 sp.str += c; 533 } 534 } 535 if (cr.count && cr.width+w > vp_lyrics[i].width) 536 { 537 if (sp.str != NULL) /* wrap */ 538 { 539 rb->memcpy(&cr, &sp, sizeof(struct snap)); 540 lrc_word = lrc_line->words+cr.nword; 541 lrc_word->count = sp.word_count; 542 lrc_word->width = sp.word_width; 543 } 544 break; 545 } 546 cr.count += c; 547 cr.width += w; 548 lrc_word->count += c; 549 lrc_word->width += w; 550 cr.str += c; 551 } 552 lrc_line->width += cr.width; 553 lrc_brpos->count = cr.count; 554 lrc_brpos->width = cr.width; 555 nlrcbrpos++; 556 lrc_brpos++; 557 cr.str = lrc_skip_space(cr.str); 558 } while (*cr.str && nlrcbrpos < max_lrcbrpos); 559 lrc_line->nline[i] = nlrcbrpos; 560 561 while (cr.nword > 0) 562 { 563 cr.nword--; 564 lrc_word--; 565 lrc_word->count = 0; 566 lrc_word->width = 0; 567 } 568 return lrc_brpos-nlrcbrpos; 569} 570 571/* sort lyrics by time using stable sort. */ 572static void sort_lrcs(void) 573{ 574 struct lrc_line *p = current.ll_head, **q = NULL, *t; 575 long time_max = 0; 576 577 current.ll_head = NULL; 578 current.ll_tail = &current.ll_head; 579 while (p != NULL) 580 { 581 t = p->next; 582 /* remove problematic lrc_lines. 583 * it would cause problem in display_lrc_line() if nword is 0. */ 584 if (p->nword) 585 { 586 q = p->time_start >= time_max? current.ll_tail: &current.ll_head; 587 while ((*q) && (*q)->time_start <= p->time_start) 588 q = &((*q)->next); 589 p->next = *q; 590 *q = p; 591 if (!p->next) 592 { 593 time_max = p->time_start; 594 current.ll_tail = &p->next; 595 } 596 } 597 p = t; 598 } 599 if (!current.too_many_lines) 600 calc_brpos(NULL, 0); /* stored data depends on order of lrcs if exist */ 601} 602static void init_time_tag(void) 603{ 604 struct lrc_line *lrc_line = current.ll_head; 605 int nline = 0; 606 if (current.type == TXT || current.type == ID3_USLT) 607 { 608 /* set time tag according to length of audio and total line count 609 * for not synched lyrics, so that scroll speed is almost constant. */ 610 for (; lrc_line; lrc_line = lrc_line->next) 611 { 612 lrc_line->time_start = nline * current.length / current.nlrcbrpos; 613 lrc_line->time_start -= lrc_line->time_start%10; 614 lrc_line->old_time_start = -1; 615 nline += lrc_line->nline[SCREEN_MAIN]; 616#ifdef HAVE_REMOTE_LCD 617 nline += lrc_line->nline[SCREEN_REMOTE]; 618#endif 619 } 620 } 621 else 622 { 623 /* reset timetags to the value read from file */ 624 for (; lrc_line; lrc_line = lrc_line->next) 625 { 626 lrc_line->time_start = lrc_line->old_time_start; 627 } 628 sort_lrcs(); 629 } 630 current.changed_lrc = false; 631} 632 633/******************************* 634 * Serch lrc file. 635 *******************************/ 636 637/* search in same or parent directries of playing file. 638 * assume playing file is /aaa/bbb/ccc/ddd.mp3, 639 * this function searchs lrc file following order. 640 * /aaa/bbb/ccc/ddd.lrc 641 * /aaa/bbb/ddd.lrc 642 * /aaa/ddd.lrc 643 * /ddd.lrc 644 */ 645 646static bool find_lrc_file_helper(const char *base_dir) 647{ 648 char fname[MAX_PATH]; 649 char *names[3] = {NULL, NULL, NULL}; 650 char *p, *dir; 651 int i, len; 652 /* /aaa/bbb/ccc/ddd.mp3 653 * dir <--q names[0] 654 */ 655 656 /* assuming file name starts with '/' */ 657 rb->strcpy(temp_buf, current.mp3_file); 658 /* get file name and remove extension */ 659 names[0] = rb->strrchr(temp_buf, '/')+1; 660 if ((p = rb->strrchr(names[0], '.')) != NULL) 661 *p = 0; 662 if (current.id3->title && rb->strcmp(names[0], current.id3->title)) 663 { 664 rb->strlcpy(fname, current.id3->title, sizeof(fname)); 665 rb->fix_path_part(fname, 0, sizeof(fname) - 1); 666 names[1] = fname; 667 } 668 669 dir = temp_buf; 670 p = names[0]-1; 671 do { 672 int n; 673 *p = 0; 674 for (n = 0; ; n++) 675 { 676 if (n == 0) 677 { 678 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/", 679 base_dir, dir); 680 } 681 else if (n == 1) 682 { 683 /* check file in subfolder named prefs.lrc_directory 684 * in the directory of mp3 file. */ 685 if (prefs.lrc_directory[0] == '/') 686 { 687 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/", 688 dir, prefs.lrc_directory); 689 } 690 else 691 continue; 692 } 693 else 694 break; 695 DEBUGF("check file in %s\n", current.lrc_file); 696 if (!rb->dir_exists(current.lrc_file)) 697 continue; 698 for (current.type = 0; current.type < NUM_TYPES; current.type++) 699 { 700 for (i = 0; names[i] != NULL; i++) 701 { 702 rb->snprintf(&current.lrc_file[len], MAX_PATH-len, 703 "%s%s", names[i], extentions[current.type]); 704 if (rb->file_exists(current.lrc_file)) 705 { 706 DEBUGF("found: `%s'\n", current.lrc_file); 707 return true; 708 } 709 } 710 } 711 } 712 } while ((p = rb->strrchr(dir, '/')) != NULL); 713 return false; 714} 715 716/* return true if a lrc file is found */ 717static bool find_lrc_file(void) 718{ 719 reset_current_data(); 720 721 DEBUGF("find lrc file for `%s'\n", current.mp3_file); 722 /* find .lrc file */ 723 if (find_lrc_file_helper("")) 724 return true; 725 if (prefs.lrc_directory[0] == '/' && rb->dir_exists(prefs.lrc_directory)) 726 { 727 if (find_lrc_file_helper(prefs.lrc_directory)) 728 return true; 729 } 730 731 current.lrc_file[0] = 0; 732 return false; 733} 734 735/******************************* 736 * Load file. 737 *******************************/ 738 739/* check tag format and calculate value of the tag. 740 * supported tag: ti, ar, offset 741 * supported format of time tag: [mm:ss], [mm:ss.xx], [mm:ss.xxx] 742 * returns value of timega if tag is time tag, -1 if tag is supported tag, 743 * -10 otherwise. 744 */ 745static char *parse_int(char *ptr, int *val) 746{ 747 *val = rb->atoi(ptr); 748 while (isdigit(*ptr)) ptr++; 749 return ptr; 750} 751static long get_time_value(char *tag, bool read_id_tags, off_t file_offset) 752{ 753 long time; 754 char *ptr; 755 int val; 756 757 if (read_id_tags) 758 { 759 if (!rb->strncmp(tag, "ti:", 3)) 760 { 761 if (!current.id3->title || rb->strcmp(&tag[3], current.id3->title)) 762 current.title = lrcbufadd(&tag[3], false); 763 return -1; 764 } 765 if (!rb->strncmp(tag, "ar:", 3)) 766 { 767 if (!current.id3->artist || rb->strcmp(&tag[3], current.id3->artist)) 768 current.artist = lrcbufadd(&tag[3], false); 769 return -1; 770 } 771 if (!rb->strncmp(tag, "offset:", 7)) 772 { 773 current.offset = rb->atoi(&tag[7]); 774 current.offset_file_offset = file_offset; 775 return -1; 776 } 777 } 778 779 /* minute */ 780 ptr = parse_int(tag, &val); 781 if (ptr-tag < 1 || ptr-tag > 2 || *ptr != ':') 782 return -10; 783 time = val * 60000; 784 /* second */ 785 tag = ptr+1; 786 ptr = parse_int(tag, &val); 787 if (ptr-tag != 2 || (*ptr != '.' && *ptr != ':' && *ptr != '\0')) 788 return -10; 789 time += val * 1000; 790 791 if (*ptr != '\0') 792 { 793 /* milliseccond */ 794 tag = ptr+1; 795 ptr = parse_int(tag, &val); 796 if (ptr-tag < 2 || ptr-tag > 3 || *ptr != '\0') 797 return -10; 798 time += ((ptr-tag)==3 ?val: val*10); 799 } 800 801 return time; 802} 803 804/* format: 805 * [time tag]line 806 * [time tag]...[time tag]line 807 * [time tag]<word time tag>word<word time tag>...<word time tag> 808 */ 809static bool parse_lrc_line(char *line, off_t file_offset) 810{ 811 struct lrc_line *lrc_line = NULL, *first_lrc_line = NULL; 812 long time, time_start; 813 char *str, *tagstart, *tagend; 814 struct lrc_word *lrc_word; 815 int nword = 0; 816 817 /* parse [time tag]...[time tag] type tags */ 818 str = line; 819 while (1) 820 { 821 if (*str != '[') break; 822 tagend = rb->strchr(str, ']'); 823 if (tagend == NULL) break; 824 *tagend = 0; 825 time = get_time_value(str+1, !lrc_line, file_offset); 826 *tagend++ = ']'; 827 if (time < 0) 828 break; 829 lrc_line = alloc_buf(sizeof(struct lrc_line)); 830 if (lrc_line == NULL) 831 return false; 832 if (!first_lrc_line) 833 first_lrc_line = lrc_line; 834 lrc_line->file_offset = file_offset; 835 lrc_line->time_start = (time/10)*10; 836 lrc_line->old_time_start = lrc_line->time_start; 837 add_lrc_line(lrc_line, NULL); 838 file_offset += (intptr_t)tagend - (intptr_t)str; 839 str = tagend; 840 } 841 if (!first_lrc_line) 842 return true; /* no time tag in line */ 843 844 lrc_line = first_lrc_line; 845 if (lrcbufadd("", false) == NULL) 846 return false; 847 848 /* parse <word time tag>...<word time tag> type tags */ 849 /* [time tag]...[time tag]line type tags share lrc_line->words and can't 850 * use lrc_line->words->timestart. use lrc_line->time_start instead. */ 851 time_start = -1; 852 tagstart = str; 853 while (*tagstart) 854 { 855 tagstart = rb->strchr(tagstart, '<'); 856 if (!tagstart) break; 857 tagend = rb->strchr(tagstart, '>'); 858 if (!tagend) break; 859 *tagend = 0; 860 time = get_time_value(tagstart+1, false, 861 file_offset + ((intptr_t)tagstart - (intptr_t)str)); 862 *tagend++ = '>'; 863 if (time < 0) 864 { 865 tagstart++; 866 continue; 867 } 868 *tagstart = 0; 869 /* found word time tag. */ 870 if (*str || time_start != -1) 871 { 872 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL) 873 return false; 874 nword++; 875 } 876 file_offset += (intptr_t)tagend - (intptr_t)str; 877 tagstart = str = tagend; 878 time_start = time; 879 } 880 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL) 881 return false; 882 nword++; 883 884 /* duplicate lrc_lines */ 885 while (lrc_line) 886 { 887 lrc_line->nword = nword; 888 lrc_line->words = lrc_word; 889 lrc_line = lrc_line->next; 890 } 891 892 return true; 893} 894 895/* format: 896 * \xa2\xe2hhmmssxx\xa2\xd0 897 * line 1 898 * line 2 899 * \xa2\xe2hhmmssxx\xa2\xd0 900 * line 3 901 * ... 902 */ 903static bool parse_snc_line(char *line, off_t file_offset) 904{ 905#define SNC_TAG_START "\xa2\xe2" 906#define SNC_TAG_END "\xa2\xd0" 907 908 /* SNC_TAG can be dencoded, so use 909 * temp_buf which contains native data */ 910 if (!rb->memcmp(temp_buf, SNC_TAG_START, 2) 911 && !rb->memcmp(temp_buf+10, SNC_TAG_END, 2)) /* time tag */ 912 { 913 const char *pos = temp_buf+2; /* skip SNC_TAG_START */ 914 int hh, mm, ss, xx; 915 916 hh = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2; 917 mm = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2; 918 ss = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2; 919 xx = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2; 920 pos += 2; /* skip SNC_TAG_END */ 921 922 /* initialize */ 923 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line)); 924 if (lrc_line == NULL) 925 return false; 926 lrc_line->file_offset = file_offset+2; 927 lrc_line->time_start = hh*3600000+mm*60000+ss*1000+xx*10; 928 lrc_line->old_time_start = lrc_line->time_start; 929 if (!add_lrc_line(lrc_line, "")) 930 return false; 931 if (pos[0]==0) 932 return true; 933 934 /* encode rest of line and add to buffer */ 935 rb->iso_decode(pos, line, prefs.encoding, rb->strlen(pos)+1); 936 } 937 if (current.ll_head) 938 { 939 rb->strcat(line, "\n"); 940 if (lrcbufadd(line, true) == NULL) 941 return false; 942 } 943 return true; 944} 945 946static bool parse_txt_line(char *line, off_t file_offset) 947{ 948 /* initialize */ 949 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line)); 950 if (lrc_line == NULL) 951 return false; 952 lrc_line->file_offset = file_offset; 953 lrc_line->time_start = 0; 954 lrc_line->old_time_start = -1; 955 if (!add_lrc_line(lrc_line, line)) 956 return false; 957 return true; 958} 959 960static void load_lrc_file(void) 961{ 962 char utf8line[MAX_LINE_LEN*3]; 963 int fd; 964 int encoding = prefs.encoding; 965 bool (*line_parser)(char *line, off_t) = NULL; 966 off_t file_offset, readsize; 967 968 switch(current.type) 969 { 970 case LRC8: 971 encoding = UTF_8; /* .lrc8 is utf8 */ 972 /* fall through */ 973 case LRC: 974 line_parser = parse_lrc_line; 975 break; 976 case SNC: 977 line_parser = parse_snc_line; 978 break; 979 case TXT: 980 line_parser = parse_txt_line; 981 break; 982 default: 983 return; 984 } 985 986 fd = rb->open(current.lrc_file, O_RDONLY); 987 if (fd < 0) return; 988 989 { 990 /* check encoding */ 991 #define BOM "\xef\xbb\xbf" 992 #define BOM_SIZE 3 993 unsigned char header[BOM_SIZE]; 994 unsigned char* (*utf_decode)(const unsigned char *, 995 unsigned char *, int) = NULL; 996 rb->read(fd, header, BOM_SIZE); 997 if (!rb->memcmp(header, BOM, BOM_SIZE)) /* UTF-8 */ 998 { 999 encoding = UTF_8; 1000 } 1001 else if (!rb->memcmp(header, "\xff\xfe", 2)) /* UTF-16LE */ 1002 { 1003 utf_decode = rb->utf16LEdecode; 1004 } 1005 else if (!rb->memcmp(header, "\xfe\xff", 2)) /* UTF-16BE */ 1006 { 1007 utf_decode = rb->utf16BEdecode; 1008 } 1009 else 1010 { 1011 rb->lseek(fd, 0, SEEK_SET); 1012 } 1013 1014 if (utf_decode) 1015 { 1016 /* convert encoding of file from UTF-16 to UTF-8 */ 1017 char temp_file[MAX_PATH]; 1018 int fe; 1019 rb->lseek(fd, 2, SEEK_SET); 1020 rb->snprintf(temp_file, MAX_PATH, "%s~", current.lrc_file); 1021 fe = rb->creat(temp_file, 0666); 1022 if (fe < 0) 1023 { 1024 rb->close(fd); 1025 return; 1026 } 1027 rb->write(fe, BOM, BOM_SIZE); 1028 while ((readsize = rb->read(fd, temp_buf, MAX_LINE_LEN)) > 0) 1029 { 1030 char *end = utf_decode(temp_buf, utf8line, readsize/2); 1031 rb->write(fe, utf8line, end-utf8line); 1032 } 1033 rb->close(fe); 1034 rb->close(fd); 1035 rb->remove(current.lrc_file); 1036 rb->rename(temp_file, current.lrc_file); 1037 fd = rb->open(current.lrc_file, O_RDONLY); 1038 if (fd < 0) return; 1039 rb->lseek(fd, BOM_SIZE, SEEK_SET); /* skip bom */ 1040 encoding = UTF_8; 1041 } 1042 } 1043 1044 file_offset = rb->lseek(fd, 0, SEEK_CUR); /* used in line_parser */ 1045 while ((readsize = rb->read_line(fd, temp_buf, MAX_LINE_LEN)) > 0) 1046 { 1047 /* note: parse_snc_line() reads temp_buf for native data. */ 1048 rb->iso_decode(temp_buf, utf8line, encoding, readsize+1); 1049 if (!line_parser(utf8line, file_offset)) 1050 break; 1051 file_offset += readsize; 1052 } 1053 rb->close(fd); 1054 1055 current.loaded_lrc = true; 1056 calc_brpos(NULL, 0); 1057 init_time_tag(); 1058 1059 return; 1060} 1061 1062#ifdef LRC_SUPPORT_ID3 1063/******************************* 1064 * read lyrics from id3 1065 *******************************/ 1066static unsigned long unsync(unsigned long b0, unsigned long b1, 1067 unsigned long b2, unsigned long b3) 1068{ 1069 return (((long)(b0 & 0x7F) << (3*7)) | 1070 ((long)(b1 & 0x7F) << (2*7)) | 1071 ((long)(b2 & 0x7F) << (1*7)) | 1072 ((long)(b3 & 0x7F) << (0*7))); 1073} 1074 1075static unsigned long bytes2int(unsigned long b0, unsigned long b1, 1076 unsigned long b2, unsigned long b3) 1077{ 1078 return (((long)(b0 & 0xFF) << (3*8)) | 1079 ((long)(b1 & 0xFF) << (2*8)) | 1080 ((long)(b2 & 0xFF) << (1*8)) | 1081 ((long)(b3 & 0xFF) << (0*8))); 1082} 1083 1084static int unsynchronize(char* tag, int len, bool *ff_found) 1085{ 1086 int i; 1087 unsigned char c; 1088 unsigned char *rp, *wp; 1089 bool _ff_found = false; 1090 if(ff_found) _ff_found = *ff_found; 1091 1092 wp = rp = (unsigned char *)tag; 1093 1094 rp = (unsigned char *)tag; 1095 for(i = 0; i<len; i++) { 1096 /* Read the next byte and write it back, but don't increment the 1097 write pointer */ 1098 c = *rp++; 1099 *wp = c; 1100 if(_ff_found) { 1101 /* Increment the write pointer if it isn't an unsynch pattern */ 1102 if(c != 0) 1103 wp++; 1104 _ff_found = false; 1105 } else { 1106 if(c == 0xff) 1107 _ff_found = true; 1108 wp++; 1109 } 1110 } 1111 if(ff_found) *ff_found = _ff_found; 1112 return (intptr_t)wp - (intptr_t)tag; 1113} 1114 1115static int read_unsynched(int fd, void *buf, int len, bool *ff_found) 1116{ 1117 int i; 1118 int rc; 1119 int remaining = len; 1120 char *wp; 1121 1122 wp = buf; 1123 1124 while(remaining) { 1125 rc = rb->read(fd, wp, remaining); 1126 if(rc <= 0) 1127 return rc; 1128 1129 i = unsynchronize(wp, remaining, ff_found); 1130 remaining -= i; 1131 wp += i; 1132 } 1133 1134 return len; 1135} 1136 1137static unsigned char* utf8cpy(const unsigned char *src, 1138 unsigned char *dst, int count) 1139{ 1140 rb->strlcpy(dst, src, count+1); 1141 return dst+rb->strlen(dst); 1142} 1143 1144static void parse_id3v2(int fd) 1145{ 1146 int minframesize; 1147 int size; 1148 long framelen; 1149 char header[10]; 1150 char tmp[8]; 1151 unsigned char version; 1152 int bytesread = 0; 1153 unsigned char global_flags; 1154 int flags; 1155 bool global_unsynch = false; 1156 bool global_ff_found = false; 1157 bool unsynch = false; 1158 int rc; 1159 enum {NOLT, SYLT, USLT} type = NOLT; 1160 1161 /* Bail out if the tag is shorter than 10 bytes */ 1162 if(current.id3->id3v2len < 10) 1163 return; 1164 1165 /* Read the ID3 tag version from the header */ 1166 if(10 != rb->read(fd, header, 10)) 1167 return; 1168 1169 /* Get the total ID3 tag size */ 1170 size = current.id3->id3v2len - 10; 1171 1172 version = current.id3->id3version; 1173 switch ( version ) 1174 { 1175 case ID3_VER_2_2: 1176 minframesize = 8; 1177 break; 1178 1179 case ID3_VER_2_3: 1180 minframesize = 12; 1181 break; 1182 1183 case ID3_VER_2_4: 1184 minframesize = 12; 1185 break; 1186 1187 default: 1188 /* unsupported id3 version */ 1189 return; 1190 } 1191 1192 global_flags = header[5]; 1193 1194 /* Skip the extended header if it is present */ 1195 if(global_flags & 0x40) { 1196 1197 if(version == ID3_VER_2_3) { 1198 if(10 != rb->read(fd, header, 10)) 1199 return; 1200 /* The 2.3 extended header size doesn't include the header size 1201 field itself. Also, it is not unsynched. */ 1202 framelen = 1203 bytes2int(header[0], header[1], header[2], header[3]) + 4; 1204 1205 /* Skip the rest of the header */ 1206 rb->lseek(fd, framelen - 10, SEEK_CUR); 1207 } 1208 1209 if(version >= ID3_VER_2_4) { 1210 if(4 != rb->read(fd, header, 4)) 1211 return; 1212 1213 /* The 2.4 extended header size does include the entire header, 1214 so here we can just skip it. This header is unsynched. */ 1215 framelen = unsync(header[0], header[1], 1216 header[2], header[3]); 1217 1218 rb->lseek(fd, framelen - 4, SEEK_CUR); 1219 } 1220 } 1221 1222 /* Is unsynchronization applied? */ 1223 if(global_flags & 0x80) { 1224 global_unsynch = true; 1225 } 1226 1227 /* We must have at least minframesize bytes left for the 1228 * remaining frames to be interesting */ 1229 while (size >= minframesize) { 1230 flags = 0; 1231 1232 /* Read frame header and check length */ 1233 if(version >= ID3_VER_2_3) { 1234 if(global_unsynch && version <= ID3_VER_2_3) 1235 rc = read_unsynched(fd, header, 10, &global_ff_found); 1236 else 1237 rc = rb->read(fd, header, 10); 1238 if(rc != 10) 1239 return; 1240 /* Adjust for the 10 bytes we read */ 1241 size -= 10; 1242 1243 flags = bytes2int(0, 0, header[8], header[9]); 1244 1245 if (version >= ID3_VER_2_4) { 1246 framelen = unsync(header[4], header[5], 1247 header[6], header[7]); 1248 } else { 1249 /* version .3 files don't use synchsafe ints for 1250 * size */ 1251 framelen = bytes2int(header[4], header[5], 1252 header[6], header[7]); 1253 } 1254 } else { 1255 if(6 != rb->read(fd, header, 6)) 1256 return; 1257 /* Adjust for the 6 bytes we read */ 1258 size -= 6; 1259 1260 framelen = bytes2int(0, header[3], header[4], header[5]); 1261 } 1262 1263 if(framelen == 0){ 1264 if (header[0] == 0 && header[1] == 0 && header[2] == 0) 1265 return; 1266 else 1267 continue; 1268 } 1269 1270 unsynch = false; 1271 1272 if(flags) 1273 { 1274 if (version >= ID3_VER_2_4) { 1275 if(flags & 0x0040) { /* Grouping identity */ 1276 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ 1277 framelen--; 1278 } 1279 } else { 1280 if(flags & 0x0020) { /* Grouping identity */ 1281 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ 1282 framelen--; 1283 } 1284 } 1285 1286 if(flags & 0x000c) /* Compression or encryption */ 1287 { 1288 /* Skip it */ 1289 size -= framelen; 1290 rb->lseek(fd, framelen, SEEK_CUR); 1291 continue; 1292 } 1293 1294 if(flags & 0x0002) /* Unsynchronization */ 1295 unsynch = true; 1296 1297 if (version >= ID3_VER_2_4) { 1298 if(flags & 0x0001) { /* Data length indicator */ 1299 if(4 != rb->read(fd, tmp, 4)) 1300 return; 1301 1302 /* We don't need the data length */ 1303 framelen -= 4; 1304 } 1305 } 1306 } 1307 1308 if (framelen == 0) 1309 continue; 1310 1311 if (framelen < 0) 1312 return; 1313 1314 if(!rb->memcmp( header, "SLT", 3 ) || 1315 !rb->memcmp( header, "SYLT", 4 )) 1316 { 1317 /* found a supported tag */ 1318 type = SYLT; 1319 break; 1320 } 1321 else if(!rb->memcmp( header, "ULT", 3 ) || 1322 !rb->memcmp( header, "USLT", 4 )) 1323 { 1324 /* found a supported tag */ 1325 type = USLT; 1326 break; 1327 } 1328 else 1329 { 1330 /* not a supported tag*/ 1331 if(global_unsynch && version <= ID3_VER_2_3) { 1332 size -= read_unsynched(fd, lrc_buffer, framelen, &global_ff_found); 1333 } else { 1334 size -= framelen; 1335 if( rb->lseek(fd, framelen, SEEK_CUR) == -1 ) 1336 return; 1337 } 1338 } 1339 } 1340 if(type == NOLT) 1341 return; 1342 1343 int encoding = 0, chsiz; 1344 char *tag, *p, utf8line[MAX_LINE_LEN*3]; 1345 unsigned char* (*utf_decode)(const unsigned char *, 1346 unsigned char *, int) = NULL; 1347 /* use middle of lrc_buffer to store tag data. */ 1348 if(framelen >= LRC_BUFFER_SIZE/3) 1349 framelen = LRC_BUFFER_SIZE/3-1; 1350 tag = lrc_buffer+LRC_BUFFER_SIZE*2/3-framelen-1; 1351 if(global_unsynch && version <= ID3_VER_2_3) 1352 bytesread = read_unsynched(fd, tag, framelen, &global_ff_found); 1353 else 1354 bytesread = rb->read(fd, tag, framelen); 1355 1356 if( bytesread != framelen ) 1357 return; 1358 1359 if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) 1360 bytesread = unsynchronize(tag, bytesread, NULL); 1361 1362 tag[bytesread] = 0; 1363 encoding = tag[0]; 1364 p = tag; 1365 /* skip some data */ 1366 if(type == SYLT) { 1367 p += 6; 1368 } else { 1369 p += 4; 1370 } 1371 1372 /* check encoding and skip content descriptor */ 1373 switch (encoding) { 1374 case 0x01: /* Unicode with or without BOM */ 1375 case 0x02: 1376 1377 /* Now check if there is a BOM 1378 (zero-width non-breaking space, 0xfeff) 1379 and if it is in little or big endian format */ 1380 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */ 1381 utf_decode = rb->utf16LEdecode; 1382 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */ 1383 utf_decode = rb->utf16BEdecode; 1384 } else 1385 utf_decode = NULL; 1386 1387 encoding = NUM_CODEPAGES; 1388 do { 1389 size = p[0] | p[1]; 1390 p += 2; 1391 } while(size); 1392 chsiz = 2; 1393 break; 1394 1395 default: 1396 utf_decode = utf8cpy; 1397 if(encoding == 0x03) /* UTF-8 encoded string */ 1398 encoding = UTF_8; 1399 else 1400 encoding = prefs.encoding; 1401 p += rb->strlen(p)+1; 1402 chsiz = 1; 1403 break; 1404 } 1405 if(encoding == NUM_CODEPAGES) 1406 { 1407 /* check if there is a BOM */ 1408 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */ 1409 utf_decode = rb->utf16LEdecode; 1410 p += 2; 1411 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */ 1412 utf_decode = rb->utf16BEdecode; 1413 p += 2; 1414 } else if(!utf_decode) { 1415 /* If there is no BOM (which is a specification violation), 1416 let's try to guess it. If one of the bytes is 0x00, it is 1417 probably the most significant one. */ 1418 if(p[1] == 0) 1419 utf_decode = rb->utf16LEdecode; 1420 else 1421 utf_decode = rb->utf16BEdecode; 1422 } 1423 } 1424 bytesread -= (intptr_t)p - (intptr_t)tag; 1425 tag = p; 1426 1427 while ( bytesread > 0 1428 && lrc_buffer_used+bytesread < LRC_BUFFER_SIZE*2/3 1429 && LRC_BUFFER_SIZE*2/3 < lrc_buffer_end) 1430 { 1431 bool is_crlf = false; 1432 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line)); 1433 if(!lrc_line) 1434 break; 1435 lrc_line->file_offset = -1; 1436 if(type == USLT) 1437 { 1438 /* replace 0x0a and 0x0d with 0x00 */ 1439 p = tag; 1440 while(1) { 1441 utf_decode(p, tmp, 2); 1442 if(!tmp[0]) break; 1443 if(tmp[0] == 0x0d || tmp[0] == 0x0a) 1444 { 1445 if(tmp[0] == 0x0d && tmp[1] == 0x0a) 1446 is_crlf = true; 1447 p[0] = 0; 1448 p[chsiz-1] = 0; 1449 break; 1450 } 1451 p += chsiz; 1452 } 1453 } 1454 if(encoding == NUM_CODEPAGES) 1455 { 1456 unsigned char* utf8 = utf8line; 1457 p = tag; 1458 do { 1459 utf8 = utf_decode(p, utf8, 1); 1460 p += 2; 1461 } while(*(utf8-1)); 1462 } 1463 else 1464 { 1465 size = rb->strlen(tag)+1; 1466 rb->iso_decode(tag, utf8line, encoding, size); 1467 p = tag+size; 1468 } 1469 1470 if(type == SYLT) { /* timestamp */ 1471 lrc_line->time_start = bytes2int(p[0], p[1], p[2], p[3]); 1472 lrc_line->old_time_start = lrc_line->time_start; 1473 p += 4; 1474 utf_decode(p, tmp, 1); 1475 if(tmp[0] == 0x0a) 1476 p += chsiz; 1477 } else { /* USLT */ 1478 lrc_line->time_start = 0; 1479 lrc_line->old_time_start = -1; 1480 if(is_crlf) p += chsiz; 1481 } 1482 bytesread -= (intptr_t)p - (intptr_t)tag; 1483 tag = p; 1484 if(!add_lrc_line(lrc_line, utf8line)) 1485 break; 1486 } 1487 1488 current.type = ID3_SYLT-SYLT+type; 1489 rb->strcpy(current.lrc_file, current.mp3_file); 1490 1491 current.loaded_lrc = true; 1492 calc_brpos(NULL, 0); 1493 init_time_tag(); 1494 1495 return; 1496} 1497 1498static bool read_id3(void) 1499{ 1500 int fd; 1501 1502 if(current.id3->codectype != AFMT_MPA_L1 1503 && current.id3->codectype != AFMT_MPA_L2 1504 && current.id3->codectype != AFMT_MPA_L3) 1505 return false; 1506 1507 fd = rb->open(current.mp3_file, O_RDONLY); 1508 if(fd < 0) return false; 1509 current.loaded_lrc = false; 1510 parse_id3v2(fd); 1511 rb->close(fd); 1512 return current.loaded_lrc; 1513} 1514#endif /* LRC_SUPPORT_ID3 */ 1515 1516/******************************* 1517 * Display information 1518 *******************************/ 1519static void display_state(void) 1520{ 1521 const char *str = NULL; 1522 1523 if (AUDIO_STOP) 1524 str = "Audio Stopped"; 1525 else if (current.found_lrc) 1526 { 1527 if (!current.loaded_lrc) 1528 str = "Loading lrc"; 1529 else if (!current.ll_head) 1530 str = "No lyrics"; 1531 } 1532 1533 const char *info = NULL; 1534 1535 if (AUDIO_PLAY && prefs.display_title) 1536 { 1537 char *title = (current.title? current.title: current.id3->title); 1538 char *artist = (current.artist? current.artist: current.id3->artist); 1539 1540 if (artist != NULL && title != NULL) 1541 { 1542 rb->snprintf(temp_buf, MAX_LINE_LEN, "%s/%s", title, artist); 1543 info = temp_buf; 1544 } 1545 else if (title != NULL) 1546 info = title; 1547 else if (current.mp3_file[0] == '/') 1548 info = rb->strrchr(current.mp3_file, '/')+1; 1549 else 1550 info = "(no info)"; 1551 } 1552 1553 int w, h; 1554 struct screen* display; 1555 FOR_NB_SCREENS(i) 1556 { 1557 display = rb->screens[i]; 1558 display->set_viewport(&vp_info[i]); 1559 display->clear_viewport(); 1560 if (info) 1561 display->puts_scroll(0, 0, info); 1562 if (str) 1563 { 1564 display->set_viewport(&vp_lyrics[i]); 1565 display->clear_viewport(); 1566 display->getstringsize(str, &w, &h); 1567 if (vp_lyrics[i].width - w < 0) 1568 display->puts_scroll(0, vp_lyrics[i].height/font_ui_height/2, 1569 str); 1570 else 1571 display->putsxy((vp_lyrics[i].width - w)*prefs.align/2, 1572 (vp_lyrics[i].height-font_ui_height)/2, str); 1573 display->set_viewport(&vp_info[i]); 1574 } 1575 display->update_viewport(); 1576 display->set_viewport(NULL); 1577 } 1578} 1579 1580static void display_time(void) 1581{ 1582 rb->snprintf(temp_buf, MAX_LINE_LEN, "%ld:%02ld/%ld:%02ld", 1583 current.elapsed/60000, (current.elapsed/1000)%60, 1584 current.length/60000, (current.length)/1000%60); 1585 int y = (prefs.display_title? font_ui_height:0); 1586 FOR_NB_SCREENS(i) 1587 { 1588 struct screen* display = rb->screens[i]; 1589 display->set_viewport(&vp_info[i]); 1590 display->setfont(FONT_SYSFIXED); 1591 display->putsxy(0, y, temp_buf); 1592 rb->gui_scrollbar_draw(display, 0, y+SYSFONT_HEIGHT+1, 1593 vp_info[i].width, SYSFONT_HEIGHT-2, 1594 current.length, 0, current.elapsed, HORIZONTAL); 1595 display->update_viewport_rect(0, y, vp_info[i].width, SYSFONT_HEIGHT*2); 1596 display->setfont(uifont); 1597 display->set_viewport(NULL); 1598 } 1599} 1600 1601/******************************* 1602 * Display lyrics 1603 *******************************/ 1604static inline void set_to_default(struct screen *display) 1605{ 1606#if (LCD_DEPTH > 1) 1607#ifdef HAVE_REMOTE_LCD 1608 if (display->screen_type != SCREEN_REMOTE) 1609#endif 1610 display->set_foreground(prefs.active_color); 1611#endif 1612 display->set_drawmode(DRMODE_SOLID); 1613} 1614static inline void set_to_active(struct screen *display) 1615{ 1616#if (LCD_DEPTH > 1) 1617#ifdef HAVE_REMOTE_LCD 1618 if (display->screen_type == SCREEN_REMOTE) 1619 display->set_drawmode(DRMODE_INVERSEVID); 1620 else 1621#endif 1622 { 1623 display->set_foreground(prefs.active_color); 1624 display->set_drawmode(DRMODE_SOLID); 1625 } 1626#else /* LCD_DEPTH == 1 */ 1627 display->set_drawmode(DRMODE_INVERSEVID); 1628#endif 1629} 1630static inline void set_to_inactive(struct screen *display) 1631{ 1632#if (LCD_DEPTH > 1) 1633#ifdef HAVE_REMOTE_LCD 1634 if (display->screen_type != SCREEN_REMOTE) 1635#endif 1636 display->set_foreground(prefs.inactive_color); 1637#endif 1638 display->set_drawmode(DRMODE_SOLID); 1639} 1640 1641static int display_lrc_line(struct lrc_line *lrc_line, int ypos, int i) 1642{ 1643 struct screen *display = rb->screens[i]; 1644 struct lrc_word *lrc_word; 1645 struct lrc_brpos *lrc_brpos; 1646 long time_start, time_end, elapsed; 1647 int count, width, nword; 1648 int xpos; 1649 const char *str; 1650 bool active_line; 1651 1652 time_start = get_time_start(lrc_line); 1653 time_end = get_time_start(lrc_line->next); 1654 active_line = (time_start <= current.elapsed 1655 && time_end > current.elapsed); 1656 1657 if (!lrc_line->width) 1658 { 1659 /* empty line. draw bar during interval. */ 1660 long rin = current.elapsed - time_start; 1661 long len = time_end - time_start; 1662 if (current.wipe && active_line && len >= 3500) 1663 { 1664 elapsed = rin * vp_lyrics[i].width / len; 1665 set_to_inactive(display); 1666 display->fillrect(elapsed, ypos+font_ui_height/4+1, 1667 vp_lyrics[i].width-elapsed-1, font_ui_height/2-2); 1668 set_to_active(display); 1669 display->drawrect(0, ypos+font_ui_height/4, 1670 vp_lyrics[i].width, font_ui_height/2); 1671 display->fillrect(1, ypos+font_ui_height/4+1, 1672 elapsed-1, font_ui_height/2-2); 1673 set_to_default(display); 1674 } 1675 return ypos + font_ui_height; 1676 } 1677 1678 lrc_brpos = calc_brpos(lrc_line, i); 1679 /* initialize line */ 1680 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2; 1681 count = 0; 1682 width = 0; 1683 1684 active_line = active_line || !prefs.active_one_line; 1685 nword = lrc_line->nword-1; 1686 lrc_word = lrc_line->words + nword; 1687 str = lrc_word->word; 1688 /* only time_start of first word could be -1 */ 1689 if (lrc_word->time_start != -1) 1690 time_end = get_word_time_start(lrc_word); 1691 else 1692 time_end = time_start; 1693 do { 1694 time_start = time_end; 1695 if (nword > 0) 1696 time_end = get_word_time_start(lrc_word-1); 1697 else 1698 time_end = get_time_start(lrc_line->next); 1699 1700 if (time_start > current.elapsed || !active_line) 1701 { 1702 /* inactive */ 1703 elapsed = 0; 1704 } 1705 else if (!current.wipe || time_end <= current.elapsed) 1706 { 1707 /* active whole word */ 1708 elapsed = lrc_word->width; 1709 } 1710 else 1711 { 1712 /* wipe word */ 1713 long rin = current.elapsed - time_start; 1714 long len = time_end - time_start; 1715 elapsed = rin * lrc_word->width / len; 1716 } 1717 1718 int word_count = lrc_word->count; 1719 int word_width = lrc_word->width; 1720 set_to_active(display); 1721 while (word_count > 0 && word_width > 0) 1722 { 1723 int c = lrc_brpos->count - count; 1724 int w = lrc_brpos->width - width; 1725 if (c > word_count || w > word_width) 1726 { 1727 c = word_count; 1728 w = word_width; 1729 } 1730 if (elapsed <= 0) 1731 { 1732 set_to_inactive(display); 1733 } 1734 else if (elapsed < w) 1735 { 1736 /* wipe text */ 1737 display->fillrect(xpos, ypos, elapsed, font_ui_height); 1738 set_to_inactive(display); 1739 display->fillrect(xpos+elapsed, ypos, 1740 w-elapsed, font_ui_height); 1741#if (LCD_DEPTH > 1) 1742#ifdef HAVE_REMOTE_LCD 1743 if (display->screen_type == SCREEN_REMOTE) 1744 display->set_drawmode(DRMODE_INVERSEVID); 1745 else 1746#endif 1747 display->set_drawmode(DRMODE_BG); 1748#else 1749 display->set_drawmode(DRMODE_INVERSEVID); 1750#endif 1751 } 1752 rb->strlcpy(temp_buf, str, c+1); 1753 display->putsxy(xpos, ypos, temp_buf); 1754 str += c; 1755 xpos += w; 1756 count += c; 1757 width += w; 1758 word_count -= c; 1759 word_width -= w; 1760 elapsed -= w; 1761 if (count >= lrc_brpos->count || width >= lrc_brpos->width) 1762 { 1763 /* prepare for next line */ 1764 lrc_brpos++; 1765 str = lrc_skip_space(str); 1766 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2; 1767 ypos += font_ui_height; 1768 count = 0; 1769 width = 0; 1770 } 1771 } 1772 lrc_word--; 1773 } while (nword--); 1774 set_to_default(display); 1775 return ypos; 1776} 1777 1778static void display_lrcs(void) 1779{ 1780 long time_start, time_end, rin, len; 1781 int nline[NB_SCREENS] = {0}; 1782 struct lrc_line *lrc_center = current.ll_head; 1783 1784 if (!lrc_center) return; 1785 1786 while (get_time_start(lrc_center->next) <= current.elapsed) 1787 { 1788 nline[SCREEN_MAIN] += lrc_center->nline[SCREEN_MAIN]; 1789#ifdef HAVE_REMOTE_LCD 1790 nline[SCREEN_REMOTE] += lrc_center->nline[SCREEN_REMOTE]; 1791#endif 1792 lrc_center = lrc_center->next; 1793 } 1794 1795 time_start = get_time_start(lrc_center); 1796 time_end = get_time_start(lrc_center->next); 1797 rin = current.elapsed - time_start; 1798 len = time_end - time_start; 1799 1800 struct screen *display; 1801 FOR_NB_SCREENS(i) 1802 { 1803 display = rb->screens[i]; 1804 /* display current line at the center of the viewport */ 1805 display->set_viewport(&vp_lyrics[i]); 1806 display->clear_viewport(); 1807 1808 struct lrc_line *lrc_line; 1809 int y, ypos = 0, nblines = vp_lyrics[i].height/font_ui_height; 1810 y = (nblines-1)/2; 1811 if (rin < 0) 1812 { 1813 /* current.elapsed < time of first lrc */ 1814 if (!current.wipe) 1815 ypos = (time_start - current.elapsed) 1816 * font_ui_height / time_start; 1817 else 1818 y++; 1819 } 1820 else if (len > 0) 1821 { 1822 if (!current.wipe) 1823 ypos = - rin * lrc_center->nline[i] * font_ui_height / len; 1824 else 1825 { 1826 long elapsed = rin * lrc_center->width / len; 1827 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_center, i); 1828 while (elapsed > lrc_brpos->width) 1829 { 1830 elapsed -= lrc_brpos->width; 1831 y--; 1832 lrc_brpos++; 1833 } 1834 } 1835 } 1836 1837 /* find first line to display */ 1838 y -= nline[i]; 1839 lrc_line = current.ll_head; 1840 while (y < -lrc_line->nline[i]) 1841 { 1842 y += lrc_line->nline[i]; 1843 lrc_line = lrc_line->next; 1844 } 1845 1846 ypos += y*font_ui_height; 1847 while (lrc_line && ypos < vp_lyrics[i].height) 1848 { 1849 ypos = display_lrc_line(lrc_line, ypos, i); 1850 lrc_line = lrc_line->next; 1851 } 1852 if (!lrc_line && ypos < vp_lyrics[i].height) 1853 display->putsxy(0, ypos, "[end]"); 1854 1855 display->update_viewport(); 1856 display->set_viewport(NULL); 1857 } 1858} 1859 1860/******************************* 1861 * Browse lyrics and edit time. 1862 *******************************/ 1863/* point playing line in lyrics */ 1864static enum themable_icons get_icon(int selected, void * data) 1865{ 1866 (void) data; 1867 struct lrc_line *lrc_line = get_lrc_line(selected); 1868 if (lrc_line) 1869 { 1870 long time_start = get_time_start(lrc_line); 1871 long time_end = get_time_start(lrc_line->next); 1872 long elapsed = current.id3->elapsed; 1873 if (time_start <= elapsed && time_end > elapsed) 1874 return Icon_Moving; 1875 } 1876 return Icon_NOICON; 1877} 1878static const char *get_lrc_timeline(int selected, void *data, 1879 char *buffer, size_t buffer_len) 1880{ 1881 (void) data; 1882 struct lrc_line *lrc_line = get_lrc_line(selected); 1883 if (lrc_line) 1884 { 1885 format_time_tag(temp_buf, get_time_start(lrc_line)); 1886 rb->snprintf(buffer, buffer_len, "[%s]%s", 1887 temp_buf, get_lrc_str(lrc_line)); 1888 return buffer; 1889 } 1890 return NULL; 1891} 1892 1893static void save_changes(void) 1894{ 1895 char new_file[MAX_PATH], *p; 1896 bool success = false; 1897 int fd, fe; 1898 if (!current.changed_lrc) 1899 return; 1900 rb->splash(HZ/2, "Saving changes..."); 1901 if (current.type == TXT || current.type > NUM_TYPES) 1902 { 1903 /* save changes to new .lrc file */ 1904 rb->strcpy(new_file, current.lrc_file); 1905 p = rb->strrchr(new_file, '.'); 1906 rb->strcpy(p, extentions[LRC]); 1907 } 1908 else 1909 { 1910 /* file already exists. use temp file. */ 1911 rb->snprintf(new_file, MAX_PATH, "%s~", current.lrc_file); 1912 } 1913 fd = rb->creat(new_file, 0666); 1914 fe = rb->open(current.lrc_file, O_RDONLY); 1915 if (fd >= 0 && fe >= 0) 1916 { 1917 struct lrc_line *lrc_line, *temp_lrc; 1918 off_t curr = 0, next = 0, size = 0, offset = 0; 1919 for (lrc_line = current.ll_head; lrc_line; 1920 lrc_line = lrc_line->next) 1921 { 1922 /* apply offset and set old_time_start -1 to indicate 1923 that time tag is not saved yet. */ 1924 lrc_line->time_start = get_time_start(lrc_line); 1925 lrc_line->old_time_start = -1; 1926 } 1927 current.offset = 0; 1928 if (current.type > NUM_TYPES) 1929 { 1930 curr = -1; 1931 rb->write(fd, BOM, BOM_SIZE); 1932 } 1933 else 1934 size = rb->filesize(fe); 1935 while (curr < size) 1936 { 1937 /* find offset of next tag */ 1938 lrc_line = NULL; 1939 for (temp_lrc = current.ll_head, next = size; 1940 temp_lrc; temp_lrc = temp_lrc->next) 1941 { 1942 offset = temp_lrc->file_offset; 1943 if (offset < next && temp_lrc->old_time_start == -1) 1944 { 1945 lrc_line = temp_lrc; 1946 next = offset; 1947 if (offset <= curr) break; 1948 } 1949 } 1950 offset = current.offset_file_offset; 1951 if (offset >= 0 && offset < next) 1952 { 1953 lrc_line = NULL; 1954 next = offset; 1955 current.offset_file_offset = -1; 1956 } 1957 if (next > curr) 1958 { 1959 if (curr == -1) curr = 0; 1960 /* copy before the next tag */ 1961 while (curr < next) 1962 { 1963 ssize_t count = next-curr; 1964 if (count > MAX_LINE_LEN) 1965 count = MAX_LINE_LEN; 1966 if (rb->read(fe, temp_buf, count)!=count) 1967 break; 1968 rb->write(fd, temp_buf, count); 1969 curr += count; 1970 } 1971 if (curr < next || curr >= size) break; 1972 } 1973 /* write tag to new file and skip tag in backup */ 1974 if (lrc_line != NULL) 1975 { 1976 lrc_line->file_offset = rb->lseek(fd, 0, SEEK_CUR); 1977 lrc_line->old_time_start = lrc_line->time_start; 1978 long t = lrc_line->time_start; 1979 if (current.type == SNC) 1980 { 1981 rb->fdprintf(fd, "%02ld%02ld%02ld%02ld", (t/3600000)%100, 1982 (t/60000)%60, (t/1000)%60, (t/10)%100); 1983 /* skip time tag */ 1984 curr += rb->read(fe, temp_buf, 8); 1985 } 1986 else /* LRC || LRC8 */ 1987 { 1988 format_time_tag(temp_buf, t); 1989 rb->fdprintf(fd, "[%s]", temp_buf); 1990 } 1991 if (next == -1) 1992 { 1993 rb->fdprintf(fd, "%s\n", get_lrc_str(lrc_line)); 1994 } 1995 } 1996 if (current.type == LRC || current.type == LRC8) 1997 { 1998 /* skip both time tag and offset tag */ 1999 while (curr++<size && rb->read(fe, temp_buf, 1)==1) 2000 if (temp_buf[0]==']') break; 2001 } 2002 } 2003 success = (curr>=size); 2004 } 2005 if (fe >= 0) rb->close(fe); 2006 if (fd >= 0) rb->close(fd); 2007 2008 if (success) 2009 { 2010 if (current.type == TXT || current.type > NUM_TYPES) 2011 { 2012 current.type = LRC; 2013 rb->strcpy(current.lrc_file, new_file); 2014 } 2015 else 2016 { 2017 rb->remove(current.lrc_file); 2018 rb->rename(new_file, current.lrc_file); 2019 } 2020 } 2021 else 2022 { 2023 rb->remove(new_file); 2024 rb->splash(HZ, "Could not save changes."); 2025 } 2026 rb->reload_directory(); 2027 current.changed_lrc = false; 2028} 2029static int timetag_editor(void) 2030{ 2031 struct gui_synclist gui_editor; 2032 struct lrc_line *lrc_line; 2033 bool exit = false; 2034 int button, idx, selected = 0; 2035 2036 if (current.id3 == NULL || !current.ll_head) 2037 { 2038 rb->splash(HZ, "No lyrics"); 2039 return LRC_GOTO_MAIN; 2040 } 2041 2042 get_lrc_line(-1); /* initialize static variables */ 2043 2044 for (lrc_line = current.ll_head, idx = 0; 2045 lrc_line; lrc_line = lrc_line->next, idx++) 2046 { 2047 long time_start = get_time_start(lrc_line); 2048 long time_end = get_time_start(lrc_line->next); 2049 long elapsed = current.id3->elapsed; 2050 if (time_start <= elapsed && time_end > elapsed) 2051 selected = idx; 2052 } 2053 2054 rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL); 2055 rb->gui_synclist_set_nb_items(&gui_editor, current.nlrcline); 2056 rb->gui_synclist_set_icon_callback(&gui_editor, get_icon); 2057 rb->gui_synclist_set_title(&gui_editor, "Timetag Editor", 2058 Icon_Menu_functioncall); 2059 rb->gui_synclist_select_item(&gui_editor, selected); 2060 rb->gui_synclist_draw(&gui_editor); 2061 2062 while (!exit) 2063 { 2064 button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK); 2065 if (rb->gui_synclist_do_button(&gui_editor, &button)) 2066 continue; 2067 2068 switch (button) 2069 { 2070 case ACTION_STD_OK: 2071 idx = rb->gui_synclist_get_sel_pos(&gui_editor); 2072 lrc_line = get_lrc_line(idx); 2073 if (lrc_line) 2074 { 2075 set_time_start(lrc_line, current.id3->elapsed-500); 2076 rb->gui_synclist_draw(&gui_editor); 2077 } 2078 break; 2079 case ACTION_STD_CONTEXT: 2080 idx = rb->gui_synclist_get_sel_pos(&gui_editor); 2081 lrc_line = get_lrc_line(idx); 2082 if (lrc_line) 2083 { 2084 long temp_time = get_time_start(lrc_line); 2085 if (lrc_set_time(get_lrc_str(lrc_line), NULL, 2086 &temp_time, 10, 0, current.length, 2087 LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN) == 1) 2088 return PLUGIN_USB_CONNECTED; 2089 set_time_start(lrc_line, temp_time); 2090 rb->gui_synclist_draw(&gui_editor); 2091 } 2092 break; 2093 case ACTION_TREE_STOP: 2094 case ACTION_STD_CANCEL: 2095 exit = true; 2096 break; 2097 default: 2098 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 2099 return PLUGIN_USB_CONNECTED; 2100 break; 2101 } 2102 } 2103 2104 FOR_NB_SCREENS(idx) 2105 rb->screens[idx]->scroll_stop(); 2106 2107 if (current.changed_lrc) 2108 { 2109 MENUITEM_STRINGLIST(save_menu, "Save Changes?", NULL, 2110 "Yes", "No (save later)", "Discard All Changes") 2111 button = 0; 2112 exit = false; 2113 while (!exit) 2114 { 2115 switch (rb->do_menu(&save_menu, &button, NULL, false)) 2116 { 2117 case 0: 2118 sort_lrcs(); 2119 save_changes(); 2120 exit = true; 2121 break; 2122 case 1: 2123 sort_lrcs(); 2124 exit = true; 2125 break; 2126 case 2: 2127 init_time_tag(); 2128 exit = true; 2129 break; 2130 case MENU_ATTACHED_USB: 2131 return PLUGIN_USB_CONNECTED; 2132 } 2133 } 2134 } 2135 return LRC_GOTO_MAIN; 2136} 2137 2138/******************************* 2139 * Settings. 2140 *******************************/ 2141static void load_or_save_settings(bool save) 2142{ 2143 static const char config_file[] = "lrcplayer.cfg"; 2144 static struct configdata config[] = { 2145#ifdef HAVE_LCD_COLOR 2146 { TYPE_INT, 0, 0xffffff, { .int_p = &prefs.inactive_color }, 2147 "inactive color", NULL }, 2148#endif 2149 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wrap }, "wrap", NULL }, 2150 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wipe }, "wipe", NULL }, 2151 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.active_one_line }, 2152 "active one line", NULL }, 2153 { TYPE_INT, 0, 2, { .int_p = &prefs.align }, "align", NULL }, 2154 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.statusbar_on }, 2155 "statusbar on", NULL }, 2156 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_title }, 2157 "display title", NULL }, 2158 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_time }, 2159 "display time", NULL }, 2160 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.backlight_on }, 2161 "backlight on", NULL }, 2162 2163 { TYPE_STRING, 0, sizeof(prefs.lrc_directory), 2164 { .string = prefs.lrc_directory }, "lrc directory", NULL }, 2165 { TYPE_INT, -1, NUM_CODEPAGES-1, { .int_p = &prefs.encoding }, 2166 "encoding", NULL }, 2167#ifdef LRC_SUPPORT_ID3 2168 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.read_id3 }, "read id3", NULL }, 2169#endif 2170 }; 2171 2172 if (!save) 2173 { 2174 /* initialize setting */ 2175#if LCD_DEPTH > 1 2176 prefs.active_color = rb->lcd_get_foreground(); 2177 prefs.inactive_color = LCD_LIGHTGRAY; 2178#endif 2179 prefs.wrap = true; 2180 prefs.wipe = true; 2181 prefs.active_one_line = false; 2182 prefs.align = 1; /* center */ 2183 prefs.statusbar_on = false; 2184 prefs.display_title = true; 2185 prefs.display_time = true; 2186 prefs.backlight_on = false; 2187#ifdef LRC_SUPPORT_ID3 2188 prefs.read_id3 = true; 2189#endif 2190 rb->strcpy(prefs.lrc_directory, "/Lyrics"); 2191 prefs.encoding = -1; /* default codepage */ 2192 2193 configfile_load(config_file, config, ARRAYLEN(config), 0); 2194 } 2195 else if (rb->memcmp(&old_prefs, &prefs, sizeof(prefs))) 2196 { 2197 rb->splash(0, "Saving Settings"); 2198 configfile_save(config_file, config, ARRAYLEN(config), 0); 2199 } 2200 rb->memcpy(&old_prefs, &prefs, sizeof(prefs)); 2201} 2202 2203static bool lrc_theme_menu(void) 2204{ 2205 enum { 2206 LRC_MENU_STATUSBAR, 2207 LRC_MENU_DISP_TITLE, 2208 LRC_MENU_DISP_TIME, 2209#ifdef HAVE_LCD_COLOR 2210 LRC_MENU_INACTIVE_COLOR, 2211#endif 2212 LRC_MENU_BACKLIGHT, 2213 }; 2214 2215 int selected = 0; 2216 bool exit = false, usb = false; 2217 2218 MENUITEM_STRINGLIST(menu, "Theme Settings", NULL, 2219 "Show Statusbar", "Display Title", 2220 "Display Time", 2221#ifdef HAVE_LCD_COLOR 2222 "Inactive Colour", 2223#endif 2224 "Backlight Always On"); 2225 2226 while (!exit && !usb) 2227 { 2228 switch (rb->do_menu(&menu, &selected, NULL, false)) 2229 { 2230 case LRC_MENU_STATUSBAR: 2231 usb = rb->set_bool("Show Statusbar", &prefs.statusbar_on); 2232 break; 2233 case LRC_MENU_DISP_TITLE: 2234 usb = rb->set_bool("Display Title", &prefs.display_title); 2235 break; 2236 case LRC_MENU_DISP_TIME: 2237 usb = rb->set_bool("Display Time", &prefs.display_time); 2238 break; 2239#ifdef HAVE_LCD_COLOR 2240 case LRC_MENU_INACTIVE_COLOR: 2241 usb = rb->set_color(NULL, "Inactive Colour", 2242 &prefs.inactive_color, -1); 2243 break; 2244#endif 2245 case LRC_MENU_BACKLIGHT: 2246 usb = rb->set_bool("Backlight Always On", &prefs.backlight_on); 2247 break; 2248 case MENU_ATTACHED_USB: 2249 usb = true; 2250 break; 2251 default: 2252 exit = true; 2253 break; 2254 } 2255 } 2256 2257 return usb; 2258} 2259 2260static bool lrc_display_menu(void) 2261{ 2262 enum { 2263 LRC_MENU_WRAP, 2264 LRC_MENU_WIPE, 2265 LRC_MENU_ALIGN, 2266 LRC_MENU_LINE_MODE, 2267 }; 2268 2269 int selected = 0; 2270 bool exit = false, usb = false; 2271 2272 MENUITEM_STRINGLIST(menu, "Display Settings", NULL, 2273 "Wrap", "Wipe", "Alignment", 2274 "Activate Only Current Line"); 2275 2276 struct opt_items align_names[] = { 2277 {"Left", -1}, {"Centre", -1}, {"Right", -1}, 2278 }; 2279 2280 while (!exit && !usb) 2281 { 2282 switch (rb->do_menu(&menu, &selected, NULL, false)) 2283 { 2284 case LRC_MENU_WRAP: 2285 usb = rb->set_bool("Wrap", &prefs.wrap); 2286 break; 2287 case LRC_MENU_WIPE: 2288 usb = rb->set_bool("Wipe", &prefs.wipe); 2289 break; 2290 case LRC_MENU_ALIGN: 2291 usb = rb->set_option("Alignment", &prefs.align, RB_INT, 2292 align_names, 3, NULL); 2293 break; 2294 case LRC_MENU_LINE_MODE: 2295 usb = rb->set_bool("Activate Only Current Line", 2296 &prefs.active_one_line); 2297 break; 2298 case MENU_ATTACHED_USB: 2299 usb = true; 2300 break; 2301 default: 2302 exit = true; 2303 break; 2304 } 2305 } 2306 2307 return usb; 2308} 2309 2310static bool lrc_lyrics_menu(void) 2311{ 2312 enum { 2313 LRC_MENU_ENCODING, 2314#ifdef LRC_SUPPORT_ID3 2315 LRC_MENU_READ_ID3, 2316#endif 2317 LRC_MENU_LRC_DIR, 2318 }; 2319 2320 int selected = 0; 2321 bool exit = false, usb = false; 2322 2323 struct opt_items cp_names[NUM_CODEPAGES+1]; 2324 int old_val; 2325 2326 MENUITEM_STRINGLIST(menu, "Lyrics Settings", NULL, 2327 "Encoding", 2328#ifdef LRC_SUPPORT_ID3 2329 "Read ID3 tag", 2330#endif 2331 "Lrc Directry"); 2332 2333 cp_names[0].string = "Use default codepage"; 2334 cp_names[0].voice_id = -1; 2335 for (old_val = 1; old_val < NUM_CODEPAGES+1; old_val++) 2336 { 2337 cp_names[old_val].string = rb->get_codepage_name(old_val-1); 2338 cp_names[old_val].voice_id = -1; 2339 } 2340 2341 while (!exit && !usb) 2342 { 2343 switch (rb->do_menu(&menu, &selected, NULL, false)) 2344 { 2345 case LRC_MENU_ENCODING: 2346 prefs.encoding++; 2347 old_val = prefs.encoding; 2348 usb = rb->set_option("Encoding", &prefs.encoding, RB_INT, 2349 cp_names, NUM_CODEPAGES+1, NULL); 2350 if (prefs.encoding != old_val) 2351 { 2352 save_changes(); 2353 if (current.type < NUM_TYPES) 2354 { 2355 /* let reload lrc file to apply encoding setting */ 2356 reset_current_data(); 2357 } 2358 } 2359 prefs.encoding--; 2360 break; 2361#ifdef LRC_SUPPORT_ID3 2362 case LRC_MENU_READ_ID3: 2363 usb = rb->set_bool("Read ID3 tag", &prefs.read_id3); 2364 break; 2365#endif 2366 case LRC_MENU_LRC_DIR: 2367 rb->strcpy(temp_buf, prefs.lrc_directory); 2368 if (!rb->kbd_input(temp_buf, sizeof(prefs.lrc_directory), NULL)) 2369 rb->strcpy(prefs.lrc_directory, temp_buf); 2370 break; 2371 case MENU_ATTACHED_USB: 2372 usb = true; 2373 break; 2374 default: 2375 exit = true; 2376 break; 2377 } 2378 } 2379 2380 return usb; 2381} 2382 2383#ifdef LRC_DEBUG 2384static const char* lrc_debug_data(int selected, void * data, 2385 char * buffer, size_t buffer_len) 2386{ 2387 (void)data; 2388 switch (selected) 2389 { 2390 case 0: 2391 rb->strlcpy(buffer, current.mp3_file, buffer_len); 2392 break; 2393 case 1: 2394 rb->strlcpy(buffer, current.lrc_file, buffer_len); 2395 break; 2396 case 2: 2397 rb->snprintf(buffer, buffer_len, "buf usage: %d,%d/%d", 2398 (int)lrc_buffer_used, (int)lrc_buffer_end, 2399 (int)lrc_buffer_size); 2400 break; 2401 case 3: 2402 rb->snprintf(buffer, buffer_len, "line count: %d,%d", 2403 current.nlrcline, current.nlrcbrpos); 2404 break; 2405 case 4: 2406 rb->snprintf(buffer, buffer_len, "loaded lrc? %s", 2407 current.loaded_lrc?"yes":"no"); 2408 break; 2409 case 5: 2410 rb->snprintf(buffer, buffer_len, "too many lines? %s", 2411 current.too_many_lines?"yes":"no"); 2412 break; 2413 default: 2414 return NULL; 2415 } 2416 return buffer; 2417} 2418 2419static bool lrc_debug_menu(void) 2420{ 2421 struct simplelist_info info; 2422 rb->simplelist_info_init(&info, "Debug Menu", 6, NULL); 2423 info.scroll_all = true; 2424 info.get_name = lrc_debug_data; 2425 return rb->simplelist_show_list(&info); 2426} 2427#endif 2428 2429/* returns one of enum lrc_screen or enum plugin_status */ 2430static int lrc_menu(void) 2431{ 2432 enum { 2433 LRC_MENU_THEME, 2434 LRC_MENU_DISPLAY, 2435 LRC_MENU_LYRICS, 2436 LRC_MENU_PLAYBACK, 2437#ifdef LRC_DEBUG 2438 LRC_MENU_DEBUG, 2439#endif 2440 LRC_MENU_OFFSET, 2441 LRC_MENU_TIMETAG_EDITOR, 2442 LRC_MENU_QUIT, 2443 }; 2444 2445 MENUITEM_STRINGLIST(menu, "Lrcplayer Menu", NULL, 2446 "Theme Settings", 2447 "Display Settings", 2448 "Lyrics Settings", 2449 "Playback Control", 2450#ifdef LRC_DEBUG 2451 "Debug Menu", 2452#endif 2453 "Time Offset", "Timetag Editor", 2454 "Quit"); 2455 int selected = 0, ret = LRC_GOTO_MENU; 2456 bool usb = false; 2457 2458 while (ret == LRC_GOTO_MENU) 2459 { 2460 switch (rb->do_menu(&menu, &selected, NULL, false)) 2461 { 2462 case LRC_MENU_THEME: 2463 usb = lrc_theme_menu(); 2464 break; 2465 case LRC_MENU_DISPLAY: 2466 usb = lrc_display_menu(); 2467 break; 2468 case LRC_MENU_LYRICS: 2469 usb = lrc_lyrics_menu(); 2470 break; 2471 case LRC_MENU_PLAYBACK: 2472 usb = playback_control(NULL); 2473 ret = LRC_GOTO_MAIN; 2474 break; 2475#ifdef LRC_DEBUG 2476 case LRC_MENU_DEBUG: 2477 usb = lrc_debug_menu(); 2478 ret = LRC_GOTO_MAIN; 2479 break; 2480#endif 2481 case LRC_MENU_OFFSET: 2482 usb = (lrc_set_time("Time Offset", "sec", &current.offset, 2483 10, -60*1000, 60*1000, 2484 LST_SET_MSEC|LST_SET_SEC) == 1); 2485 ret = LRC_GOTO_MAIN; 2486 break; 2487 case LRC_MENU_TIMETAG_EDITOR: 2488 ret = LRC_GOTO_EDITOR; 2489 break; 2490 case LRC_MENU_QUIT: 2491 ret = PLUGIN_OK; 2492 break; 2493 case MENU_ATTACHED_USB: 2494 usb = true; 2495 break; 2496 default: 2497 ret = LRC_GOTO_MAIN; 2498 break; 2499 } 2500 if (usb) 2501 ret = PLUGIN_USB_CONNECTED; 2502 } 2503 return ret; 2504} 2505 2506/******************************* 2507 * Main. 2508 *******************************/ 2509/* returns true if song has changed to know when to load new lyrics. */ 2510static bool check_audio_status(void) 2511{ 2512 static int last_audio_status = 0; 2513 if (current.ff_rewind == -1) 2514 current.audio_status = rb->audio_status(); 2515 current.id3 = rb->audio_current_track(); 2516 if ((last_audio_status^current.audio_status)&AUDIO_STATUS_PLAY) 2517 { 2518 last_audio_status = current.audio_status; 2519 return true; 2520 } 2521 if (AUDIO_STOP || current.id3 == NULL) 2522 return false; 2523 if (rb->strcmp(current.mp3_file, current.id3->path)) 2524 { 2525 return true; 2526 } 2527 return false; 2528} 2529static void ff_rewind(long time, bool resume) 2530{ 2531 if (AUDIO_PLAY) 2532 { 2533 if (!AUDIO_PAUSE) 2534 { 2535 resume = true; 2536 rb->audio_pause(); 2537 } 2538 rb->audio_ff_rewind(time); 2539 rb->sleep(HZ/10); /* take affect seeking */ 2540 if (resume) 2541 rb->audio_resume(); 2542 } 2543} 2544 2545static int handle_button(void) 2546{ 2547 int ret = LRC_GOTO_MAIN; 2548 static int step = 0; 2549 int limit, button = rb->get_action(CONTEXT_WPS, HZ/10); 2550 switch (button) 2551 { 2552 case ACTION_WPS_BROWSE: 2553 case ACTION_WPS_STOP: 2554 save_changes(); 2555 ret = PLUGIN_OK; 2556 break; 2557 case ACTION_WPS_PLAY: 2558 if (AUDIO_STOP && rb->global_status->resume_index != -1) 2559 { 2560 if (rb->playlist_resume() != -1) 2561 { 2562 rb->playlist_resume_track(rb->global_status->resume_index, 2563 rb->global_status->resume_crc32, 2564 rb->global_status->resume_elapsed, 2565 rb->global_status->resume_offset); 2566 } 2567 } 2568 else if (AUDIO_PAUSE) 2569 rb->audio_resume(); 2570 else 2571 rb->audio_pause(); 2572 break; 2573 case ACTION_WPS_SEEKFWD: 2574 case ACTION_WPS_SEEKBACK: 2575 if (AUDIO_STOP) 2576 break; 2577 if (current.ff_rewind > -1) 2578 { 2579 if (button == ACTION_WPS_SEEKFWD) 2580 /* fast forwarding, calc max step relative to end */ 2581 limit = (current.length - current.ff_rewind) * 3 / 100; 2582 else 2583 /* rewinding, calc max step relative to start */ 2584 limit = (current.ff_rewind) * 3 / 100; 2585 limit = MAX(limit, 500); 2586 2587 if (step > limit) 2588 step = limit; 2589 2590 if (button == ACTION_WPS_SEEKFWD) 2591 current.ff_rewind += step; 2592 else 2593 current.ff_rewind -= step; 2594 2595 if (current.ff_rewind > current.length-100) 2596 current.ff_rewind = current.length-100; 2597 if (current.ff_rewind < 0) 2598 current.ff_rewind = 0; 2599 2600 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */ 2601 step += step >> (rb->global_settings->ff_rewind_accel + 3); 2602 } 2603 else 2604 { 2605 current.ff_rewind = current.elapsed; 2606 if (!AUDIO_PAUSE) 2607 rb->audio_pause(); 2608 step = 1000 * rb->global_settings->ff_rewind_min_step; 2609 } 2610 break; 2611 case ACTION_WPS_STOPSEEK: 2612 if (current.ff_rewind == -1) 2613 break; 2614 ff_rewind(current.ff_rewind, !AUDIO_PAUSE); 2615 current.elapsed = current.ff_rewind; 2616 current.ff_rewind = -1; 2617 break; 2618 case ACTION_WPS_SKIPNEXT: 2619 rb->audio_next(); 2620 break; 2621 case ACTION_WPS_SKIPPREV: 2622 if (current.elapsed < 3000) 2623 rb->audio_prev(); 2624 else 2625 ff_rewind(0, false); 2626 break; 2627 case ACTION_WPS_VOLDOWN: 2628 rb->adjust_volume(-1); 2629 break; 2630 case ACTION_WPS_VOLUP: 2631 rb->adjust_volume(1); 2632 break; 2633 case ACTION_WPS_CONTEXT: 2634 ret = LRC_GOTO_EDITOR; 2635 break; 2636 case ACTION_WPS_MENU: 2637 ret = LRC_GOTO_MENU; 2638 break; 2639 default: 2640 if(rb->default_event_handler(button) == SYS_USB_CONNECTED) 2641 ret = PLUGIN_USB_CONNECTED; 2642 break; 2643 } 2644 return ret; 2645} 2646 2647static int lrc_main(void) 2648{ 2649 int ret = LRC_GOTO_MAIN; 2650 long id3_timeout = 0; 2651 bool update_display_state = true; 2652 2653 /* y offset of vp_lyrics */ 2654 int h = (prefs.display_title?font_ui_height:0)+ 2655 (prefs.display_time?SYSFONT_HEIGHT*2:0); 2656 2657 2658 FOR_NB_SCREENS(i) 2659 { 2660 rb->viewportmanager_theme_enable(i, prefs.statusbar_on, &vp_info[i]); 2661 vp_lyrics[i] = vp_info[i]; 2662 vp_lyrics[i].flags &= ~VP_FLAG_ALIGNMENT_MASK; 2663 vp_lyrics[i].y += h; 2664 vp_lyrics[i].height -= h; 2665 } 2666 2667#ifdef HAVE_BACKLIGHT 2668 if (prefs.backlight_on) 2669 backlight_ignore_timeout(); 2670#endif 2671 2672 /* in case settings that may affect break position 2673 * are changed (statusbar_on and wrap). */ 2674 if (!current.too_many_lines) 2675 calc_brpos(NULL, 0); 2676 2677 while (ret == LRC_GOTO_MAIN) 2678 { 2679 if (check_audio_status()) 2680 { 2681 update_display_state = true; 2682 if (AUDIO_STOP) 2683 { 2684 current.id3 = NULL; 2685 id3_timeout = 0; 2686 } 2687 else if (rb->strcmp(current.mp3_file, current.id3->path)) 2688 { 2689 save_changes(); 2690 reset_current_data(); 2691 rb->strcpy(current.mp3_file, current.id3->path); 2692 id3_timeout = *rb->current_tick+HZ*3; 2693 current.found_lrc = false; 2694 } 2695 } 2696 if (current.id3 && current.id3->length) 2697 { 2698 if (current.ff_rewind == -1) 2699 { 2700 long di = current.id3->elapsed - current.elapsed; 2701 if (di < -250 || di > 0) 2702 current.elapsed = current.id3->elapsed; 2703 } 2704 else 2705 current.elapsed = current.ff_rewind; 2706 current.length = current.id3->length; 2707 if (current.elapsed > current.length) 2708 current.elapsed = current.length; 2709 } 2710 else 2711 { 2712 current.elapsed = 0; 2713 current.length = 1; 2714 } 2715 2716 if (current.id3 && id3_timeout && 2717 (TIME_AFTER(*rb->current_tick, id3_timeout) || 2718 current.id3->artist)) 2719 { 2720 update_display_state = true; 2721 id3_timeout = 0; 2722 2723 current.found_lrc = find_lrc_file(); 2724#ifdef LRC_SUPPORT_ID3 2725 if (!current.found_lrc && prefs.read_id3) 2726 { 2727 /* no lyrics file found. try to read from id3 tag. */ 2728 current.found_lrc = read_id3(); 2729 } 2730#endif 2731 } 2732 else if (current.found_lrc && !current.loaded_lrc) 2733 { 2734 /* current.loaded_lrc is false after changing encode setting */ 2735 update_display_state = true; 2736 display_state(); 2737 load_lrc_file(); 2738 } 2739 if (update_display_state) 2740 { 2741 if (current.type == TXT || current.type == ID3_USLT) 2742 current.wipe = false; 2743 else 2744 current.wipe = prefs.wipe; 2745 display_state(); 2746 update_display_state = false; 2747 } 2748 if (AUDIO_PLAY) 2749 { 2750 if (prefs.display_time) 2751 display_time(); 2752 if (!id3_timeout) 2753 display_lrcs(); 2754 } 2755 2756 ret = handle_button(); 2757 } 2758 2759 FOR_NB_SCREENS(i) 2760 rb->viewportmanager_theme_undo(i, false); 2761 2762#ifdef HAVE_BACKLIGHT 2763 if (prefs.backlight_on) 2764 backlight_use_settings(); 2765#endif 2766 2767 return ret; 2768} 2769 2770/* this is the plugin entry point */ 2771enum plugin_status plugin_start(const void* parameter) 2772{ 2773 int ret = LRC_GOTO_MAIN; 2774 2775 /* initialize settings. */ 2776 load_or_save_settings(false); 2777 2778 uifont = rb->screens[0]->getuifont(); 2779 font_ui_height = rb->font_get(uifont)->height; 2780 2781 lrc_buffer = rb->plugin_get_buffer(&lrc_buffer_size); 2782 lrc_buffer = ALIGN_UP(lrc_buffer, 4); /* 4 bytes aligned */ 2783 lrc_buffer_size = (lrc_buffer_size - 4)&~3; 2784 2785 reset_current_data(); 2786 current.id3 = NULL; 2787 current.mp3_file[0] = 0; 2788 current.lrc_file[0] = 0; 2789 current.ff_rewind = -1; 2790 current.found_lrc = false; 2791 if (parameter && check_audio_status()) 2792 { 2793 const char *ext; 2794 rb->strcpy(current.mp3_file, current.id3->path); 2795 /* use passed parameter as lrc file. */ 2796 rb->strcpy(current.lrc_file, parameter); 2797 if (!rb->file_exists(current.lrc_file)) 2798 { 2799 rb->splash(HZ, "Specified file dose not exist."); 2800 return PLUGIN_ERROR; 2801 } 2802 ext = rb->strrchr(current.lrc_file, '.'); 2803 if (!ext) ext = current.lrc_file; 2804 for (current.type = 0; current.type < NUM_TYPES; current.type++) 2805 { 2806 if (!rb->strcasecmp(ext, extentions[current.type])) 2807 break; 2808 } 2809 if (current.type == NUM_TYPES) 2810 { 2811 rb->splashf(HZ, "%s is not supported", ext); 2812 return PLUGIN_ERROR; 2813 } 2814 current.found_lrc = true; 2815 } 2816 2817 while (ret >= PLUGIN_OTHER) 2818 { 2819 switch (ret) 2820 { 2821 case LRC_GOTO_MAIN: 2822 ret = lrc_main(); 2823 break; 2824 case LRC_GOTO_MENU: 2825 ret = lrc_menu(); 2826 break; 2827 case LRC_GOTO_EDITOR: 2828 ret = timetag_editor(); 2829 break; 2830 default: 2831 ret = PLUGIN_ERROR; 2832 break; 2833 } 2834 } 2835 2836 load_or_save_settings(true); 2837 return ret; 2838}