From 3d7d1d4d5b0289c6357e02be70efe2958ae55e0a Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Tue, 28 May 2024 00:01:57 +0200 Subject: plugins: properties: show track info for whole playlist Track Info can now be displayed for the set of all tracks contained in a playlist. This lets you calculate a playlist's length, for example, even if it is not currently playing. This functionality can be accessed from the existing "Properties" screen for a selected playlist file. A line has been added at the very bottom to show Track Info. Change-Id: I311532b7cfa9e29d46c0cd5623ba4c06c1dd5b5f --- apps/misc.c | 23 ++++++++++ apps/misc.h | 2 + apps/playlist.c | 115 +++++++++++++++++++++++++++++----------------- apps/playlist.h | 3 ++ apps/plugin.c | 1 + apps/plugin.h | 3 ++ apps/plugins/properties.c | 107 +++++++++++++++++++++++++++--------------- apps/tagtree.c | 24 ---------- 8 files changed, 175 insertions(+), 103 deletions(-) diff --git a/apps/misc.c b/apps/misc.c index 7ba229a6f3..b8e69d09c6 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -200,6 +200,29 @@ bool warn_on_pl_erase(void) return true; } +bool show_search_progress(bool init, int count) +{ + static int last_tick = 0; + + /* Don't show splashes for 1/2 second after starting search */ + if (init) + { + last_tick = current_tick + HZ/2; + return true; + } + + /* Update progress every 1/10 of a second */ + if (TIME_AFTER(current_tick, last_tick + HZ/10)) + { + splashf(0, str(LANG_PLAYLIST_SEARCH_MSG), count, str(LANG_OFF_ABORT)); + if (action_userabort(TIMEOUT_NOBLOCK)) + return false; + last_tick = current_tick; + yield(); + } + + return true; +} /* Performance optimized version of the read_line() (see below) function. */ int fast_readline(int fd, char *buf, int buf_size, void *parameters, diff --git a/apps/misc.h b/apps/misc.h index 28a982d1da..c6485db4ff 100644 --- a/apps/misc.h +++ b/apps/misc.h @@ -110,6 +110,8 @@ void talk_timedate(void); * returns true if the playlist should be replaced */ bool warn_on_pl_erase(void); +bool show_search_progress(bool init, int count); + /* Read (up to) a line of text from fd into buffer and return number of bytes * read (which may be larger than the number of bytes stored in buffer). If * an error occurs, -1 is returned (and buffer contains whatever could be diff --git a/apps/playlist.c b/apps/playlist.c index 5a3ada8efc..3600918eb3 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -2498,74 +2498,105 @@ int playlist_insert_directory(struct playlist_info* playlist, } /* - * Insert all tracks from specified playlist into dynamic playlist. + * If action_cb is *not* NULL, it will be called for every track contained + * in the playlist specified by filename. If action_cb is NULL, you must + * instead provide a playlist insert context to use for adding each track + * into a dynamic playlist. */ -int playlist_insert_playlist(struct playlist_info* playlist, const char *filename, - int position, bool queue) +bool playlist_entries_iterate(const char *filename, + struct playlist_insert_context *pl_context, + bool (*action_cb)(const char *file_name)) { - int fd = -1; + int fd = -1, i = 0; + bool ret = false; int max; char *dir; char temp_buf[MAX_PATH+1]; char trackname[MAX_PATH+1]; - int result = -1; bool utf8 = is_m3u8_name(filename); - struct playlist_insert_context pl_context; cpu_boost(true); - if (playlist_insert_context_create(playlist, &pl_context, position, queue, true) >= 0) + fd = open_utf8(filename, O_RDONLY); + if (fd < 0) { - fd = open_utf8(filename, O_RDONLY); - if (fd < 0) - { - notify_access_error(); - goto out; - } + notify_access_error(); + goto out; + } - /* we need the directory name for formatting purposes */ - size_t dirlen = path_dirname(filename, (const char **)&dir); - //dir = strmemdupa(dir, dirlen); + /* we need the directory name for formatting purposes */ + size_t dirlen = path_dirname(filename, (const char **)&dir); + //dir = strmemdupa(dir, dirlen); - while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0) - { - /* user abort */ - if (action_userabort(TIMEOUT_NOBLOCK)) - break; - if (temp_buf[0] != '#' && temp_buf[0] != '\0') + if (action_cb) + show_search_progress(true, 0); + + while ((max = read_line(fd, temp_buf, sizeof(temp_buf))) > 0) + { + /* user abort */ + if (!action_cb && action_userabort(TIMEOUT_NOBLOCK)) + break; + + if (temp_buf[0] != '#' && temp_buf[0] != '\0') + { + i++; + if (!utf8) { - if (!utf8) - { - /* Use trackname as a temporay buffer. Note that trackname must - * be as large as temp_buf. - */ - max = convert_m3u_name(temp_buf, max, sizeof(temp_buf), trackname); - } + /* Use trackname as a temporay buffer. Note that trackname must + * be as large as temp_buf. + */ + max = convert_m3u_name(temp_buf, max, sizeof(temp_buf), trackname); + } - /* we need to format so that relative paths are correctly - handled */ - if (format_track_path(trackname, temp_buf, - sizeof(trackname), dir, dirlen) < 0) - { - goto out; - } + /* we need to format so that relative paths are correctly + handled */ + if (format_track_path(trackname, temp_buf, + sizeof(trackname), dir, dirlen) < 0) + { + goto out; + } - if (playlist_insert_context_add(&pl_context, trackname) < 0) + if (action_cb) + { + if (!action_cb(trackname)) goto out; + else if (!show_search_progress(false, i)) + break; } - - /* let the other threads work */ - yield(); + else if (playlist_insert_context_add(pl_context, trackname) < 0) + goto out; } - } - result = 0; + /* let the other threads work */ + yield(); + } + ret = true; out: close(fd); + cpu_boost(false); + return ret; +} + +/* + * Insert all tracks from specified playlist into dynamic playlist. + */ +int playlist_insert_playlist(struct playlist_info* playlist, const char *filename, + int position, bool queue) +{ + + int result = -1; + + struct playlist_insert_context pl_context; + cpu_boost(true); + + if (playlist_insert_context_create(playlist, &pl_context, position, queue, true) >= 0 + && playlist_entries_iterate(filename, &pl_context, NULL)) + result = 0; + cpu_boost(false); playlist_insert_context_release(&pl_context); return result; diff --git a/apps/playlist.h b/apps/playlist.h index 4d814c7523..f7426df9a3 100644 --- a/apps/playlist.h +++ b/apps/playlist.h @@ -159,6 +159,9 @@ int playlist_insert_directory(struct playlist_info* playlist, bool recurse); int playlist_insert_playlist(struct playlist_info* playlist, const char *filename, int position, bool queue); +bool playlist_entries_iterate(const char *filename, + struct playlist_insert_context *pl_context, + bool (*action_cb)(const char *file_name)); void playlist_skip_entry(struct playlist_info *playlist, int steps); int playlist_delete(struct playlist_info* playlist, int index); int playlist_move(struct playlist_info* playlist, int index, int new_index); diff --git a/apps/plugin.c b/apps/plugin.c index 931b8f1fd4..7934cc1754 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -838,6 +838,7 @@ static const struct plugin_api rockbox_api = { playlist_get_first_index, playlist_get_display_index, filetype_get_plugin, + playlist_entries_iterate, }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index df519f28cf..e9dedf02a1 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -975,6 +975,9 @@ struct plugin_api { int (*playlist_get_first_index)(const struct playlist_info* playlist); int (*playlist_get_display_index)(void); char* (*filetype_get_plugin)(int attr, char *buffer, size_t buffer_len); + bool (*playlist_entries_iterate)(const char *filename, + struct playlist_insert_context *pl_context, + bool (*action_cb)(const char *file_name)); }; /* plugin header */ diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c index ce3c03694c..32bc8b9150 100644 --- a/apps/plugins/properties.c +++ b/apps/plugins/properties.c @@ -19,10 +19,7 @@ * ****************************************************************************/ #include "plugin.h" - -#ifdef HAVE_TAGCACHE #include "lib/mul_id3.h" -#endif #if !defined(ARRAY_SIZE) #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) @@ -38,6 +35,7 @@ struct dir_stats { enum props_types { PROPS_FILE = 0, + PROPS_PLAYLIST, PROPS_ID3, PROPS_MUL_ID3, PROPS_DIR @@ -46,10 +44,8 @@ enum props_types { static int props_type = PROPS_FILE; static struct mp3entry id3; -#ifdef HAVE_TAGCACHE static int mul_id3_count; static int skipped_count; -#endif static char str_filename[MAX_PATH]; static char str_dirname[MAX_PATH]; @@ -64,6 +60,7 @@ static int32_t size_unit; static struct tm tm; #define NUM_FILE_PROPERTIES 5 +#define NUM_PLAYLIST_PROPERTIES 1 + NUM_FILE_PROPERTIES static const unsigned char* const props_file[] = { ID2P(LANG_PROPERTIES_PATH), str_dirname, @@ -71,6 +68,7 @@ static const unsigned char* const props_file[] = ID2P(LANG_PROPERTIES_SIZE), str_size, ID2P(LANG_PROPERTIES_DATE), str_date, ID2P(LANG_PROPERTIES_TIME), str_time, + ID2P(LANG_MENU_SHOW_ID3_INFO), "...", }; #define NUM_DIR_PROPERTIES 4 @@ -120,7 +118,8 @@ static bool file_properties(const char* selected_file) log = human_size_log((unsigned long)info.size); nsize = ((unsigned long)info.size) >> (log*10); size_unit = units[log]; - rb->snprintf(str_size, sizeof str_size, "%lu %s", nsize, rb->str(size_unit)); + rb->snprintf(str_size, sizeof str_size, "%lu %s", + nsize, rb->str(size_unit)); rb->gmtime_r(&info.mtime, &tm); rb->snprintf(str_date, sizeof str_date, "%04d/%02d/%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); @@ -249,7 +248,7 @@ static const char * get_props(int selected_item, void* data, if (PROPS_DIR == props_type) rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_dir)) ? "ERROR" : (char *) p2str(props_dir[selected_item]), buffer_len); - else if (PROPS_FILE == props_type) + else rb->strlcpy(buffer, selected_item >= (int)(ARRAY_SIZE(props_file)) ? "ERROR" : (char *) p2str(props_file[selected_item]), buffer_len); @@ -259,7 +258,8 @@ static const char * get_props(int selected_item, void* data, static int speak_property_selection(int selected_item, void *data) { struct dir_stats *stats = data; - int32_t id = P2ID((props_type == PROPS_DIR ? props_dir : props_file)[selected_item]); + int32_t id = P2ID((props_type == PROPS_DIR ? + props_dir : props_file)[selected_item]); rb->talk_id(id, false); switch (id) { @@ -326,7 +326,8 @@ static int browse_file_or_dir(struct dir_stats *stats) rb->gui_synclist_set_voice_callback(&properties_lists, speak_property_selection); rb->gui_synclist_set_nb_items(&properties_lists, 2 * (props_type == PROPS_FILE ? NUM_FILE_PROPERTIES : - NUM_DIR_PROPERTIES)); + props_type == PROPS_PLAYLIST ? + NUM_PLAYLIST_PROPERTIES : NUM_DIR_PROPERTIES)); rb->gui_synclist_select_item(&properties_lists, 0); rb->gui_synclist_draw(&properties_lists); rb->gui_synclist_speak_item(&properties_lists); @@ -339,11 +340,17 @@ static int browse_file_or_dir(struct dir_stats *stats) continue; switch(button) { + case ACTION_STD_OK: + if (props_type == PROPS_PLAYLIST && + rb->gui_synclist_get_sel_pos(&properties_lists) + == ARRAY_SIZE(props_file) - 2) + return -1; + break; case ACTION_STD_CANCEL: - return false; + return 0; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) - return true; + return 1; break; } } @@ -372,7 +379,6 @@ static bool determine_file_or_dir(void) return false; } -#ifdef HAVE_TAGCACHE bool mul_id3_add(const char *file_name) { if (!file_name || rb->mp3info(&id3, file_name)) @@ -385,10 +391,37 @@ bool mul_id3_add(const char *file_name) return true; } + +static bool has_pl_extension(const char* filename) +{ + char *dot = rb->strrchr(filename, '.'); + return (dot && (!rb->strcasecmp(dot, ".m3u") || !rb->strcasecmp(dot, ".m3u8"))); +} + +/* Assemble track info from a database table or the contents of a playlist file */ +static bool assemble_track_info(const char *filename) +{ + props_type = PROPS_MUL_ID3; + mul_id3_count = skipped_count = 0; + + if ( (filename && !rb->playlist_entries_iterate(filename, NULL, &mul_id3_add)) +#ifdef HAVE_TAGCACHE + || (!filename && !rb->tagtree_subentries_do_action(&mul_id3_add)) #endif + || mul_id3_count == 0) + return false; + else if (mul_id3_count > 1) /* otherwise, the retrieved id3 can be used as-is */ + finalize_id3(&id3); + + if (skipped_count > 0) + rb->splashf(HZ*2, "Skipped %d", skipped_count); + + return true; +} enum plugin_status plugin_start(const void* parameter) { + int ret; static struct dir_stats stats = { .len = MAX_PATH, @@ -405,23 +438,7 @@ enum plugin_status plugin_start(const void* parameter) rb->touchscreen_set_mode(rb->global_settings->touch_mode); #endif -#ifdef HAVE_TAGCACHE - if (!rb->strcmp(file, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER))) /* db table selected */ - { - props_type = PROPS_MUL_ID3; - mul_id3_count = skipped_count = 0; - - if (!rb->tagtree_subentries_do_action(&mul_id3_add) || mul_id3_count == 0) - return PLUGIN_ERROR; - else if (mul_id3_count > 1) /* otherwise, the retrieved id3 can be used as-is */ - finalize_id3(&id3); - - if (skipped_count > 0) - rb->splashf(HZ*2, "Skipped %d", skipped_count); - } - else -#endif - if (file[0] == '/') /* single track selected */ + if (file[0] == '/') /* single file selected */ { const char* file_name = rb->strrchr(file, '/') + 1; int dirlen = (file_name - file); @@ -437,8 +454,12 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_OK; } + if (props_type == PROPS_FILE && has_pl_extension(file)) + props_type = PROPS_PLAYLIST; + /* get the info depending on its_a_dir */ - if(!(props_type == PROPS_DIR ? dir_properties(file, &stats) : file_properties(file))) + if(!(props_type == PROPS_DIR ? + dir_properties(file, &stats) : file_properties(file))) { /* something went wrong (to do: tell user what it was (nesting,...) */ rb->splash(0, ID2P(LANG_PROPERTIES_FAIL)); @@ -446,20 +467,32 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_OK; } } - else + /* database table selected */ + else if (rb->strcmp(file, MAKE_ACT_STR(ACTIVITY_DATABASEBROWSER)) + || !assemble_track_info(NULL)) return PLUGIN_ERROR; FOR_NB_SCREENS(i) rb->viewportmanager_theme_enable(i, true, NULL); - bool usb = props_type == PROPS_ID3 ? rb->browse_id3(&id3, 0, 0, &tm, 1) : -#ifdef HAVE_TAGCACHE - props_type == PROPS_MUL_ID3 ? rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count) : -#endif - browse_file_or_dir(&stats); + if (props_type == PROPS_ID3) + ret = rb->browse_id3(&id3, 0, 0, &tm, 1); /* Track Info for single file */ + else if (props_type == PROPS_MUL_ID3) + ret = rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count); /* database tracks */ + else if ((ret = browse_file_or_dir(&stats)) < 0) + ret = assemble_track_info(file) ? /* playlist tracks */ + rb->browse_id3(&id3, 0, 0, NULL, mul_id3_count) : -1; FOR_NB_SCREENS(i) rb->viewportmanager_theme_undo(i, false); - return usb ? PLUGIN_USB_CONNECTED : PLUGIN_OK; + switch (ret) + { + case 1: + return PLUGIN_USB_CONNECTED; + case -1: + return PLUGIN_ERROR; + default: + return PLUGIN_OK; + } } diff --git a/apps/tagtree.c b/apps/tagtree.c index 3a875d6da6..be728dcc95 100644 --- a/apps/tagtree.c +++ b/apps/tagtree.c @@ -1312,30 +1312,6 @@ void tagtree_init(void) initialize_tagtree(); } -static bool show_search_progress(bool init, int count) -{ - static int last_tick = 0; - - /* Don't show splashes for 1/2 second after starting search */ - if (init) - { - last_tick = current_tick + HZ/2; - return true; - } - - /* Update progress every 1/10 of a second */ - if (TIME_AFTER(current_tick, last_tick + HZ/10)) - { - splashf(0, str(LANG_PLAYLIST_SEARCH_MSG), count, str(LANG_OFF_ABORT)); - if (action_userabort(TIMEOUT_NOBLOCK)) - return false; - last_tick = current_tick; - yield(); - } - - return true; -} - static int format_str(struct tagcache_search *tcs, struct display_format *fmt, char *buf, int buf_size) { -- cgit v1.2.3