forked from len0rd/rockbox
		
	Now returns true if the action was handled in that function instead of returning the handled action. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@14733 a1c6a512-1295-4272-9138-f99709370657
		
			
				
	
	
		
			405 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			405 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 | |
|  *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 | |
|  *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 | |
|  *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 | |
|  *                     \/            \/     \/    \/            \/
 | |
|  * $Id$
 | |
|  *
 | |
|  * Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon
 | |
|  *
 | |
|  * All files in this archive are subject to the GNU General Public License.
 | |
|  * See the file COPYING in the source tree root for full license agreement.
 | |
|  *
 | |
|  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 | |
|  * KIND, either express or implied.
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| 
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdbool.h>
 | |
| #include <atoi.h>
 | |
| #include <ctype.h>
 | |
| #include <string.h>
 | |
| #include "system.h"
 | |
| #include "audio.h"
 | |
| #include "kernel.h"
 | |
| #include "logf.h"
 | |
| #include "sprintf.h"
 | |
| #include "misc.h"
 | |
| #include "screens.h"
 | |
| #include "splash.h"
 | |
| #include "list.h"
 | |
| #include "action.h"
 | |
| #include "lang.h"
 | |
| #include "debug.h"
 | |
| #include "settings.h"
 | |
| #include "buffer.h"
 | |
| #include "plugin.h"
 | |
| #include "playback.h"
 | |
| #include "cuesheet.h"
 | |
| 
 | |
| #define CUE_DIR ROCKBOX_DIR "/cue"
 | |
| 
 | |
| #if CONFIG_CODEC != SWCODEC
 | |
| /* special trickery because the hwcodec playback engine is in firmware/ */
 | |
| static bool cuesheet_handler(const char *filename)
 | |
