From a513cee8222006d5f970b4495ae7d1f1b6facf9b Mon Sep 17 00:00:00 2001 From: Christian Soffke Date: Wed, 25 May 2022 20:11:29 +0200 Subject: [PATCH] PictureFlow: Add 'Track Info' for tracks or whole albums Context menu gains new option to view metadata for individual tracks or albums. Will display an album's length and total file size. Other fields are displayed only if they are identical across all tracks (except for the album year, which is determined by the highest value encountered). Change-Id: Ibc14cfaf2cb3d91b8d1cfbee05c6261db4975355 --- apps/plugin.c | 3 + apps/plugin.h | 5 +- apps/plugins/pictureflow/pictureflow.c | 258 +++++++++++++++++++++++-- apps/screens.c | 6 + manual/plugins/pictureflow.tex | 8 +- 5 files changed, 261 insertions(+), 19 deletions(-) diff --git a/apps/plugin.c b/apps/plugin.c index b017db017b..7c6e91424a 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -821,6 +821,9 @@ static const struct plugin_api rockbox_api = { /* new stuff at the end, sort into place next time the API gets incompatible */ + + splash_progress, + splash_progress_set_delay, }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index ed688eb753..768fc0ff27 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -157,7 +157,7 @@ int plugin_open(const char *plugin, const char *parameter); #define PLUGIN_MAGIC 0x526F634B /* RocK */ /* increase this every time the api struct changes */ -#define PLUGIN_API_VERSION 260 +#define PLUGIN_API_VERSION 261 /* update this to latest version if a change to the api struct breaks backwards compatibility (and please take the opportunity to sort in any @@ -945,6 +945,9 @@ struct plugin_api { #endif /* new stuff at the end, sort into place next time the API gets incompatible */ + + void (*splash_progress)(int current, int total, const char *fmt, ...) ATTRIBUTE_PRINTF(3, 4); + void (*splash_progress_set_delay)(long delay_ticks); }; /* plugin header */ diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c index 54497d8306..61d0b64e94 100644 --- a/apps/plugins/pictureflow/pictureflow.c +++ b/apps/plugins/pictureflow/pictureflow.c @@ -397,6 +397,26 @@ struct track_data { #endif }; +#if PF_PLAYBACK_CAPABLE +struct multiple_tracks_id3 { + unsigned long length; + unsigned long filesize; + unsigned long frequency; + unsigned int artist_hash; + unsigned int composer_hash; + unsigned int albumartist_hash; + unsigned int grouping_hash; + unsigned int comment_hash; + unsigned int album_hash; + unsigned int genre_hash; + unsigned int codectype; + unsigned int bitrate; + bool filesize_ovf; + bool length_ovf; + bool vbr; +}; +#endif + struct rect { int left; int right; @@ -558,6 +578,8 @@ static struct pf_index_t pf_idx; static struct pf_track_t pf_tracks; +static struct mp3entry id3; + void reset_track_list(void); static bool thread_is_running; @@ -2093,7 +2115,6 @@ static bool get_albumart_for_index_from_db(const int slide_index, char *buf, pf_idx.album_index[slide_index].artist_seek); if ( rb->tagcache_get_next(&tcs) ) { - struct mp3entry id3; int fd; #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) @@ -2218,6 +2239,9 @@ static unsigned int mfnv(char *str) const unsigned int p = 16777619; unsigned int hash = 0x811C9DC5; // 2166136261; + if (!str) + return 0; + while(*str) hash = (hash ^ *str++) * p; hash += hash << 13; @@ -4010,6 +4034,169 @@ static void select_prev_album(void) } #if PF_PLAYBACK_CAPABLE +static void collect_id3(struct multiple_tracks_id3 *mul_id3, bool is_first_track) +{ + if (is_first_track) + { + mul_id3->artist_hash = mfnv(id3.artist); + mul_id3->album_hash = mfnv(id3.album); + mul_id3->genre_hash = mfnv(id3.genre_string); + mul_id3->composer_hash = mfnv(id3.composer); + mul_id3->albumartist_hash = mfnv(id3.albumartist); + mul_id3->grouping_hash = mfnv(id3.grouping); + mul_id3->comment_hash = mfnv(id3.comment); + mul_id3->codectype = id3.codectype; + mul_id3->vbr = id3.vbr; + mul_id3->bitrate = id3.bitrate; + mul_id3->frequency = id3.frequency; + } + else + { + if (mul_id3->artist_hash && (mfnv(id3.artist) != mul_id3->artist_hash)) + mul_id3->artist_hash = 0; + if (mul_id3->album_hash && (mfnv(id3.album) != mul_id3->album_hash)) + mul_id3->album_hash = 0; + if (mul_id3->genre_hash && (mfnv(id3.genre_string) != mul_id3->genre_hash)) + mul_id3->genre_hash = 0; + if (mul_id3->composer_hash && (mfnv(id3.composer) != mul_id3->composer_hash)) + mul_id3->composer_hash = 0; + if (mul_id3->albumartist_hash && (mfnv(id3.albumartist) != + mul_id3->albumartist_hash)) + mul_id3->albumartist_hash = 0; + if (mul_id3->grouping_hash && (mfnv(id3.grouping) != mul_id3->grouping_hash)) + mul_id3->grouping_hash = 0; + if (mul_id3->comment_hash && (mfnv(id3.comment) != mul_id3->comment_hash)) + mul_id3->comment_hash = 0; + + if (mul_id3->codectype && (id3.codectype != mul_id3->codectype)) + mul_id3->codectype = AFMT_UNKNOWN; + if (mul_id3->bitrate && (id3.bitrate != mul_id3->bitrate || + id3.vbr != mul_id3->vbr)) + mul_id3->bitrate = 0; + if (mul_id3->frequency && (id3.frequency != mul_id3->frequency)) + mul_id3->frequency = 0; + } + + if (ULONG_MAX - mul_id3->length < id3.length) + { + mul_id3->length_ovf = true; + mul_id3->length = 0; + } + else if (!mul_id3->length_ovf) + mul_id3->length += id3.length; + + if (INT_MAX - mul_id3->filesize < id3.filesize) /* output_dyn_value expects int */ + { + mul_id3->filesize_ovf = true; + mul_id3->filesize = 0; + } + else if (!mul_id3->filesize_ovf) + mul_id3->filesize += id3.filesize; +} + + +static void write_id3_mul_tracks(struct multiple_tracks_id3 *mul_id3) +{ + id3.path[0] = '\0'; + id3.title = NULL; + if (!mul_id3->artist_hash) + id3.artist = NULL; + if (!mul_id3->album_hash) + id3.album = NULL; + if (!mul_id3->genre_hash) + id3.genre_string = NULL; + if (!mul_id3->composer_hash) + id3.composer = NULL; + if (!mul_id3->albumartist_hash) + id3.albumartist = NULL; + if (!mul_id3->grouping_hash) + id3.grouping = NULL; + if (!mul_id3->comment_hash) + id3.comment = NULL; + id3.disc_string = NULL; + id3.track_string = NULL; + id3.year_string = NULL; + id3.year = pf_idx.album_index[center_index].year; + id3.length = mul_id3->length; + id3.filesize = mul_id3->filesize; + id3.frequency = mul_id3->frequency; + id3.bitrate = mul_id3->bitrate; + id3.codectype = mul_id3->codectype; + id3.vbr = mul_id3->vbr; + id3.discnum = 0; + id3.tracknum = 0; + id3.track_level = 0; + id3.album_level = 0; +} + +static void init_mul_id3(struct multiple_tracks_id3 *mul_id3) +{ + mul_id3->artist_hash = 0; + mul_id3->album_hash = 0; + mul_id3->genre_hash = 0; + mul_id3->composer_hash = 0; + mul_id3->albumartist_hash = 0; + mul_id3->grouping_hash = 0; + mul_id3->comment_hash = 0; + mul_id3->codectype = 0; + mul_id3->vbr = false; + mul_id3->bitrate = 0; + mul_id3->frequency = 0; + mul_id3->length = 0; + mul_id3->filesize = 0; + mul_id3->length_ovf = false; + mul_id3->filesize_ovf = false; +} + +static int show_id3_info(const char *selected_file) +{ + int fd, i; + unsigned long last_tick; + const char *file_name; + bool id3_retrieval_successful; + bool is_multiple_tracks = insert_whole_album && pf_tracks.count > 1; + struct multiple_tracks_id3 mul_id3; + + init_mul_id3(&mul_id3); + + last_tick = *(rb->current_tick) + HZ/2; + rb->splash_progress_set_delay(HZ / 2); /* wait 1/2 sec before progress */ + i = 0; + do { + id3_retrieval_successful = false; + file_name = i == 0 ? selected_file : get_track_filename(i); + fd = rb->open(file_name, O_RDONLY); + if (fd >= 0) + { + if (rb->get_metadata(&id3, fd, file_name)) + id3_retrieval_successful = true; + rb->close(fd); + } + if (!id3_retrieval_successful) + return 0; + + if (is_multiple_tracks) + { + rb->splash_progress(i, pf_tracks.count, + "%s (%s)", rb->str(LANG_WAIT), rb->str(LANG_OFF_ABORT)); + if (TIME_AFTER(*(rb->current_tick), last_tick + HZ/4)) + { + if (rb->action_userabort(TIMEOUT_NOBLOCK)) + return 0; + last_tick = *(rb->current_tick); + } + + collect_id3(&mul_id3, i == 0); + rb->yield(); + } + } while (++i < pf_tracks.count && is_multiple_tracks); + + if (is_multiple_tracks) + write_id3_mul_tracks(&mul_id3); + + return rb->browse_id3(&id3, 0, 0, NULL) ? PLUGIN_USB_CONNECTED : 0; +} + static bool playlist_insert(int position, bool queue, bool create_new) { @@ -4061,14 +4248,8 @@ static bool track_list_ready(void) return true; } -/** - Brings up "Current Playlist" menu with first - track of selection. - Onplay menu code calls back playlist_insert for - adding all of the tracks. -*/ -static void show_current_playlist_menu(void) +static bool context_menu_ready(void) { #ifdef USEGSLIB grey_show(false); @@ -4080,13 +4261,23 @@ static void show_current_playlist_menu(void) #ifdef USEGSLIB grey_show(true); #endif - return; + return false; } +#if LCD_DEPTH > 1 +#ifdef USEGSLIB + rb->lcd_set_foreground(N_BRIGHT(0)); + rb->lcd_set_background(N_BRIGHT(255)); +#endif +#endif insert_whole_album = pf_state != pf_show_tracks; FOR_NB_SCREENS(i) rb->viewportmanager_theme_enable(i, true, NULL); - rb->onplay_show_playlist_menu(get_track_filename(pf_tracks.sel), - &playlist_insert); + + return true; +} + +static void context_menu_cleanup(void) +{ FOR_NB_SCREENS(i) rb->viewportmanager_theme_undo(i, false); if (insert_whole_album) @@ -4098,6 +4289,39 @@ static void show_current_playlist_menu(void) } +static int context_menu(void) +{ + char *file_name = get_track_filename(pf_tracks.sel); + + enum { + PF_CURRENT_PLAYLIST = 0, + PF_ID3_INFO + }; + MENUITEM_STRINGLIST(context_menu, ID2P(LANG_ONPLAY_MENU_TITLE), NULL, + ID2P(LANG_PLAYING_NEXT), + ID2P(LANG_MENU_SHOW_ID3_INFO)); + + while (1) { + switch (rb->do_menu(&context_menu, + NULL, NULL, false)) { + + case PF_CURRENT_PLAYLIST: + rb->onplay_show_playlist_menu(file_name, + &playlist_insert); + return 0; + case PF_ID3_INFO: + return show_id3_info(file_name); + case MENU_ATTACHED_USB: + return PLUGIN_USB_CONNECTED; + default: + return 0; + + } + } +} + + + /* * Puts selected album's tracks into a newly created playlist and starts playing */ @@ -4247,7 +4471,6 @@ static void set_initial_slide(const char* selected_file) pf_cfg.last_album); else { - static struct mp3entry id3; #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) if (rb->tagcache_fill_tags(&id3, selected_file)) set_current_slide(id3_get_index(&id3)); @@ -4601,8 +4824,8 @@ static int pictureflow_main(const char* selected_file) #if PF_PLAYBACK_CAPABLE case PF_CONTEXT: if (pf_state == pf_idle || pf_state == pf_scrolling || - pf_state == pf_show_tracks || pf_state == pf_cover_out) { - + pf_state == pf_show_tracks || pf_state == pf_cover_out) + { if ( pf_state == pf_scrolling) { set_current_slide(target); @@ -4611,7 +4834,12 @@ static int pictureflow_main(const char* selected_file) else if (pf_state == pf_cover_out) interrupt_cover_out_animation(); - show_current_playlist_menu(); + if (context_menu_ready()) + { + ret = context_menu(); + context_menu_cleanup(); + if ( ret != 0 ) return ret; + } } break; #endif diff --git a/apps/screens.c b/apps/screens.c index 91280a72f1..869d081498 100644 --- a/apps/screens.c +++ b/apps/screens.c @@ -622,6 +622,8 @@ static const char * id3_get_or_speak_info(int selected_item, void* data, talk_spell(val, true); break; case LANG_ID3_BITRATE: + if (!id3->bitrate) + return NULL; snprintf(buffer, buffer_len, "%d kbps%s", id3->bitrate, id3->vbr ? str(LANG_ID3_VBR) : (const unsigned char*) ""); val=buffer; @@ -633,6 +635,8 @@ static const char * id3_get_or_speak_info(int selected_item, void* data, } break; case LANG_ID3_FREQUENCY: + if (!id3->frequency) + return NULL; snprintf(buffer, buffer_len, "%ld Hz", id3->frequency); val=buffer; if(say_it) @@ -661,6 +665,8 @@ static const char * id3_get_or_speak_info(int selected_item, void* data, talk_spell(val, true); break; case LANG_FILESIZE: /* not LANG_ID3_FILESIZE because the string is shared */ + if (!id3->filesize) + return NULL; output_dyn_value(buffer, buffer_len, id3->filesize, byte_units, 4, true); val=buffer; if(say_it && val) diff --git a/manual/plugins/pictureflow.tex b/manual/plugins/pictureflow.tex index 5b4e80e107..5aee31c004 100644 --- a/manual/plugins/pictureflow.tex +++ b/manual/plugins/pictureflow.tex @@ -1,9 +1,11 @@ \subsection{PictureFlow} \screenshot{plugins/images/ss-pictureflow}{PictureFlow}{img:pictureflow} PictureFlow is a visual browser for your albums. After you've selected something to play, -PictureFlow will continue running by default, or can optionally show the WPS. Using the -context menu, albums or songs can be added to the dynamic playlist directly from -PictureFlow (see \reference{ref:playingnext_submenu}). +PictureFlow will continue running by default, or can optionally show the WPS. Using +the context menu, songs can be added to the dynamic playlist directly from PictureFlow +(see \reference{ref:playingnext_submenu}). +Various metadata, such as format, length or year of an album or its songs can also be +displayed. \subsubsection{Sort Options}