A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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 = ¤t.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 = ¤t.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: ¤t.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(¤t.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", ¤t.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}