diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 5af10d4ab8..ad98416b60 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -2373,3 +2373,57 @@ new: #carry on adding normal LANG_ strings below +id: LANG_VOICE +desc: root of voice menu +eng: "Voice" +voice: "Voice" +new: + +id: LANG_VOICE_MENU +desc: item of voice menu, enable/disable the voice UI +eng: "Voice Menus" +voice: "Voice Menus" +new: + +id: LANG_VOICE_DIR +desc: item of voice menu, set the "talkbox" mode for directories +eng: "Voice Directories" +voice: "Voice Directories" +new: + +id: LANG_VOICE_FILE +desc: item of voice menu, set the voive mode for files +eng: "Voice Filenames" +voice: "Voice Filenames" +new: + +id: LANG_VOICE_NUMBER +desc: "talkbox" mode for files+directories +eng: "Numbers" +voice: "Numbers" +new: + +id: LANG_VOICE_DIR_ENTER +desc: "talkbox" mode for directories +eng: "on enter" +voice: "on enter" +new: + +id: LANG_VOICE_DIR_HOVER +desc: "talkbox" mode for directories +eng: "while hovering" +voice: "while hovering" +new: + +id: VOICE_FILE +desc: spoken only, prefix for file number +eng: "" +voice: "file" +new: + +id: VOICE_DIR +desc: spoken only, prefix for directory number +eng: "" +voice: "folder" +new: + diff --git a/apps/menu.c b/apps/menu.c index d1f073645b..eae5ca2021 100644 --- a/apps/menu.c +++ b/apps/menu.c @@ -243,7 +243,7 @@ static void put_cursor(int m, int target) if (do_update) { /* "say" the entry under the cursor */ int voice_id = menus[m].items[menus[m].cursor].voice_id; - if (voice_id >= 0) /* valid ID given? */ + if (voice_id >= 0 && global_settings.talk_menu) /* valid ID given? */ talk_id(voice_id, false); /* say it */ } @@ -320,7 +320,7 @@ int menu_show(int m) /* say current entry */ voice_id = menus[m].items[menus[m].cursor].voice_id; - if (voice_id >= 0) /* valid ID given? */ + if (voice_id >= 0 && global_settings.talk_menu) /* valid ID given? */ talk_id(voice_id, false); /* say it */ while (!exit) { diff --git a/apps/settings.c b/apps/settings.c index 2bc7f45f0b..a9691f3a6a 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -456,6 +456,9 @@ int settings_save( void ) config_block[0xf4]=((unsigned char)global_settings.rec_prerecord_time | ((unsigned char)global_settings.rec_directory << 5)); + config_block[0xf5] = (global_settings.talk_dir & 7) | + ((global_settings.talk_file & 3) << 3) | + ((global_settings.talk_menu & 1) << 5); if(save_config_buffer()) { @@ -793,6 +796,11 @@ void settings_load(void) global_settings.rec_prerecord_time = config_block[0xf4] & 0x1f; global_settings.rec_directory = (config_block[0xf4] >> 5) & 3; } + if (config_block[0xf5] != 0xff) { + global_settings.talk_dir = config_block[0xf5] & 7; + global_settings.talk_file = (config_block[0xf5] >> 3) & 3; + global_settings.talk_menu = (config_block[0xf5] >> 5) & 1; + } #ifdef HAVE_LCD_CHARCELLS if (config_block[0xa8] != 0xff) @@ -1193,6 +1201,20 @@ bool settings_load_config(char* file) set_cfg_option(&global_settings.playlist_viewer_track_display, value, options, 2); } + else if (!strcasecmp(name, "talk dir")) + { + static char* options[] = {"off", "number", "enter", "hover"}; + set_cfg_option(&global_settings.talk_dir, value, options, 4); + } + else if (!strcasecmp(name, "talk file")) + { + static char* options[] = {"off", "number"}; + set_cfg_option(&global_settings.talk_dir, value, options, 2); + } + else if (!strcasecmp(name, "talk menu")) + { + set_cfg_bool(&global_settings.talk_menu, value); + } } close(fd); @@ -1539,6 +1561,16 @@ bool settings_save_config(void) options[global_settings.playlist_viewer_track_display]); } } + fprintf(fd, "#\r\n# Voice\r\n#\r\n"); + { + static char* options[] = {"off", "number", "enter", "hover"}; + fprintf(fd, "talk dir: %s\r\n", + options[global_settings.talk_dir]); + fprintf(fd, "talk file: %s\r\n", /* recycle the options, */ + options[global_settings.talk_file]); /* first 2 are alike */ + fprintf(fd, "talk menu: %s\r\n", + boolopt[global_settings.talk_menu]); + } close(fd); @@ -1646,6 +1678,10 @@ void settings_reset(void) { global_settings.playlist_viewer_icons = true; global_settings.playlist_viewer_indices = true; global_settings.playlist_viewer_track_display = 0; + /* talking menu on by default, to help the blind (if voice file present) */ + global_settings.talk_menu = 1; + global_settings.talk_dir = 0; + global_settings.talk_file = 0; } bool set_bool(char* string, bool* variable ) @@ -1713,7 +1749,7 @@ bool set_int(char* string, #endif lcd_update(); - if (*variable != last_value) + if (global_settings.talk_menu && *variable != last_value) { if (voice_unit < UNIT_LAST) { /* use the available unit definition */ @@ -1829,7 +1865,7 @@ bool set_option(char* string, void* variable, enum optiontype type, while ( !done ) { index = type==INT ? *intvar : (int)*boolvar; lcd_puts(0, 1, options[index].string); - if (index != oldindex) + if (global_settings.talk_menu && index != oldindex) { talk_id(options[index].voice_id, false); oldindex = index; diff --git a/apps/settings.h b/apps/settings.h index 350d14a346..0bf67b474b 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -202,6 +202,11 @@ struct user_settings bool playlist_viewer_icons; /* display icons on viewer */ bool playlist_viewer_indices; /* display playlist indices on viewer */ int playlist_viewer_track_display; /* how to display tracks in viewer */ + + /* voice UI settings */ + bool talk_menu; /* enable voice UI */ + int talk_dir; /* talkbox mode: 0=off 1=number 2=clip@enter 3=clip@hover */ + int talk_file; /* voice filename mode: 0=off, 1=number, other t.b.d. */ }; enum optiontype { INT, BOOL }; diff --git a/apps/settings_menu.c b/apps/settings_menu.c index 35602f001b..a0f039b9a0 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -876,6 +876,56 @@ static bool language_browse(void) return rockbox_browse(ROCKBOX_DIR LANG_DIR, SHOW_LNG); } +static bool voice_menus(void) +{ + bool ret; + bool temp = global_settings.talk_menu; + /* work on a temp variable first, avoid "life" disabling */ + ret = set_bool( str(LANG_VOICE_MENU), &temp ); + global_settings.talk_menu = temp; + return ret; +} + +static bool voice_dirs(void) +{ + struct opt_items names[] = { + { STR(LANG_OFF) }, + { STR(LANG_VOICE_NUMBER) }, + { STR(LANG_VOICE_DIR_ENTER) }, + { STR(LANG_VOICE_DIR_HOVER) } + }; + return set_option( str(LANG_VOICE_DIR), + &global_settings.talk_dir, INT, names, 4, NULL); +} + +static bool voice_files(void) +{ + struct opt_items names[] = { + { STR(LANG_OFF) }, + { STR(LANG_VOICE_NUMBER) } + }; + return set_option( str(LANG_VOICE_DIR), + &global_settings.talk_file, INT, names, 2, NULL); +} + +static bool voice_menu(void) +{ + int m; + bool result; + + struct menu_item items[] = { + { STR(LANG_VOICE_MENU), voice_menus }, + { STR(LANG_VOICE_DIR), voice_dirs }, + { STR(LANG_VOICE_FILE), voice_files } + }; + + m=menu_init( items, sizeof(items) / sizeof(*items), NULL, + NULL, NULL, NULL); + result = menu_run(m); + menu_exit(m); + return result; +} + #ifdef HAVE_LCD_BITMAP static bool font_browse(void) { @@ -1283,6 +1333,7 @@ bool settings_menu(void) { STR(LANG_SYSTEM), system_settings_menu }, { STR(LANG_BOOKMARK_SETTINGS),bookmark_settings_menu }, { STR(LANG_LANGUAGE), language_browse }, + { STR(LANG_VOICE), voice_menu }, }; m=menu_init( items, sizeof(items) / sizeof(*items), NULL, diff --git a/apps/sleeptimer.c b/apps/sleeptimer.c index 1a6e3ec844..a236ed9ba7 100644 --- a/apps/sleeptimer.c +++ b/apps/sleeptimer.c @@ -144,7 +144,7 @@ bool sleeptimer_screen(void) hours, minutes); lcd_puts(0, 1, buf); - if (sayit) + if (sayit && global_settings.talk_menu) { bool enqueue = false; /* first one should not ne queued */ diff --git a/apps/sound_menu.c b/apps/sound_menu.c index 67e6999e8d..23d4c811a5 100644 --- a/apps/sound_menu.c +++ b/apps/sound_menu.c @@ -87,7 +87,8 @@ bool set_sound(char* string, { snprintf(str,sizeof str,"%d %s ", val, unit); } - talk_value(val, talkunit, false); /* speak it */ + if (global_settings.talk_menu) + talk_value(val, talkunit, false); /* speak it */ } lcd_puts(0,1,str); status_draw(true); diff --git a/apps/talk.c b/apps/talk.c index 3849f0e847..6dd0076f06 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -30,14 +30,14 @@ #include "mpeg.h" #include "lang.h" #include "talk.h" -#include "screens.h" /* test hack */ -#include "kernel.h" +#include "id3.h" extern void bitswap(unsigned char *data, int length); /* no header for this */ /***************** Constants *****************/ #define QUEUE_SIZE 20 const char* voicefont_file = "/.rockbox/langs/english.voice"; +const char* dir_thumbnail_name = ".dirname.mp3"; /***************** Data types *****************/ @@ -317,6 +317,7 @@ int talk_file(char* filename, bool enqueue) { int fd; int size; + struct mp3entry info; if (mpeg_status()) /* busy, buffer in use */ return -1; @@ -324,12 +325,19 @@ int talk_file(char* filename, bool enqueue) if (p_thumbnail == NULL || size_for_thumbnail <= 0) return -1; + if(mp3info(&info, filename)) /* use this to find real start */ + { + return 0; /* failed to open, or invalid */ + } + fd = open(filename, O_RDONLY); if (fd < 0) /* failed to open */ { return 0; } + lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ + size = read(fd, p_thumbnail, size_for_thumbnail); close(fd); diff --git a/apps/talk.h b/apps/talk.h index 518549ed74..f3c1366bb0 100644 --- a/apps/talk.h +++ b/apps/talk.h @@ -54,6 +54,9 @@ enum { /* convenience macro to have both string and ID as arguments */ #define STR(id) str(id), id +/* publish this string, so it's stored only once (better than #define) */ +extern const char* dir_thumbnail_name; + void talk_init(void); int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */ diff --git a/apps/tree.c b/apps/tree.c index d56dacc130..9d6a66684e 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -55,6 +55,7 @@ #include "plugin.h" #include "power.h" #include "action.h" +#include "talk.h" #ifdef HAVE_LCD_BITMAP #include "widgets.h" @@ -112,6 +113,7 @@ static int dircursor; static int dirstart; static int dirlevel; static int filesindir; +static int dirsindir; /* how many of the dircache entries are directories */ static int dirpos[MAX_DIR_LEVELS]; static int cursorpos[MAX_DIR_LEVELS]; static char lastdir[MAX_PATH]; @@ -122,6 +124,7 @@ static bool reload_dir = false; static int boot_size = 0; static int boot_cluster; static bool boot_changed = false; +static bool enqueue_next = false; static bool start_wps = false; static bool dirbrowse(char *root, int *dirfilter); @@ -199,7 +202,8 @@ static int build_playlist(int start_index) for(i = 0;i < filesindir;i++) { - if((dircache[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) + if((dircache[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA + && (strcmp(dircache[i].name, dir_thumbnail_name) != 0)) { DEBUGF("Adding %s\n", dircache[i].name); if (playlist_add(dircache[i].name) < 0) @@ -216,6 +220,36 @@ static int build_playlist(int start_index) return start_index; } + +static int play_dirname(int start_index, bool enqueue) +{ + int fd; + char dirname_mp3_filename[MAX_PATH+1]; + + if (mpeg_status() & MPEG_STATUS_PLAY) + return 0; + + snprintf(dirname_mp3_filename, sizeof(dirname_mp3_filename), "%s/%s/%s", + currdir, dircache[start_index].name, dir_thumbnail_name); + + DEBUGF("Checking for %s\n", dirname_mp3_filename); + + fd = open(dirname_mp3_filename, O_RDONLY); + if (fd < 0) + { + DEBUGF("Failed to find: %s\n", dirname_mp3_filename); + return -1; + } + + close(fd); + + DEBUGF("Found: %s\n", dirname_mp3_filename); + + talk_file(dirname_mp3_filename, enqueue); + return 1; +} + + static int compare(const void* p1, const void* p2) { struct entry* e1 = (struct entry*)p1; @@ -278,6 +312,7 @@ struct entry* load_and_sort_directory(char *dirname, int *dirfilter, return NULL; /* not a directory */ name_buffer_length = 0; + dirsindir = 0; *buffer_full = false; for ( i=0; i < max_files_in_dir; i++ ) { @@ -369,6 +404,9 @@ struct entry* load_and_sort_directory(char *dirname, int *dirfilter, dptr->name = &name_buffer[name_buffer_length]; strcpy(dptr->name,entry->d_name); name_buffer_length += len + 1; + + if (dptr->attr & ATTR_DIRECTORY) /* count the remaining dirs */ + dirsindir++; } *num_files = i; closedir(dir); @@ -1019,6 +1057,14 @@ static bool dirbrowse(char *root, int *dirfilter) snprintf(buf,sizeof(buf),"/%s",file->name); if (file->attr & ATTR_DIRECTORY) { + if (global_settings.talk_dir == 2) /* enter */ + { + /* play_dirname */ + DEBUGF("Playing directory thumbnail: %s", currdir); + play_dirname(dircursor+dirstart, false); + /* avoid reading getting cut by next filename */ + enqueue_next = true; + } memcpy(currdir,buf,sizeof(currdir)); if ( dirlevel < MAX_DIR_LEVELS ) { dirpos[dirlevel] = dirstart; @@ -1425,6 +1471,32 @@ static bool dirbrowse(char *root, int *dirfilter) showfileline(dircursor, i, true, dirfilter); /* scroll please */ need_update = true; + + if (dircache[i].attr & ATTR_DIRECTORY) /* directory? */ + { + int ret = 0; + /* play directory thumbnail */ + if (global_settings.talk_dir == 3) /* hover */ + { + DEBUGF("Playing directory thumbnail: %s", currdir); + ret = play_dirname(dircursor+dirstart, false); + } + + if (global_settings.talk_dir == 1 /* dirs as numbers */ + || ret == -1) /* or no thumbnail found above */ + { + talk_id(VOICE_DIR, false); + talk_number(i+1, true); + } + } + else if (global_settings.talk_file == 1) /* files as numbers */ + { + /* enqueue_next is true if still talking the dir name */ + talk_id(VOICE_FILE, enqueue_next); + talk_number(i-dirsindir+1, true); + enqueue_next = false; + } + } }