Add support for cp1252 (Western European) codepage.
[maemo-rb.git] / apps / cuesheet.c
blob354f976997068a8af85e8abf9b68cf3b26c95eae
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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 ****************************************************************************/
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <ctype.h>
26 #include <string.h>
27 #include "system.h"
28 #include "audio.h"
29 #include "kernel.h"
30 #include "logf.h"
31 #include "misc.h"
32 #include "screens.h"
33 #include "list.h"
34 #include "action.h"
35 #include "lang.h"
36 #include "debug.h"
37 #include "settings.h"
38 #include "plugin.h"
39 #include "playback.h"
40 #include "cuesheet.h"
41 #include "gui/wps.h"
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];
50 char *dot, *slash;
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);
58 return true;
61 cue_file->pos = 0;
62 cue_file->size = 0;
63 cue_file->path[0] = '\0';
64 slash = strrchr(track_id3->path, '/');
65 if (!slash)
66 return false;
67 strlcpy(cuepath, track_id3->path, MAX_PATH);
68 dot = strrchr(cuepath, '.');
69 strcpy(dot, ".cue");
71 if (!file_exists(cuepath))
73 strcpy(cuepath, CUE_DIR);
74 strcat(cuepath, slash);
75 char *dot = strrchr(cuepath, '.');
76 strcpy(dot, ".cue");
77 if (!file_exists(cuepath))
78 return false;
81 strlcpy(cue_file->path, cuepath, MAX_PATH);
82 return true;
85 static char *get_string(const char *line)
87 char *start, *end;
89 start = strchr(line, '"');
90 if (!start)
92 start = strchr(line, ' ');
94 if (!start)
95 return NULL;
98 end = strchr(++start, '"');
99 if (end)
100 *end = '\0';
102 return start;
105 /* parse cuesheet "cue_file" and store the information in "cue" */
106 bool parse_cuesheet(struct cuesheet_file *cue_file, struct cuesheet *cue)
108 char line[MAX_PATH];
109 char *s;
110 unsigned char char_enc = CHAR_ENC_ISO_8859_1;
111 bool is_embedded = false;
112 int line_len;
113 int bytes_left = 0;
114 int read_bytes = MAX_PATH;
115 unsigned char utf16_buf[MAX_PATH];
117 int fd = open(cue_file->path, O_RDONLY, 0644);
118 if(fd < 0)
119 return false;
120 if (cue_file->pos > 0)
122 is_embedded = true;
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);
148 if (is_embedded)
150 if (bom_read > 0)
151 bytes_left -= bom_read;
152 if (read_bytes > bytes_left)
153 read_bytes = bytes_left;
156 /* Initialization */
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 */
168 *s = '\0';
169 strcpy(line, utf16_buf);
170 /* chomp the trailing 0 after the newline */
171 lseek(fd, 1, SEEK_CUR);
172 line_len++;
174 else if (char_enc == CHAR_ENC_UTF_16_BE)
176 s = utf16BEdecode(line, utf16_buf, line_len);
177 *s = '\0';
178 strcpy(line, utf16_buf);
180 s = skip_whitespace(line);
182 if (!strncmp(s, "TRACK", 5))
184 cue->track_count++;
186 else if (!strncmp(s, "INDEX 01", 8))
188 s = strchr(s,' ');
189 s = skip_whitespace(s);
190 s = strchr(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))
202 char *dest = NULL;
203 char *string = get_string(s);
204 if (!string)
205 break;
207 switch (*s)
209 case 'T': /* TITLE */
210 dest = (cue->track_count <= 0) ? cue->title :
211 cue->tracks[cue->track_count-1].title;
212 break;
214 case 'P': /* PERFORMER */
215 dest = (cue->track_count <= 0) ? cue->performer :
216 cue->tracks[cue->track_count-1].performer;
217 break;
219 case 'S': /* SONGWRITER */
220 dest = (cue->track_count <= 0) ? cue->songwriter :
221 cue->tracks[cue->track_count-1].songwriter;
222 break;
225 if (dest)
227 if (char_enc == CHAR_ENC_ISO_8859_1)
229 dest = iso_decode(string, dest, -1,
230 MIN(strlen(string), MAX_NAME));
231 *dest = '\0';
233 else
235 strlcpy(dest, string, MAX_NAME*3 + 1);
239 if (is_embedded)
241 bytes_left -= line_len;
242 if (bytes_left <= 0)
243 break;
244 if (bytes_left < read_bytes)
245 read_bytes = bytes_left;
248 close(fd);
250 /* If some songs don't have performer info, we copy the cuesheet performer */
251 int i;
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);
261 return true;
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))
270 return false;
272 else
274 #if (CONFIG_CODEC == SWCODEC)
275 audio_pre_ff_rewind();
276 audio_ff_rewind(pos);
277 #else
278 audio_pause();
279 audio_ff_rewind(pos);
280 audio_resume();
281 #endif
282 return true;
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)
290 int i=0;
291 while (i < cue->track_count-1 && cue->tracks[i+1].offset < curpos)
292 i++;
294 cue->curr_track_idx = i;
295 cue->curr_track = cue->tracks + i;
296 return i;
299 /* callback that gives list item titles for the cuesheet browser */
300 static const char* list_get_name_cb(int selected_item,
301 void *data,
302 char *buffer,
303 size_t buffer_len)
305 struct cuesheet *cue = (struct cuesheet *)data;
307 if (selected_item & 1)
308 strlcpy(buffer, cue->tracks[selected_item/2].title, buffer_len);
309 else
310 snprintf(buffer, buffer_len, "%02d. %s", selected_item/2+1,
311 cue->tracks[selected_item/2].performer);
313 return buffer;
316 void browse_cuesheet(struct cuesheet *cue)
318 struct gui_synclist lists;
319 int action;
320 bool done = false;
321 int sel;
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);
332 if (id3)
334 gui_synclist_select_item(&lists,
335 2*cue_find_current_track(cue, id3->elapsed));
338 while (!done)
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))
343 continue;
344 switch (action)
346 case ACTION_STD_OK:
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);
357 break;
358 case ACTION_STD_CANCEL:
359 done = true;
364 bool display_cuesheet_content(char* filename)
366 size_t bufsize = 0;
367 struct cuesheet_file cue_file;
368 struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize);
369 if (!cue || bufsize < sizeof(struct cuesheet))
370 return false;
372 strlcpy(cue_file.path, filename, MAX_PATH);
373 cue_file.pos = 0;
374 cue_file.size = 0;
376 if (!parse_cuesheet(&cue_file, cue))
377 return false;
379 browse_cuesheet(cue);
380 return true;
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 */
395 return false;
397 else
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))
409 track += direction;
413 seek(cue->tracks[track].offset);
414 return true;
419 #ifdef HAVE_LCD_BITMAP
420 static inline void draw_veritcal_line_mark(struct screen * screen,
421 int x, int y, int h)
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)
433 int i,xi;
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);
443 #endif
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);
453 return true;
455 return false;