summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorPaul Sauro <olsroparadise@proton.me>2024-09-07 13:55:45 +0200
committerWilliam Wilgus <me.theuser@yahoo.com>2024-09-09 18:21:38 -0400
commitf7db73097a30a9cd5ec6415fcc197749ee1bc559 (patch)
tree116924fcc95a4400ead473ce69652192a405eff7 /apps
parenta7cfee640dcaa00e3717bd2ef4bd54489f491960 (diff)
downloadrockbox-f7db73097a30a9cd5ec6415fcc197749ee1bc559.tar.gz
rockbox-f7db73097a30a9cd5ec6415fcc197749ee1bc559.zip
Playlist viewer: Add new options to allow formatting using tags
Offer new options to show elegantly your entries in any playlist/dynamic playlist viewer. This is especially important if you dual boot an iPod with Stock OS and want to sync with iTunes; with this very popular setup, file names are obfuscated which results in any Rockbox playlist viewer difficult to enjoy, and it was a long standing issue reported by several Rockbox users over the years. The only way to show the title was to open a contextual menu on each song to get infos about the selected song, which is a very long and anti-ergonomic process to understand what is on your current playlist/randomized playlist. The idea of this patch is to provide new alternatives that the user can select. I personally selected the Title & Album view which provides excellent readability. This patch was built with performance in mind using lazy loading to load one by one the tags then cache a string and use the little cache as much as possible to make scrolling in the same area as smooth as possible. Performance remains very acceptable even on an iPod 4G with its original hard drive. Using a real compact flash with my iPod Mini 2G reduces the latency even more. Those new options are disabled by default because they impact noticeably the scrolling performance and are less relevant if your files are decently properly named. Unfortunately, the search feature in a playlist will need to continue to use the raw filename because reading the tags for a whole playlist is a performance disaster. This works decently while viewing just because I made a code that load those one by one as much as possible. I focused also on testing that the opening of the playlist viewer UI remained fast, and loading one by one with lazy loading allows to get very little overhead in this regard. Change-Id: I00d9c802e29f8372447813b035bbae207a016467
Diffstat (limited to 'apps')
-rw-r--r--apps/lang/english.lang28
-rw-r--r--apps/lang/francais.lang28
-rw-r--r--apps/playlist.h2
-rw-r--r--apps/playlist_viewer.c205
-rw-r--r--apps/settings.h7
-rw-r--r--apps/settings_list.c8
6 files changed, 223 insertions, 55 deletions
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index c518d3573c..1ee1ffac0f 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -2032,6 +2032,34 @@
2032 </voice> 2032 </voice>
2033</phrase> 2033</phrase>
2034<phrase> 2034<phrase>
2035 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
2036 desc: track display options
2037 user: core
2038 <source>
2039 *: "Title & Album from ID3 tags"
2040 </source>
2041 <dest>
2042 *: "Title & Album from ID3 tags"
2043 </dest>
2044 <voice>
2045 *: "Title and Album from tags"
2046 </voice>
2047</phrase>
2048<phrase>
2049 id: LANG_DISPLAY_TITLE_FROMTAGS
2050 desc: track display options
2051 user: core
2052 <source>
2053 *: "Title from ID3 tags"
2054 </source>
2055 <dest>
2056 *: "Title from ID3 tags"
2057 </dest>
2058 <voice>
2059 *: "Title from tags"
2060 </voice>
2061</phrase>
2062<phrase>
2035 id: LANG_BUILDING_DATABASE 2063 id: LANG_BUILDING_DATABASE
2036 desc: splash database building progress 2064 desc: splash database building progress
2037 user: core 2065 user: core
diff --git a/apps/lang/francais.lang b/apps/lang/francais.lang
index 518b48fb96..b8883d4997 100644
--- a/apps/lang/francais.lang
+++ b/apps/lang/francais.lang
@@ -2005,6 +2005,34 @@
2005 </voice> 2005 </voice>
2006</phrase> 2006</phrase>
2007<phrase> 2007<phrase>
2008 id: LANG_DISPLAY_TITLEALBUM_FROMTAGS
2009 desc: track display options
2010 user: core
2011 <source>
2012 *: "Title & Album from ID3 tags"
2013 </source>
2014 <dest>
2015 *: "Titre & Album depuis les tags ID3"
2016 </dest>
2017 <voice>
2018 *: "Titre et Album depuis les tags"
2019 </voice>
2020</phrase>
2021<phrase>
2022 id: LANG_DISPLAY_TITLE_FROMTAGS
2023 desc: track display options
2024 user: core
2025 <source>
2026 *: "Title from ID3 tags"
2027 </source>
2028 <dest>
2029 *: "Titre depuis les tags ID3"
2030 </dest>
2031 <voice>
2032 *: "Titre depuis les tags"
2033 </voice>
2034</phrase>
2035<phrase>
2008 id: LANG_BUILDING_DATABASE 2036 id: LANG_BUILDING_DATABASE
2009 desc: splash database building progress 2037 desc: splash database building progress
2010 user: core 2038 user: core
diff --git a/apps/playlist.h b/apps/playlist.h
index f7426df9a3..2fb1ce100e 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -33,6 +33,8 @@
33#define PLAYLIST_ATTR_QUEUED 0x01 33#define PLAYLIST_ATTR_QUEUED 0x01
34#define PLAYLIST_ATTR_INSERTED 0x02 34#define PLAYLIST_ATTR_INSERTED 0x02
35#define PLAYLIST_ATTR_SKIPPED 0x04 35#define PLAYLIST_ATTR_SKIPPED 0x04
36#define PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED 0x08
37#define PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED 0x10
36 38
37#define PLAYLIST_DISPLAY_COUNT 10 39#define PLAYLIST_DISPLAY_COUNT 10
38 40
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index 5bf547a3fc..d556f3b557 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -50,6 +50,7 @@
50#include "playlist_menu.h" 50#include "playlist_menu.h"
51#include "menus/exported_menus.h" 51#include "menus/exported_menus.h"
52#include "yesno.h" 52#include "yesno.h"
53#include "playback.h"
53 54
54/* Maximum number of tracks we can have loaded at one time */ 55/* Maximum number of tracks we can have loaded at one time */
55#define MAX_PLAYLIST_ENTRIES 200 56#define MAX_PLAYLIST_ENTRIES 200
@@ -142,7 +143,7 @@ static bool playlist_viewer_init(struct playlist_viewer * viewer,
142 const char* filename, bool reload, 143 const char* filename, bool reload,
143 int *most_recent_selection); 144 int *most_recent_selection);
144 145
145static void format_line(const struct playlist_entry* track, char* str, 146static void format_line(struct playlist_entry* track, char* str,
146 int len); 147 int len);
147 148
148static bool update_playlist(bool force); 149static bool update_playlist(bool force);
@@ -159,6 +160,27 @@ static void playlist_buffer_init(struct playlist_buffer *pb, char *names_buffer,
159 pb->num_loaded = 0; 160 pb->num_loaded = 0;
160} 161}
161 162
163static int playlist_buffer_get_index(struct playlist_buffer *pb, int index)
164{
165 int buffer_index;
166 if (pb->direction == FORWARD)
167 {
168 if (index >= pb->first_index)
169 buffer_index = index-pb->first_index;
170 else /* rotation : track0 in buffer + requested track */
171 buffer_index = viewer.num_tracks-pb->first_index+index;
172 }
173 else
174 {
175 if (index <= pb->first_index)
176 buffer_index = pb->first_index-index;
177 else /* rotation : track0 in buffer + dist from the last track
178 to the requested track (num_tracks-requested track) */
179 buffer_index = pb->first_index+viewer.num_tracks-index;
180 }
181 return buffer_index;
182}
183
162/* 184/*
163 * Loads the entries following 'index' in the playlist buffer 185 * Loads the entries following 'index' in the playlist buffer
164 */ 186 */
@@ -227,6 +249,25 @@ static void playlist_buffer_load_entries_screen(struct playlist_buffer * pb,
227 playlist_buffer_load_entries(pb, start, direction); 249 playlist_buffer_load_entries(pb, start, direction);
228} 250}
229 251
252static bool retrieve_id3_tags(const int index, const char* name, struct mp3entry *id3, int flags)
253{
254 bool id3_retrieval_successful = false;
255
256 if (!viewer.playlist &&
257 (audio_status() & AUDIO_STATUS_PLAY) &&
258 (playlist_get_resume_info(&viewer.current_playing_track) == index))
259 {
260 copy_mp3entry(id3, audio_current_track()); /* retrieve id3 from RAM */
261 id3_retrieval_successful = true;
262 }
263 else
264 {
265 /* Read from disk, the database, doesn't store frequency, file size or codec (g4470) ChrisS*/
266 id3_retrieval_successful = get_metadata_ex(id3, -1, name, flags);
267 }
268 return id3_retrieval_successful;
269}
270
230static int playlist_entry_load(struct playlist_entry *entry, int index, 271static int playlist_entry_load(struct playlist_entry *entry, int index,
231 char* name_buffer, int remaining_size) 272 char* name_buffer, int remaining_size)
232{ 273{
@@ -242,6 +283,13 @@ static int playlist_entry_load(struct playlist_entry *entry, int index,
242 283
243 len = strlcpy(name_buffer, info.filename, remaining_size) + 1; 284 len = strlcpy(name_buffer, info.filename, remaining_size) + 1;
244 285
286 if (global_settings.playlist_viewer_track_display >
287 PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH && len <= remaining_size)
288 {
289 /* Allocate space for the id3viewc if the option is enabled */
290 len += MAX_PATH + 1;
291 }
292
245 if (len <= remaining_size) 293 if (len <= remaining_size)
246 { 294 {
247 entry->name = name_buffer; 295 entry->name = name_buffer;
@@ -253,27 +301,6 @@ static int playlist_entry_load(struct playlist_entry *entry, int index,
253 return -1; 301 return -1;
254} 302}
255 303
256static int playlist_buffer_get_index(struct playlist_buffer *pb, int index)
257{
258 int buffer_index;
259 if (pb->direction == FORWARD)
260 {
261 if (index >= pb->first_index)
262 buffer_index = index-pb->first_index;
263 else /* rotation : track0 in buffer + requested track */
264 buffer_index = viewer.num_tracks-pb->first_index+index;
265 }
266 else
267 {
268 if (index <= pb->first_index)
269 buffer_index = pb->first_index-index;
270 else /* rotation : track0 in buffer + dist from the last track
271 to the requested track (num_tracks-requested track) */
272 buffer_index = pb->first_index+viewer.num_tracks-index;
273 }
274 return buffer_index;
275}
276
277#define distance(a, b) \ 304#define distance(a, b) \
278 a>b? (a) - (b) : (b) - (a) 305 a>b? (a) - (b) : (b) - (a)
279static bool playlist_buffer_needs_reload(struct playlist_buffer* pb, 306static bool playlist_buffer_needs_reload(struct playlist_buffer* pb,
@@ -440,7 +467,9 @@ static void format_name(char* dest, const char* src, size_t bufsz)
440{ 467{
441 switch (global_settings.playlist_viewer_track_display) 468 switch (global_settings.playlist_viewer_track_display)
442 { 469 {
443 case 0: 470 case PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME:
471 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM: /* If loading from tags failed, only display the file name */
472 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE: /* If loading from tags failed, only display the file name */
444 default: 473 default:
445 { 474 {
446 /* Only display the filename */ 475 /* Only display the filename */
@@ -450,7 +479,7 @@ static void format_name(char* dest, const char* src, size_t bufsz)
450 strrsplt(dest, '.'); 479 strrsplt(dest, '.');
451 break; 480 break;
452 } 481 }
453 case 1: 482 case PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH:
454 /* Full path */ 483 /* Full path */
455 strlcpy(dest, src, bufsz); 484 strlcpy(dest, src, bufsz);
456 break; 485 break;
@@ -458,22 +487,99 @@ static void format_name(char* dest, const char* src, size_t bufsz)
458} 487}
459 488
460/* Format display line */ 489/* Format display line */
461static void format_line(const struct playlist_entry* track, char* str, 490static void format_line(struct playlist_entry* track, char* str,
462 int len) 491 int len)
463{ 492{
464 char name[MAX_PATH]; 493 char *id3viewc = NULL;
465 char *skipped = ""; 494 char *skipped = "";
466 format_name(name, track->name, sizeof(name));
467
468 if (track->attr & PLAYLIST_ATTR_SKIPPED) 495 if (track->attr & PLAYLIST_ATTR_SKIPPED)
469 skipped = "(ERR) "; 496 skipped = "(ERR) ";
470 497 if (!(track->attr & PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED) &&
471 if (global_settings.playlist_viewer_indices) 498 (global_settings.playlist_viewer_track_display ==
472 /* Display playlist index */ 499 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM ||
473 snprintf(str, len, "%d. %s%s", track->display_index, skipped, name); 500 global_settings.playlist_viewer_track_display ==
501 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE
502 ))
503 {
504 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_ATTEMPTED;
505 struct mp3entry id3;
506 bool retrieve_success = retrieve_id3_tags(track->index, track->name,
507 &id3, METADATA_EXCLUDE_ID3_PATH);
508 if (retrieve_success)
509 {
510 if (!id3viewc)
511 {
512 id3viewc = track->name + strlen(track->name) + 1;
513 }
514 struct mp3entry * pid3 = &id3;
515 id3viewc[0] = '\0';
516 if (global_settings.playlist_viewer_track_display ==
517 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM)
518 {
519 /* Title & Album */
520 if (pid3->title && pid3->title[0] != '\0')
521 {
522 char* cur_str = id3viewc;
523 int title_len = strlen(pid3->title);
524 int rem_space = MAX_PATH;
525 for (int i = 0; i < title_len && rem_space > 0; i++)
526 {
527 cur_str[0] = pid3->title[i];
528 cur_str++;
529 rem_space--;
530 }
531 if (rem_space > 10)
532 {
533 cur_str[0] = (char) ' ';
534 cur_str[1] = (char) '-';
535 cur_str[2] = (char) ' ';
536 cur_str += 3;
537 rem_space -= 3;
538 cur_str = strmemccpy(cur_str, pid3->album && pid3->album[0] != '\0' ?
539 pid3->album : (char*) str(LANG_TAGNAVI_UNTAGGED), rem_space);
540 if (cur_str)
541 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED;
542 }
543 }
544 }
545 else if (global_settings.playlist_viewer_track_display ==
546 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE)
547 {
548 /* Just the title */
549 if (pid3->title && pid3->title[0] != '\0' &&
550 strmemccpy(id3viewc, pid3->title, MAX_PATH)
551 )
552 track->attr |= PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED;
553 }
554 /* Yield to reduce as much as possible the perceived UI lag,
555 because retrieving id3 tags is an expensive operation */
556 yield();
557 }
558 }
559
560 if (!(track->attr & PLAYLIST_ATTR_RETRIEVE_ID3_SUCCEEDED))
561 {
562 /* Simply use a formatted file name */
563 char name[MAX_PATH];
564 format_name(name, track->name, sizeof(name));
565 if (global_settings.playlist_viewer_indices)
566 /* Display playlist index */
567 snprintf(str, len, "%d. %s%s", track->display_index, skipped, name);
568 else
569 snprintf(str, len, "%s%s", skipped, name);
570 }
474 else 571 else
475 snprintf(str, len, "%s%s", skipped, name); 572 {
476 573 if (!id3viewc)
574 {
575 id3viewc = track->name + strlen(track->name) + 1;
576 }
577 if (global_settings.playlist_viewer_indices)
578 /* Display playlist index */
579 snprintf(str, len, "%d. %s%s", track->display_index, skipped, id3viewc);
580 else
581 snprintf(str, len, "%s%s", skipped, id3viewc);
582 }
477} 583}
478 584
479/* Update playlist in case something has changed or forced */ 585/* Update playlist in case something has changed or forced */
@@ -512,20 +618,7 @@ static bool update_playlist(bool force)
512static enum pv_onplay_result show_track_info(const struct playlist_entry *current_track) 618static enum pv_onplay_result show_track_info(const struct playlist_entry *current_track)
513{ 619{
514 struct mp3entry id3; 620 struct mp3entry id3;
515 bool id3_retrieval_successful = false; 621 bool id3_retrieval_successful = retrieve_id3_tags(current_track->index, current_track->name, &id3, 0);
516
517 if (!viewer.playlist &&
518 (audio_status() & AUDIO_STATUS_PLAY) &&
519 (playlist_get_resume_info(&viewer.current_playing_track) == current_track->index))
520 {
521 copy_mp3entry(&id3, audio_current_track()); /* retrieve id3 from RAM */
522 id3_retrieval_successful = true;
523 }
524 else
525 {
526 /* Read from disk, the database, doesn't store frequency, file size or codec (g4470) ChrisS*/
527 id3_retrieval_successful = get_metadata(&id3, -1, current_track->name);
528 }
529 622
530 return id3_retrieval_successful && 623 return id3_retrieval_successful &&
531 browse_id3_ex(&id3, viewer.playlist, current_track->display_index, 624 browse_id3_ex(&id3, viewer.playlist, current_track->display_index,
@@ -790,11 +883,17 @@ static int playlist_callback_voice(int selected_item, void *data)
790 883
791 switch(global_settings.playlist_viewer_track_display) 884 switch(global_settings.playlist_viewer_track_display)
792 { 885 {
793 case 1: /*full path*/ 886 case PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH:
887 /*full path*/
794 talk_fullpath(track->name, true); 888 talk_fullpath(track->name, true);
795 break; 889 break;
796 default: 890 default:
797 case 0: /*filename only*/ 891 case PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME:
892 /*filename only*/
893 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM:
894 /* If loading from tags failed, only talk the file name */
895 case PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE:
896 /* If loading from tags failed, only talk the file name */
798 talk_file_or_spell(NULL, track->name, NULL, true); 897 talk_file_or_spell(NULL, track->name, NULL, true);
799 break; 898 break;
800 } 899 }
@@ -1154,7 +1253,8 @@ static int say_search_item(int selected_item, void *data)
1154{ 1253{
1155 struct playlist_search_data *s_data = data; 1254 struct playlist_search_data *s_data = data;
1156 playlist_get_track_info(viewer.playlist, s_data->found_indicies[selected_item], s_data->track); 1255 playlist_get_track_info(viewer.playlist, s_data->found_indicies[selected_item], s_data->track);
1157 if(global_settings.playlist_viewer_track_display == 1) /* full path*/ 1256 if(global_settings.playlist_viewer_track_display == PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH)
1257 /* full path*/
1158 talk_fullpath(s_data->track->filename, false); 1258 talk_fullpath(s_data->track->filename, false);
1159 else talk_file_or_spell(NULL, s_data->track->filename, NULL, false); 1259 else talk_file_or_spell(NULL, s_data->track->filename, NULL, false);
1160 return 0; 1260 return 0;
@@ -1197,7 +1297,8 @@ bool search_playlist(void)
1197 1297
1198 playlist_get_track_info(viewer.playlist, i, &track); 1298 playlist_get_track_info(viewer.playlist, i, &track);
1199 const char *trackname = track.filename; 1299 const char *trackname = track.filename;
1200 if (track_display == 0) /* if we only display filename only search filename */ 1300 if (track_display != PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH)
1301 /* if we only display filename only search filename */
1201 trackname = strrchr(track.filename, '/'); 1302 trackname = strrchr(track.filename, '/');
1202 1303
1203 if (trackname && strcasestr(trackname, search_str)) 1304 if (trackname && strcasestr(trackname, search_str))
diff --git a/apps/settings.h b/apps/settings.h
index 1343538b0b..a9a9f647e3 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -78,6 +78,13 @@ enum
78 TRIG_TYPE_NEW_FILE 78 TRIG_TYPE_NEW_FILE
79}; 79};
80 80
81enum {
82 PLAYLIST_VIEWER_ENTRY_SHOW_FILE_NAME = 0,
83 PLAYLIST_VIEWER_ENTRY_SHOW_FULL_PATH = 1,
84 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE_AND_ALBUM = 2,
85 PLAYLIST_VIEWER_ENTRY_SHOW_ID3_TITLE = 3
86};
87
81#ifdef HAVE_CROSSFADE 88#ifdef HAVE_CROSSFADE
82enum { 89enum {
83 CROSSFADE_ENABLE_OFF = 0, 90 CROSSFADE_ENABLE_OFF = 0,
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 3b29703fe9..a9f627bfa6 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1421,9 +1421,11 @@ const struct settings_list settings[] = {
1421 OFFON_SETTING(0,playlist_viewer_indices,LANG_SHOW_INDICES,true, 1421 OFFON_SETTING(0,playlist_viewer_indices,LANG_SHOW_INDICES,true,
1422 "playlist viewer indices",NULL), 1422 "playlist viewer indices",NULL),
1423 CHOICE_SETTING(0, playlist_viewer_track_display, LANG_TRACK_DISPLAY, 0, 1423 CHOICE_SETTING(0, playlist_viewer_track_display, LANG_TRACK_DISPLAY, 0,
1424 "playlist viewer track display","track name,full path", 1424 "playlist viewer track display",
1425 NULL, 2, ID2P(LANG_DISPLAY_TRACK_NAME_ONLY), 1425 "track name,full path,title and album from tags,title from tags",
1426 ID2P(LANG_DISPLAY_FULL_PATH)), 1426 NULL, 4, ID2P(LANG_DISPLAY_TRACK_NAME_ONLY),
1427 ID2P(LANG_DISPLAY_FULL_PATH),ID2P(LANG_DISPLAY_TITLEALBUM_FROMTAGS),
1428 ID2P(LANG_DISPLAY_TITLE_FROMTAGS)),
1427 CHOICE_SETTING(0, recursive_dir_insert, LANG_RECURSE_DIRECTORY , RECURSE_ON, 1429 CHOICE_SETTING(0, recursive_dir_insert, LANG_RECURSE_DIRECTORY , RECURSE_ON,
1428 "recursive directory insert", off_on_ask, NULL , 3 , 1430 "recursive directory insert", off_on_ask, NULL , 3 ,
1429 ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_ASK)), 1431 ID2P(LANG_OFF), ID2P(LANG_ON), ID2P(LANG_ASK)),