1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
43 #define CUE_DIR ROCKBOX_DIR "/cue"
45 bool look_for_cuesheet_file(struct mp3entry
*track_id3
, struct cuesheet_file
*cue_file
)
47 /* DEBUGF("look for cue file\n"); */
49 char cuepath
[MAX_PATH
];
52 if (track_id3
->has_embedded_cuesheet
)
54 cue_file
->pos
= track_id3
->embedded_cuesheet
.pos
;
55 cue_file
->size
= track_id3
->embedded_cuesheet
.size
;
56 cue_file
->encoding
= track_id3
->embedded_cuesheet
.encoding
;
57 strlcpy(cue_file
->path
, track_id3
->path
, MAX_PATH
);
63 cue_file
->path
[0] = '\0';
64 slash
= strrchr(track_id3
->path
, '/');
67 strlcpy(cuepath
, track_id3
->path
, MAX_PATH
);
68 dot
= strrchr(cuepath
, '.');
71 if (!file_exists(cuepath
))
73 strcpy(cuepath
, CUE_DIR
);
74 strcat(cuepath
, slash
);
75 char *dot
= strrchr(cuepath
, '.');
77 if (!file_exists(cuepath
))
81 strlcpy(cue_file
->path
, cuepath
, MAX_PATH
);
85 static char *get_string(const char *line
)
89 start
= strchr(line
, '"');
92 start
= strchr(line
, ' ');
98 end
= strchr(++start
, '"');
105 /* parse cuesheet "cue_file" and store the information in "cue" */
106 bool parse_cuesheet(struct cuesheet_file
*cue_file
, struct cuesheet
*cue
)
110 unsigned char char_enc
= CHAR_ENC_ISO_8859_1
;
111 bool is_embedded
= false;
114 int read_bytes
= MAX_PATH
;
115 unsigned char utf16_buf
[MAX_PATH
];
117 int fd
= open(cue_file
->path
, O_RDONLY
, 0644);
120 if (cue_file
->pos
> 0)
123 lseek(fd
, cue_file
->pos
, SEEK_SET
);
124 bytes_left
= cue_file
->size
;
125 char_enc
= cue_file
->encoding
;
128 /* Look for a Unicode BOM */
129 unsigned char bom_read
= 0;
130 read(fd
, line
, BOM_UTF_8_SIZE
);
131 if(!memcmp(line
, BOM_UTF_8
, BOM_UTF_8_SIZE
))
133 char_enc
= CHAR_ENC_UTF_8
;
134 bom_read
= BOM_UTF_8_SIZE
;
136 else if(!memcmp(line
, BOM_UTF_16_LE
, BOM_UTF_16_SIZE
))
138 char_enc
= CHAR_ENC_UTF_16_LE
;
139 bom_read
= BOM_UTF_16_SIZE
;
141 else if(!memcmp(line
, BOM_UTF_16_BE
, BOM_UTF_16_SIZE
))
143 char_enc
= CHAR_ENC_UTF_16_BE
;
144 bom_read
= BOM_UTF_16_SIZE
;
146 if (bom_read
< BOM_UTF_8_SIZE
)
147 lseek(fd
, cue_file
->pos
+ bom_read
, SEEK_SET
);
151 bytes_left
-= bom_read
;
152 if (read_bytes
> bytes_left
)
153 read_bytes
= bytes_left
;
157 memset(cue
, 0, sizeof(struct cuesheet
));
158 strcpy(cue
->path
, cue_file
->path
);
159 cue
->curr_track
= cue
->tracks
;
161 while ((line_len
= read_line(fd
, line
, read_bytes
)) > 0
162 && cue
->track_count
< MAX_TRACKS
)
164 if (char_enc
== CHAR_ENC_UTF_16_LE
)
166 s
= utf16LEdecode(line
, utf16_buf
, line_len
);
167 /* terminate the string at the newline */
169 strcpy(line
, utf16_buf
);
170 /* chomp the trailing 0 after the newline */
171 lseek(fd
, 1, SEEK_CUR
);
174 else if (char_enc
== CHAR_ENC_UTF_16_BE
)
176 s
= utf16BEdecode(line
, utf16_buf
, line_len
);
178 strcpy(line
, utf16_buf
);
180 s
= skip_whitespace(line
);
182 if (!strncmp(s
, "TRACK", 5))
186 else if (!strncmp(s
, "INDEX 01", 8))
189 s
= skip_whitespace(s
);
191 s
= skip_whitespace(s
);
192 cue
->tracks
[cue
->track_count
-1].offset
= 60*1000 * atoi(s
);
193 s
= strchr(s
,':') + 1;
194 cue
->tracks
[cue
->track_count
-1].offset
+= 1000 * atoi(s
);
195 s
= strchr(s
,':') + 1;
196 cue
->tracks
[cue
->track_count
-1].offset
+= 13 * atoi(s
);
198 else if (!strncmp(s
, "TITLE", 5)
199 || !strncmp(s
, "PERFORMER", 9)
200 || !strncmp(s
, "SONGWRITER", 10))
203 char *string
= get_string(s
);
209 case 'T': /* TITLE */
210 dest
= (cue
->track_count
<= 0) ? cue
->title
:
211 cue
->tracks
[cue
->track_count
-1].title
;
214 case 'P': /* PERFORMER */
215 dest
= (cue
->track_count
<= 0) ? cue
->performer
:
216 cue
->tracks
[cue
->track_count
-1].performer
;
219 case 'S': /* SONGWRITER */
220 dest
= (cue
->track_count
<= 0) ? cue
->songwriter
:
221 cue
->tracks
[cue
->track_count
-1].songwriter
;
227 if (char_enc
== CHAR_ENC_ISO_8859_1
)
229 dest
= iso_decode(string
, dest
, -1,
230 MIN(strlen(string
), MAX_NAME
));
235 strlcpy(dest
, string
, MAX_NAME
*3 + 1);
241 bytes_left
-= line_len
;
244 if (bytes_left
< read_bytes
)
245 read_bytes
= bytes_left
;
250 /* If some songs don't have performer info, we copy the cuesheet performer */
252 for (i
= 0; i
< cue
->track_count
; i
++)
254 if (*(cue
->tracks
[i
].performer
) == '\0')
255 strlcpy(cue
->tracks
[i
].performer
, cue
->performer
, MAX_NAME
*3);
257 if (*(cue
->tracks
[i
].songwriter
) == '\0')
258 strlcpy(cue
->tracks
[i
].songwriter
, cue
->songwriter
, MAX_NAME
*3);
264 /* takes care of seeking to a track in a playlist
265 * returns false if audio isn't playing */
266 static bool seek(unsigned long pos
)
268 if (!(audio_status() & AUDIO_STATUS_PLAY
))
274 #if (CONFIG_CODEC == SWCODEC)
275 audio_pre_ff_rewind();
276 audio_ff_rewind(pos
);
279 audio_ff_rewind(pos
);
286 /* returns the index of the track currently being played
287 and updates the information about the current track. */
288 int cue_find_current_track(struct cuesheet
*cue
, unsigned long curpos
)
291 while (i
< cue
->track_count
-1 && cue
->tracks
[i
+1].offset
< curpos
)
294 cue
->curr_track_idx
= i
;
295 cue
->curr_track
= cue
->tracks
+ i
;
299 /* callback that gives list item titles for the cuesheet browser */
300 static const char* list_get_name_cb(int selected_item
,
305 struct cuesheet
*cue
= (struct cuesheet
*)data
;
307 if (selected_item
& 1)
308 strlcpy(buffer
, cue
->tracks
[selected_item
/2].title
, buffer_len
);
310 snprintf(buffer
, buffer_len
, "%02d. %s", selected_item
/2+1,
311 cue
->tracks
[selected_item
/2].performer
);
316 void browse_cuesheet(struct cuesheet
*cue
)
318 struct gui_synclist lists
;
322 char title
[MAX_PATH
];
323 struct cuesheet_file cue_file
;
324 struct mp3entry
*id3
= audio_current_track();
326 snprintf(title
, MAX_PATH
, "%s: %s", cue
->performer
, cue
->title
);
327 gui_synclist_init(&lists
, list_get_name_cb
, cue
, false, 2, NULL
);
328 gui_synclist_set_nb_items(&lists
, 2*cue
->track_count
);
329 gui_synclist_set_title(&lists
, title
, 0);
334 gui_synclist_select_item(&lists
,
335 2*cue_find_current_track(cue
, id3
->elapsed
));
340 gui_synclist_draw(&lists
);
341 action
= get_action(CONTEXT_LIST
,TIMEOUT_BLOCK
);
342 if (gui_synclist_do_button(&lists
, &action
, LIST_WRAP_UNLESS_HELD
))
347 id3
= audio_current_track();
348 if (id3
&& *id3
->path
&& strcmp(id3
->path
, "No file!"))
350 look_for_cuesheet_file(id3
, &cue_file
);
351 if (id3
->cuesheet
&& !strcmp(cue
->path
, cue_file
.path
))
353 sel
= gui_synclist_get_sel_pos(&lists
);
354 seek(cue
->tracks
[sel
/2].offset
);
358 case ACTION_STD_CANCEL
:
364 bool display_cuesheet_content(char* filename
)
367 struct cuesheet_file cue_file
;
368 struct cuesheet
*cue
= (struct cuesheet
*)plugin_get_buffer(&bufsize
);
369 if (!cue
|| bufsize
< sizeof(struct cuesheet
))
372 strlcpy(cue_file
.path
, filename
, MAX_PATH
);
376 if (!parse_cuesheet(&cue_file
, cue
))
379 browse_cuesheet(cue
);
383 /* skips backwards or forward in the current cuesheet
384 * the return value indicates whether we're still in a cusheet after skipping
385 * it also returns false if we weren't in a cuesheet.
386 * direction should be 1 or -1.
388 bool curr_cuesheet_skip(struct cuesheet
*cue
, int direction
, unsigned long curr_pos
)
390 int track
= cue_find_current_track(cue
, curr_pos
);
392 if (direction
>= 0 && track
== cue
->track_count
- 1)
394 /* we want to get out of the cuesheet */
399 if (!(direction
<= 0 && track
== 0))
401 /* If skipping forward, skip to next cuesheet segment. If skipping
402 backward before DEFAULT_SKIP_TRESH milliseconds have elapsed, skip
403 to previous cuesheet segment. If skipping backward after
404 DEFAULT_SKIP_TRESH seconds have elapsed, skip to the start of the
405 current cuesheet segment */
406 if (direction
== 1 ||
407 ((curr_pos
- cue
->tracks
[track
].offset
) < DEFAULT_SKIP_TRESH
))
413 seek(cue
->tracks
[track
].offset
);
419 #ifdef HAVE_LCD_BITMAP
420 static inline void draw_veritcal_line_mark(struct screen
* screen
,
423 screen
->set_drawmode(DRMODE_COMPLEMENT
);
424 screen
->vline(x
, y
, y
+h
-1);
427 /* draw the cuesheet markers for a track of length "tracklen",
428 between (x,y) and (x+w,y) */
429 void cue_draw_markers(struct screen
*screen
, struct cuesheet
*cue
,
430 unsigned long tracklen
,
431 int x
, int y
, int w
, int h
)
434 unsigned long tracklen_seconds
= tracklen
/1000; /* duration in seconds */
436 for (i
=1; i
< cue
->track_count
; i
++)
438 /* Convert seconds prior to multiplication to avoid overflow. */
439 xi
= x
+ (w
* (cue
->tracks
[i
].offset
/1000)) / tracklen_seconds
;
440 draw_veritcal_line_mark(screen
, xi
, y
, h
);
445 bool cuesheet_subtrack_changed(struct mp3entry
*id3
)
447 struct cuesheet
*cue
= id3
->cuesheet
;
448 if (cue
&& (id3
->elapsed
< cue
->curr_track
->offset
449 || (cue
->curr_track_idx
< cue
->track_count
- 1
450 && id3
->elapsed
>= (cue
->curr_track
+1)->offset
)))
452 cue_find_current_track(cue
, id3
->elapsed
);