| {
 | |
|     return cuesheet_is_enabled() && look_for_cuesheet_file(filename, NULL);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void cuesheet_init(void)
 | |
| {
 | |
|     if (global_settings.cuesheet) {
 | |
|         curr_cue = (struct cuesheet *)buffer_alloc(sizeof(struct cuesheet));
 | |
|         temp_cue = (struct cuesheet *)buffer_alloc(sizeof(struct cuesheet));
 | |
| #if CONFIG_CODEC != SWCODEC
 | |
|         audio_set_cuesheet_callback(cuesheet_handler);
 | |
| #endif
 | |
|     } else {
 | |
|         curr_cue = NULL;
 | |
|         temp_cue = NULL;
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool cuesheet_is_enabled(void)
 | |
| {
 | |
|     return (curr_cue != NULL);
 | |
| }
 | |
| 
 | |
| bool look_for_cuesheet_file(const char *trackpath, char *found_cue_path)
 | |
| {
 | |
|     DEBUGF("look for cue file\n");
 | |
|     char cuepath[MAX_PATH];
 | |
|     strncpy(cuepath, trackpath, MAX_PATH);
 | |
|     char *dot = strrchr(cuepath, '.');
 | |
|     strcpy(dot, ".cue");
 | |
| 
 | |
|     int fd = open(cuepath,O_RDONLY);
 | |
|     if (fd < 0)
 | |
|     {
 | |
|         strcpy(cuepath, CUE_DIR);
 | |
|         strcat(cuepath, strrchr(trackpath, '/'));
 | |
|         char *dot = strrchr(cuepath, '.');
 | |
|         strcpy(dot, ".cue");
 | |
|         fd = open(cuepath,O_RDONLY);
 | |
|         if (fd < 0)
 | |
|         {
 | |
|             if (found_cue_path)
 | |
|                 found_cue_path = NULL;
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
|     close(fd);
 | |
|     if (found_cue_path)
 | |
|         strncpy(found_cue_path, cuepath, MAX_PATH);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static char *skip_whitespace(char* buf)
 | |
| {
 | |
|     char *r = buf;
 | |
|     while (*r && isspace(*r))
 | |
|         r++;
 | |
|     return r;
 | |
| }
 | |
| 
 | |
| 
 | |
| static char *get_string(const char *line)
 | |
| {
 | |
|     char *start, *end;
 | |
| 
 | |
|     start = strchr(line, '"');
 | |
|     if (!start)
 | |
|     {
 | |
|         start = strchr(line, ' ');
 | |
| 
 | |
|         if (!start)
 | |
|             return NULL;
 | |
|     }
 | |
| 
 | |
|     end = strchr(++start, '"');
 | |
|     if (end)
 | |
|         *end = '\0';
 | |
| 
 | |
|     return start;
 | |
| }
 | |
| 
 | |
| /* parse cuesheet "file" and store the information in "cue" */
 | |
| bool parse_cuesheet(char *file, struct cuesheet *cue)
 | |
| {
 | |
|     char line[MAX_PATH];
 | |
|     char *s;
 | |
| 
 | |
|     DEBUGF("cue parse\n");
 | |
|     int fd = open(file,O_RDONLY);
 | |
|     if (fd < 0)
 | |
|     {
 | |
|         /* couln't open the file */
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /* Initialization */
 | |
|     memset(cue, 0, sizeof(struct cuesheet));
 | |
|     strcpy(cue->path, file);
 | |
|     cue->curr_track = cue->tracks;
 | |
| 
 | |
|     while ( read_line(fd,line,MAX_PATH) && cue->track_count < MAX_TRACKS )
 | |
|     {
 | |
|         s = skip_whitespace(line);
 | |
| 
 | |
|         if (!strncmp(s, "TRACK", 5))
 | |
|         {
 | |
|             cue->track_count++;
 | |
|         }
 | |
|         else if (!strncmp(s, "INDEX 01", 8))
 | |
|         {
 | |
|             s = strchr(s,' ');
 | |
|             s = skip_whitespace(s);
 | |
|             s = strchr(s,' ');
 | |
|             s = skip_whitespace(s);
 | |
|             cue->tracks[cue->track_count-1].offset = 60*1000 * atoi(s);
 | |
|             s = strchr(s,':') + 1;
 | |
|             cue->tracks[cue->track_count-1].offset += 1000 * atoi(s);
 | |
|             s = strchr(s,':') + 1;
 | |
|             cue->tracks[cue->track_count-1].offset += 13 * atoi(s);
 | |
|         }
 | |
|         else if (!strncmp(s, "TITLE", 5)
 | |
|                  || !strncmp(s, "PERFORMER", 9)
 | |
|                  || !strncmp(s, "SONGWRITER", 10))
 | |
|         {
 | |
|             char *dest = NULL;
 | |
|             char *string = get_string(s);
 | |
|             if (!string)
 | |
|                 break;
 | |
| 
 | |
|             switch (*s)
 | |
|             {
 | |
|                 case 'T': /* TITLE */
 | |
|                     dest = (cue->track_count <= 0) ? cue->title :
 | |
|                             cue->tracks[cue->track_count-1].title;
 | |
|                     break;
 | |
| 
 | |
|                 case 'P': /* PERFORMER */
 | |
|                     dest = (cue->track_count <= 0) ? cue->performer :
 | |
|                         cue->tracks[cue->track_count-1].performer;
 | |
|                     break;
 | |
| 
 | |
|                 case 'S': /* SONGWRITER */
 | |
|                     dest = (cue->track_count <= 0) ? cue->songwriter :
 | |
|                             cue->tracks[cue->track_count-1].songwriter;
 | |
|                     break;
 | |
|             }
 | |
| 
 | |
|             if (dest)
 | |
|                 strncpy(dest, string, MAX_NAME);
 | |
|         }
 | |
|     }
 | |
|     close(fd);
 | |
| 
 | |
|     /* If some songs don't have performer info, we copy the cuesheet performer */
 | |
|     int i;
 | |
|     for (i = 0; i < cue->track_count; i++)
 | |
|     {
 | |
|         if (*(cue->tracks[i].performer) == '\0')
 | |
|             strncpy(cue->tracks[i].performer, cue->performer, MAX_NAME);
 | |
| 
 | |
|         if (*(cue->tracks[i].songwriter) == '\0')
 | |
|             strncpy(cue->tracks[i].songwriter, cue->songwriter, MAX_NAME);
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /* takes care of seeking to a track in a playlist
 | |
|  * returns false if audio  isn't playing */
 | |
| static bool seek(unsigned long pos)
 | |
| {
 | |
|     if (!(audio_status() & AUDIO_STATUS_PLAY))
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| #if (CONFIG_CODEC == SWCODEC)
 | |
|         audio_pre_ff_rewind();
 | |
|         audio_ff_rewind(pos);
 | |
| #else
 | |
|         audio_pause();
 | |
|         audio_ff_rewind(pos);
 | |
|         audio_resume();
 | |
| #endif
 | |
|         return true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* returns the index of the track currently being played
 | |
|    and updates the information about the current track. */
 | |
| int cue_find_current_track(struct cuesheet *cue, unsigned long curpos)
 | |
| {
 | |
|     int i=0;
 | |
|     while (i < cue->track_count-1 && cue->tracks[i+1].offset < curpos)
 | |
|         i++;
 | |
| 
 | |
|     cue->curr_track_idx = i;
 | |
|     cue->curr_track = cue->tracks + i;
 | |
|     return i;
 | |
| }
 | |
| 
 | |
| /* callback that gives list item titles for the cuesheet browser */
 | |
| static char *list_get_name_cb(int selected_item,
 | |
|                              void *data,
 | |
|                              char *buffer)
 | |
| {
 | |
|     struct cuesheet *cue = (struct cuesheet *)data;
 | |
| 
 | |
|     if (selected_item & 1)
 | |
|         snprintf(buffer, MAX_PATH, "%s",
 | |
|                  cue->tracks[selected_item/2].title);
 | |
|     else
 | |
|         snprintf(buffer, MAX_PATH, "%02d. %s", selected_item/2+1,
 | |
|                  cue->tracks[selected_item/2].performer);
 | |
| 
 | |
|     return buffer;
 | |
| }
 | |
| 
 | |
| void browse_cuesheet(struct cuesheet *cue)
 | |
| {
 | |
|     struct gui_synclist lists;
 | |
|     int action;
 | |
|     bool done = false;
 | |
|     int sel;
 | |
|     char title[MAX_PATH];
 | |
|     char cuepath[MAX_PATH];
 | |
|     struct mp3entry *id3 = audio_current_track();
 | |
| 
 | |
|     snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title);
 | |
|     gui_synclist_init(&lists, list_get_name_cb, cue, false, 2);
 | |
|     gui_synclist_set_nb_items(&lists, 2*cue->track_count);
 | |
|     gui_synclist_set_title(&lists, title, 0);
 | |
| 
 | |
|     if (id3 && *id3->path && strcmp(id3->path, "No file!"))
 | |
|     {
 | |
|         look_for_cuesheet_file(id3->path, cuepath);
 | |
|     }
 | |
| 
 | |
|     if (id3 && id3->cuesheet_type && !strcmp(cue->path, cuepath))
 | |
|     {
 | |
|         gui_synclist_select_item(&lists,
 | |
|                                  2*cue_find_current_track(cue, id3->elapsed));
 | |
|     }
 | |
| 
 | |
|     while (!done)
 | |
|     {
 | |
|         gui_synclist_draw(&lists);
 | |
|         action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
 | |
|         if (gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD))
 | |
|             continue;
 | |
|         switch (action)
 | |
|         {
 | |
|             case ACTION_STD_OK:
 | |
|                 id3 = audio_current_track();
 | |
|                 if (id3 && *id3->path && strcmp(id3->path, "No file!"))
 | |
|                 {
 | |
|                     look_for_cuesheet_file(id3->path, cuepath);
 | |
|                     if (id3->cuesheet_type && !strcmp(cue->path, cuepath))
 | |
|                     {
 | |
|                         sel = gui_synclist_get_sel_pos(&lists);
 | |
|                         seek(cue->tracks[sel/2].offset);
 | |
|                     }
 | |
|                 }
 | |
|                 break;
 | |
|             case ACTION_STD_CANCEL:
 | |
|                 done = true;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool display_cuesheet_content(char* filename)
 | |
| {
 | |
|     size_t bufsize = 0;
 | |
|     struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize);
 | |
|     if (!cue || bufsize < sizeof(struct cuesheet))
 | |
|         return false;
 | |
| 
 | |
|     if (!parse_cuesheet(filename, cue))
 | |
|         return false;
 | |
| 
 | |
|     browse_cuesheet(cue);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| /* skips backwards or forward in the current cuesheet
 | |
|  * the return value indicates whether we're still in a cusheet after skipping
 | |
|  * it also returns false if we weren't in a cuesheet.
 | |
|  * direction should be 1 or -1.
 | |
|  */
 | |
| bool curr_cuesheet_skip(int direction, unsigned long curr_pos)
 | |
| {
 | |
|     int track = cue_find_current_track(curr_cue, curr_pos);
 | |
| 
 | |
|     if (direction >= 0 && track == curr_cue->track_count - 1)
 | |
|     {
 | |
|         /* we want to get out of the cuesheet */
 | |
|         return false;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (!(direction <= 0 && track == 0))
 | |
|             track += direction;
 | |
| 
 | |
|         seek(curr_cue->tracks[track].offset);
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| void cue_spoof_id3(struct cuesheet *cue, struct mp3entry *id3)
 | |
| {
 | |
|     if (!cue || !cue->curr_track)
 | |
|         return;
 | |
| 
 | |
|     struct cue_track_info *track = cue->curr_track;
 | |
| 
 | |
|     id3->title = *track->title ? track->title : NULL;
 | |
|     id3->artist = *track->performer ? track->performer : NULL;
 | |
|     id3->composer = *track->songwriter ? track->songwriter : NULL;
 | |
|     id3->album = *cue->title ? cue->title : NULL;
 | |
| 
 | |
|     /* if the album artist is the same as the track artist, we hide it. */
 | |
|     if (strcmp(cue->performer, track->performer))
 | |
|         id3->albumartist = *cue->performer ? cue->performer : NULL;
 | |
|     else
 | |
|         id3->albumartist = NULL;
 | |
| 
 | |
|     int i = cue->curr_track_idx;
 | |
|     id3->tracknum = i+1;
 | |
|     if (id3->track_string)
 | |
|         snprintf(id3->track_string, 10, "%d/%d", i+1, cue->track_count);
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_LCD_BITMAP
 | |
| static inline void draw_veritcal_line_mark(struct screen * screen,
 | |
|                                            int x, int y, int h)
 | |
| {
 | |
|     screen->set_drawmode(DRMODE_COMPLEMENT);
 | |
|     screen->vline(x, y, y+h-1);
 | |
| }
 | |
| 
 | |
| /* draw the cuesheet markers for a track of length "tracklen",
 | |
|    between (x1,y) and (x2,y) */
 | |
| void cue_draw_markers(struct screen *screen, unsigned long tracklen,
 | |
|                       int x1, int x2, int y, int h)
 | |
| {
 | |
|     int i,xi;
 | |
|     int w = x2 - x1;
 | |
|     for (i=1; i < curr_cue->track_count; i++)
 | |
|     {
 | |
|         xi = x1 + (w * curr_cue->tracks[i].offset)/tracklen;
 | |
|         draw_veritcal_line_mark(screen, xi, y, h);
 | |
|     }
 | |
| }
 | |
| #endif
 |