A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 378 lines 13 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2006 Peter D'Hoye 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/mul_id3.h" 23#include "lib/simple_viewer.h" 24 25#if !defined(ARRAY_SIZE) 26 #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) 27#endif 28 29enum props_types { 30 PROPS_FILE = 0, 31 PROPS_PLAYLIST, 32 PROPS_ID3, 33 PROPS_MUL_ID3, 34 PROPS_DIR 35}; 36 37static struct gui_synclist properties_lists; 38static struct mp3entry id3; 39static struct tm tm; 40static unsigned long display_size; 41static int32_t lang_size_unit; 42static int props_type, mul_id3_count, skipped_count; 43 44static char str_filename[MAX_PATH], str_dirname[MAX_PATH], 45 str_size[64], str_dircount[64], str_filecount[64], 46 str_audio_filecount[64], str_date[64], str_time[64]; 47 48 49#define NUM_FILE_PROPERTIES 5 50#define NUM_PLAYLIST_PROPERTIES (1 + NUM_FILE_PROPERTIES) 51static const unsigned char* const props_file[] = 52{ 53 ID2P(LANG_PROPERTIES_PATH), str_dirname, 54 ID2P(LANG_PROPERTIES_FILENAME), str_filename, 55 ID2P(LANG_PROPERTIES_SIZE), str_size, 56 ID2P(LANG_PROPERTIES_DATE), str_date, 57 ID2P(LANG_PROPERTIES_TIME), str_time, 58 59 ID2P(LANG_MENU_SHOW_ID3_INFO), "...", 60}; 61 62#define NUM_DIR_PROPERTIES 4 63#define NUM_AUDIODIR_PROPERTIES (1 + NUM_DIR_PROPERTIES) 64static const unsigned char* const props_dir[] = 65{ 66 ID2P(LANG_PROPERTIES_PATH), str_dirname, 67 ID2P(LANG_PROPERTIES_SUBDIRS), str_dircount, 68 ID2P(LANG_PROPERTIES_FILES), str_filecount, 69 ID2P(LANG_PROPERTIES_SIZE), str_size, 70 71 ID2P(LANG_MENU_SHOW_ID3_INFO), str_audio_filecount, 72}; 73 74static bool dir_properties(const char* selected_file, struct dir_stats *stats) 75{ 76 rb->strlcpy(stats->dirname, selected_file, sizeof(stats->dirname)); 77 78#ifdef HAVE_ADJUSTABLE_CPU_FREQ 79 rb->cpu_boost(true); 80#endif 81 bool success = collect_dir_stats(stats, NULL); 82#ifdef HAVE_ADJUSTABLE_CPU_FREQ 83 rb->cpu_boost(false); 84#endif 85 if (!success) 86 return false; 87 88 rb->strlcpy(str_dirname, selected_file, sizeof(str_dirname)); 89 rb->snprintf(str_dircount, sizeof str_dircount, "%d", stats->dir_count); 90 rb->snprintf(str_filecount, sizeof str_filecount, "%d", stats->file_count); 91 rb->snprintf(str_audio_filecount, sizeof str_filecount, "%d", 92 stats->audio_file_count); 93 display_size = human_size(stats->byte_count, &lang_size_unit); 94 rb->snprintf(str_size, sizeof str_size, "%lu %s", display_size, 95 rb->str(lang_size_unit)); 96 return true; 97} 98 99static const unsigned char* p2str(const unsigned char* p) 100{ 101 int id = P2ID(p); 102 return (id != -1) ? rb->str(id) : p; 103} 104 105static const char * get_props(int selected_item, void* data, 106 char *buffer, size_t buffer_len) 107{ 108 (void)data; 109 if (PROPS_DIR == props_type) 110 rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_dir)) ? 111 "ERROR" : (char *) p2str(props_dir[selected_item]), buffer_len); 112 else 113 rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_file)) ? 114 "ERROR" : (char *) p2str(props_file[selected_item]), buffer_len); 115 return buffer; 116} 117 118static int speak_property_selection(int selected_item, void *data) 119{ 120 struct dir_stats *stats = data; 121 int32_t id = P2ID((props_type == PROPS_DIR ? 122 props_dir : props_file)[selected_item]); 123 rb->talk_id(id, false); 124 switch (id) 125 { 126 case LANG_PROPERTIES_PATH: 127 rb->talk_fullpath(str_dirname, true); 128 break; 129 case LANG_PROPERTIES_FILENAME: 130 rb->talk_file_or_spell(str_dirname, str_filename, NULL, true); 131 break; 132 case LANG_PROPERTIES_SIZE: 133 rb->talk_number(display_size, true); 134 rb->talk_id(lang_size_unit, true); 135 break; 136 case LANG_PROPERTIES_DATE: 137 rb->talk_date(&tm, true); 138 break; 139 case LANG_PROPERTIES_TIME: 140 rb->talk_time(&tm, true); 141 break; 142 case LANG_PROPERTIES_SUBDIRS: 143 rb->talk_number(stats->dir_count, true); 144 break; 145 case LANG_PROPERTIES_FILES: 146 rb->talk_number(stats->file_count, true); 147 break; 148 default: 149 rb->talk_spell(props_file[selected_item + 1], true); 150 break; 151 } 152 return 0; 153} 154 155static void setup_properties_list(struct dir_stats *stats) 156{ 157 int nb_props; 158 if (props_type == PROPS_FILE) 159 nb_props = NUM_FILE_PROPERTIES; 160 else if (props_type == PROPS_PLAYLIST) 161 nb_props = NUM_PLAYLIST_PROPERTIES; 162 else 163 nb_props = NUM_DIR_PROPERTIES; 164 165 rb->gui_synclist_init(&properties_lists, &get_props, stats, false, 2, NULL); 166 rb->gui_synclist_set_title(&properties_lists, 167 rb->str(props_type == PROPS_DIR ? 168 LANG_PROPERTIES_DIRECTORY_PROPERTIES : 169 LANG_PROPERTIES_FILE_PROPERTIES), 170 NOICON); 171 if (rb->global_settings->talk_menu) 172 rb->gui_synclist_set_voice_callback(&properties_lists, speak_property_selection); 173 rb->gui_synclist_set_nb_items(&properties_lists, nb_props*2); 174} 175 176static int browse_file_or_dir(struct dir_stats *stats) 177{ 178 if (props_type == PROPS_DIR && stats->audio_file_count) 179 rb->gui_synclist_set_nb_items(&properties_lists, NUM_AUDIODIR_PROPERTIES*2); 180 rb->gui_synclist_draw(&properties_lists); 181 rb->gui_synclist_speak_item(&properties_lists); 182 while(true) 183 { 184 int button = rb->get_action(CONTEXT_LIST, HZ); 185 /* HZ so the status bar redraws corectly */ 186 if (rb->gui_synclist_do_button(&properties_lists, &button)) 187 continue; 188 switch(button) 189 { 190 case ACTION_STD_OK:; 191 int sel_pos = rb->gui_synclist_get_sel_pos(&properties_lists); 192 193 /* "Show Track Info..." selected? */ 194 if ((props_type == PROPS_PLAYLIST || props_type == PROPS_DIR) && 195 sel_pos == (props_type == PROPS_DIR ? 196 ARRAY_SIZE(props_dir) : ARRAY_SIZE(props_file)) - 2) 197 return -1; 198 else 199 { 200 /* Display field in fullscreen */ 201 FOR_NB_SCREENS(i) 202 rb->viewportmanager_theme_enable(i, false, NULL); 203 if (props_type == PROPS_DIR) 204 view_text((char *) p2str(props_dir[sel_pos]), 205 (char *) props_dir[sel_pos + 1]); 206 else 207 view_text((char *) p2str(props_file[sel_pos]), 208 (char *) props_file[sel_pos + 1]); 209 FOR_NB_SCREENS(i) 210 rb->viewportmanager_theme_undo(i, false); 211 212 rb->gui_synclist_set_title(&properties_lists, 213 rb->str(props_type == PROPS_DIR ? 214 LANG_PROPERTIES_DIRECTORY_PROPERTIES : 215 LANG_PROPERTIES_FILE_PROPERTIES), 216 NOICON); 217 rb->gui_synclist_draw(&properties_lists); 218 } 219 break; 220 case ACTION_STD_CANCEL: 221 return 0; 222 default: 223 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) 224 return 1; 225 break; 226 } 227 } 228} 229 230static bool determine_props_type(const char *file) 231{ 232 if (file[0] == PATH_SEPCH) 233 { 234 const char* basename = rb->strrchr(file, PATH_SEPCH) + 1; 235 const int dir_len = (basename - file); 236 if ((int) sizeof(str_dirname) <= dir_len) 237 return false; 238 rb->strlcpy(str_dirname, file, dir_len + 1); 239 rb->strlcpy(str_filename, basename, sizeof str_filename); 240 struct dirent* entry; 241 DIR* dir = rb->opendir(str_dirname); 242 if (!dir) 243 return false; 244 while(0 != (entry = rb->readdir(dir))) 245 { 246 if(rb->strcmp(entry->d_name, str_filename)) 247 continue; 248 249 struct dirinfo info = rb->dir_get_info(dir, entry); 250 if (info.attribute & ATTR_DIRECTORY) 251 props_type = PROPS_DIR; 252 else 253 { 254 display_size = human_size(info.size, &lang_size_unit); 255 rb->snprintf(str_size, sizeof str_size, "%lu %s", 256 display_size, rb->str(lang_size_unit)); 257 rb->gmtime_r(&info.mtime, &tm); 258 rb->snprintf(str_date, sizeof str_date, "%04d/%02d/%02d", 259 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); 260 rb->snprintf(str_time, sizeof str_time, "%02d:%02d:%02d", 261 tm.tm_hour, tm.tm_min, tm.tm_sec); 262 263 if (rb->filetype_get_attr(entry->d_name) == FILE_ATTR_M3U) 264 props_type = PROPS_PLAYLIST; 265 else 266 props_type = rb->get_metadata(&id3, -1, file) ? 267 PROPS_ID3 : PROPS_FILE; 268 } 269 rb->closedir(dir); 270 return true; 271 } 272 rb->closedir(dir); 273 } 274 else if (!rb->strcmp(file, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER))) 275 { 276 props_type = PROPS_MUL_ID3; 277 return true; 278 } 279 return false; 280} 281 282bool mul_id3_add(const char *file_name) 283{ 284 if (!file_name || !rb->get_metadata(&id3, -1, file_name)) 285 skipped_count++; 286 else 287 { 288 collect_id3(&id3, mul_id3_count == 0); 289 mul_id3_count++; 290 } 291 return true; 292} 293 294/* Assemble track info from a dir, a playlist, or a database table */ 295static bool assemble_track_info(const char *filename, struct dir_stats *stats) 296{ 297 if (props_type == PROPS_DIR) 298 { 299#ifdef HAVE_ADJUSTABLE_CPU_FREQ 300 rb->cpu_boost(true); 301#endif 302 rb->strlcpy(stats->dirname, filename, sizeof(stats->dirname)); 303 rb->splash_progress_set_delay(HZ/2); /* hide progress bar for 0.5s */ 304 bool success = collect_dir_stats(stats, &mul_id3_add); 305#ifdef HAVE_ADJUSTABLE_CPU_FREQ 306 rb->cpu_boost(false); 307#endif 308 if (!success) 309 return false; 310 } 311 else if(props_type == PROPS_PLAYLIST && 312 !rb->playlist_entries_iterate(filename, NULL, &mul_id3_add)) 313 return false; 314#ifdef HAVE_TAGCACHE 315 else if (props_type == PROPS_MUL_ID3 && 316 !rb->tagtree_subentries_do_action(&mul_id3_add)) 317 return false; 318#endif 319 320 if (mul_id3_count == 0) 321 { 322 rb->splashf(HZ*2, "None found"); 323 return false; 324 } 325 else if (mul_id3_count > 1) /* otherwise, the retrieved id3 can be used as-is */ 326 finalize_id3(&id3); 327 328 if (skipped_count > 0) 329 rb->splashf(HZ*2, "Skipped %d", skipped_count); 330 331 return true; 332} 333 334enum plugin_status plugin_start(const void* parameter) 335{ 336 static struct dir_stats stats; 337 const char *file = parameter; 338#ifdef HAVE_TOUCHSCREEN 339 rb->touchscreen_set_mode(rb->global_settings->touch_mode); 340#endif 341 int ret = file && determine_props_type(file); 342 if (!ret) 343 { 344 rb->splashf(0, "Could not find: %s", file ?: "(NULL)"); 345 rb->action_userabort(TIMEOUT_BLOCK); 346 return PLUGIN_OK; 347 } 348 349 if (props_type == PROPS_MUL_ID3) 350 ret = assemble_track_info(NULL, NULL); 351 else if (props_type != PROPS_ID3) 352 { 353 setup_properties_list(&stats); /* Show title during dir scan */ 354 if (props_type == PROPS_DIR) 355 ret = dir_properties(file, &stats); 356 } 357 if (!ret) 358 { 359 if (!stats.canceled) 360 { 361 rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); /* TODO: describe error */ 362 rb->action_userabort(TIMEOUT_BLOCK); 363 } 364 } 365 else if (props_type == PROPS_ID3) 366 /* Track Info for single file */ 367 ret = rb->browse_id3(&id3, 0, 0, &tm, 1, &view_text); 368 else if (props_type == PROPS_MUL_ID3) 369 /* database tracks */ 370 ret = rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count, &view_text); 371 else if ((ret = browse_file_or_dir(&stats)) < 0) 372 ret = assemble_track_info(file, &stats) ? 373 /* playlist or folder tracks */ 374 rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count, &view_text) : 375 (stats.canceled ? 0 : -1); 376 377 return ret == -1 ? PLUGIN_ERROR : ret == 1 ? PLUGIN_USB_CONNECTED : PLUGIN_OK; 378}