A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Feature] Playlis to cue plugin generate valid cue files from a playlist

uses remarks to store extra id3 info and display and playlist index

Change-Id: I00c9f6389445bb601dde6eb8f36157044024f8cb

+390 -1
+1 -1
apps/gui/skin_engine/skin_tokens.c
··· 74 74 static const char* get_codectype(const struct mp3entry* id3) 75 75 { 76 76 if (id3 && id3->codectype < AFMT_NUM_CODECS) { 77 - return audio_formats[id3->codectype].label; 77 + return get_codec_string(id3->codectype); 78 78 } else { 79 79 return NULL; 80 80 }
+1
apps/plugin.c
··· 841 841 filetype_get_plugin, 842 842 playlist_entries_iterate, 843 843 lang_is_rtl, 844 + get_codec_string, 844 845 }; 845 846 846 847 static int plugin_buffer_handle;
+1
apps/plugin.h
··· 979 979 struct playlist_insert_context *pl_context, 980 980 bool (*action_cb)(const char *file_name)); 981 981 int (*lang_is_rtl)(void); 982 + const char* (*get_codec_string)(int codectype); 982 983 }; 983 984 984 985 /* plugin header */
+1
apps/plugins/CATEGORIES
··· 23 23 codebuster,games 24 24 credits,viewers 25 25 cube,demos 26 + cue_playlist,viewers 26 27 dart_scorer,apps 27 28 db_commit,apps 28 29 db_folder_select,viewers
+1
apps/plugins/SOURCES
··· 9 9 chessclock.c 10 10 credits.c 11 11 cube.c 12 + cue_playlist.c 12 13 dart_scorer.c 13 14 dict.c 14 15 jackpot.c
+375
apps/plugins/cue_playlist.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2024 William Wilgus 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 + 22 + /* convert supplied playlist file to a .cue file */ 23 + 24 + #include "plugin.h" 25 + 26 + #if defined(DEBUG) || defined(SIMULATOR) 27 + #define logf(...) rb->debugf(__VA_ARGS__); rb->debugf("\n") 28 + #elif defined(ROCKBOX_HAS_LOGF) 29 + #define logf rb->logf 30 + #else 31 + #define logf(...) do { } while(0) 32 + #endif 33 + 34 + #define CPS_MAX_ENTRY_SZ (4 *1024) 35 + #define TDINDENT " " /* prepend spaces for track data formatting */ 36 + 37 + static struct cps 38 + { 39 + char *buffer; 40 + size_t buffer_sz; 41 + size_t buffer_index; 42 + int cue_fd; 43 + int entries; 44 + } cps; 45 + 46 + static int sprfunc(void *ptr, int letter) 47 + { 48 + /* callback for vuprintf */ 49 + (void) ptr; 50 + if (cps.buffer_index < cps.buffer_sz - 1) 51 + { 52 + cps.buffer[cps.buffer_index++] = letter; 53 + return 1; 54 + } 55 + return -1; 56 + } 57 + 58 + void cps_printf(const char *fmt, ...) 59 + { 60 + /* NOTE! this is made for flushing the buffer to disk -- WARNING 61 + * Nothing is NULL terminated here unless explicitly made so.. \0 */ 62 + va_list ap; 63 + va_start(ap, fmt); 64 + rb->vuprintf(sprfunc, NULL, fmt, ap); 65 + va_end(ap); 66 + } 67 + 68 + static uint32_t write_metadata_tags(struct mp3entry *id3) 69 + { 70 + /* check an ID3 and write any numeric tags and valid string tags (non empty) */ 71 + #define ISVALID(s) (s != NULL && s[0] != '\0') 72 + uint32_t tag_flags = 0; 73 + 74 + const char *performer = rb->str(LANG_TAGNAVI_UNTAGGED); 75 + if (ISVALID(id3->artist)) 76 + performer = id3->artist; 77 + else if (ISVALID(id3->albumartist)) 78 + performer = id3->albumartist; 79 + 80 + const char *title = rb->str(LANG_TAGNAVI_UNTAGGED); 81 + if (ISVALID(id3->title)) 82 + title = id3->title; 83 + 84 + #define PERFORMER_TITLE_SZ TDINDENT "PERFORMER \"%s\"\n" \ 85 + TDINDENT "TITLE \"%s\"\n" \ 86 + TDINDENT "SIZE_INFO %ld\n" 87 + 88 + cps_printf(PERFORMER_TITLE_SZ, performer, title, id3->filesize); 89 + if (ISVALID(id3->composer)) 90 + cps_printf(TDINDENT "COMPOSER \"%s\"\n", id3->composer); 91 + 92 + cps_printf(TDINDENT "INDEX 01 00:00:00\n"); 93 + 94 + #define ID3_TAG_NUM(theid3, TAGID, flag) \ 95 + {cps_printf(TDINDENT "REM %s %lu\n", TAGID, (unsigned long)theid3);tag_flags |= flag;} 96 + #define ID3_TAG_STR(theid3, TAGID, flag) if (ISVALID(theid3)) \ 97 + {cps_printf(TDINDENT "REM %s \"%s\"\n", TAGID, theid3);tag_flags |= flag;} 98 + 99 + ID3_TAG_STR(id3->album, "ALBUM", 0x01); 100 + ID3_TAG_STR(id3->albumartist, "ALBUMARTIST", 0x02); 101 + ID3_TAG_STR(id3->comment, "COMMENT", 0x04); 102 + ID3_TAG_STR(id3->genre_string, "GENRE", 0x08); 103 + ID3_TAG_STR(id3->disc_string, "DISC", 0x10); 104 + ID3_TAG_STR(id3->track_string, "TRACK", 0x20); 105 + ID3_TAG_STR(id3->grouping, "GROUPING", 0x40); 106 + ID3_TAG_STR(id3->mb_track_id, "MB_TRACK_ID", 0x80); 107 + ID3_TAG_STR(rb->get_codec_string(id3->codectype), "ID3_CODEC", 0x100); 108 + 109 + ID3_TAG_NUM(id3->discnum, "DISCNUM", 0x200); 110 + ID3_TAG_NUM(id3->tracknum, "TRACKNUM", 0x400); 111 + ID3_TAG_NUM(id3->length, "LENGTH", 0x800); 112 + ID3_TAG_NUM(id3->bitrate, "BITRATE", 0x1000); 113 + ID3_TAG_NUM(id3->frequency, "FREQUENCY", 0x2000); 114 + ID3_TAG_NUM(id3->track_level, "TRACK_LEVEL",0x4000); 115 + ID3_TAG_NUM(id3->album_level, "ALBUM_LEVEL", 0x8000); 116 + #undef ID3_TAG_STR 117 + #undef ID3_TAG_NUM 118 + #undef IS_VALID 119 + return tag_flags; 120 + } 121 + 122 + static bool current_playlist_filename_cb(const char *filename, int attr, int index, int display_index) 123 + { 124 + /* worker function for writing the actual cue data */ 125 + int szpos = 0; /* records position of the size string */ 126 + int namepos = 0; /* records position of the end of filename string */ 127 + struct mp3entry id3; 128 + 129 + logf("found: %s", filename); 130 + 131 + uint32_t id3_flags = 0; 132 + bool have_metadata = rb->get_metadata(&id3, -1, filename); 133 + 134 + if (!have_metadata && !rb->file_exists(filename)) 135 + return false; 136 + #define RB_ENTRY_DATA_FMT "REM RB_ENTRY_DATA " \ 137 + "\"DISPLAY_INDEX %012u " \ 138 + "PLAYLIST_INDEX %012u " \ 139 + "SIZE %n%012zu TAGS %012lu\"\n" 140 + 141 + const char *audiotype = "WAVE"; /* everything except MP3 */ 142 + const char *skipped = "";; 143 + const char *queued = ""; 144 + 145 + size_t entry_start = cps.buffer_index; /* get start to calculate final size */ 146 + 147 + cps_printf(RB_ENTRY_DATA_FMT, display_index, index, &szpos, 0, 0UL); 148 + 149 + size_t file_start = cps.buffer_index; 150 + cps_printf("FILE \"%s%n\"", filename, &namepos); 151 + 152 + if (cps.buffer[file_start + namepos - 1] == '3') 153 + audiotype = "MP3"; 154 + 155 + cps_printf(" %s\n", audiotype); 156 + 157 + if (attr & PLAYLIST_ATTR_SKIPPED) 158 + skipped = TDINDENT "REM SKIPPED\n"; 159 + if (attr & PLAYLIST_ATTR_QUEUED) 160 + queued = TDINDENT "REM QUEUED\n"; 161 + 162 + cps_printf(" TRACK %d AUDIO\n%s%s", display_index, skipped, queued); 163 + 164 + if (have_metadata) 165 + id3_flags = write_metadata_tags(&id3); 166 + 167 + if (cps.buffer_index - entry_start < CPS_MAX_ENTRY_SZ) 168 + { 169 + /* place the write pointer at the size entry so we can update size + tags*/ 170 + size_t index = cps.buffer_index; 171 + cps.buffer_index = entry_start + szpos; 172 + cps_printf("%012zu TAGS %012lu", (index - entry_start), id3_flags); 173 + cps.buffer_index = index; /* set the write pointer back at the end */ 174 + } 175 + else 176 + { 177 + rb->splashf(HZ * 3, "Entry too large %s", filename); 178 + cps.buffer_index = entry_start; 179 + return false; 180 + } 181 + 182 + cps.entries++; 183 + return true; 184 + } 185 + 186 + static bool playlist_filename_cb(const char *filename) 187 + { 188 + /* get entries from an on-disk playlist */ 189 + return current_playlist_filename_cb(filename, 0, 190 + cps.entries, cps.entries); 191 + } 192 + 193 + static bool current_playlist_get_entries(void) 194 + { 195 + /* get entries from a loaded playlist ( may have queued or skipped tracks ) */ 196 + #if defined(HAVE_ADJUSTABLE_CPU_FREQ) 197 + #define cpuboost(enable) rb->cpu_boost(enable); 198 + #else 199 + #define cpuboost(enable) do{ } while(0) 200 + #endif 201 + struct playlist_track_info info; 202 + int count = rb->playlist_amount(); 203 + int i, res = 0; 204 + logf("current playlist contains %d entries", count); 205 + 206 + cpuboost(true); 207 + 208 + long next_progress_tick = *rb->current_tick; 209 + for (i = 0; i < count; i++) 210 + { 211 + res = rb->playlist_get_track_info(NULL, i, &info); 212 + int attr = info.attr; 213 + int index = info.index; 214 + int display_index = info.display_index; 215 + if (res < 0 || !current_playlist_filename_cb(info.filename, attr, index, display_index)) 216 + break; 217 + 218 + if (cps.buffer_index >= (cps.buffer_sz - CPS_MAX_ENTRY_SZ)) 219 + { 220 + logf("Buffer full, writing to disk"); 221 + rb->write(cps.cue_fd, cps.buffer, cps.buffer_index); 222 + cps.buffer_index = 0; 223 + } 224 + 225 + if (TIME_AFTER(*rb->current_tick, next_progress_tick)) 226 + { 227 + rb->splash_progress(i, count, "Processing current playlist %d", i); 228 + int action = rb->get_action(CONTEXT_STD, TIMEOUT_NOBLOCK); 229 + if (action == ACTION_STD_CANCEL) 230 + { 231 + res = -10; 232 + break; 233 + } 234 + if (rb->default_event_handler(action) == SYS_USB_CONNECTED) 235 + { 236 + cpuboost(false); 237 + return PLUGIN_USB_CONNECTED; 238 + } 239 + next_progress_tick = *rb->current_tick + HZ / 2; 240 + } 241 + rb->yield(); 242 + } 243 + 244 + cpuboost(false); 245 + 246 + return res >= 0; 247 + #undef cpuboost 248 + } 249 + 250 + static void init_new_cue(const char *playlist_filename) 251 + { 252 + if (cps.cue_fd >= 0) 253 + { 254 + rb->lseek(cps.cue_fd, 0, SEEK_SET); 255 + rb->fdprintf(cps.cue_fd, "REM COMMENT \"generated by Rockbox version: " \ 256 + "%s\"\n", rb->rbversion); 257 + 258 + rb->fdprintf(cps.cue_fd, "TITLE \"%s\"\n", playlist_filename); /* top level TITLE */ 259 + } 260 + } 261 + 262 + static void finalize_new_cue(void) 263 + { 264 + rb->write(cps.cue_fd, "\n", 1); 265 + rb->close(cps.cue_fd); 266 + } 267 + 268 + static int create_new_cue(const char *filename) 269 + { 270 + char buf[MAX_PATH]; 271 + if (!filename) 272 + filename = "/Playlists/current.cue"; 273 + const char *dot = rb->strrchr(filename, '.'); 274 + int dotpos = 0; 275 + if (dot) 276 + dotpos = dot - filename; 277 + rb->snprintf(buf, sizeof(buf), "%.*s.cue", dotpos, filename); 278 + cps.cue_fd = rb->open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0666); 279 + 280 + init_new_cue(filename); 281 + 282 + return cps.cue_fd; 283 + } 284 + 285 + enum plugin_status plugin_start(const void* parameter) 286 + { 287 + 288 + bool res; 289 + rb->splash(HZ*2, ID2P(LANG_WAIT)); 290 + 291 + const char *filename = parameter; 292 + 293 + if (create_new_cue(filename) < 0) 294 + { 295 + rb->splashf(HZ, "creat() failed: %d", cps.cue_fd); 296 + return PLUGIN_ERROR; 297 + } 298 + 299 + cps.buffer = rb->plugin_get_buffer(&cps.buffer_sz); 300 + if (cps.buffer != NULL) 301 + { 302 + cps.buffer_index = 0; 303 + #ifdef STORAGE_WANTS_ALIGN 304 + /* align start and length for DMA */ 305 + STORAGE_ALIGN_BUFFER(cps.buffer, cps.buffer_sz); 306 + #else 307 + /* align start and length to 32 bit */ 308 + ALIGN_BUFFER(cps.buffer, cps.buffer_sz, 4); 309 + #endif 310 + } 311 + if (cps.buffer == NULL|| cps.buffer_sz < CPS_MAX_ENTRY_SZ) 312 + { 313 + rb->splashf(HZ, "No Buffers Available :( "); 314 + return PLUGIN_ERROR; 315 + } 316 + 317 + if (filename && filename[0]) 318 + res = rb->playlist_entries_iterate(filename, NULL, &playlist_filename_cb); 319 + else 320 + res = current_playlist_get_entries(); 321 + 322 + if (res) 323 + { 324 + 325 + if (cps.buffer_index > 0) 326 + { 327 + rb->write(cps.cue_fd, cps.buffer, cps.buffer_index); 328 + cps.buffer_index = 0; 329 + 330 + } 331 + rb->splashf(HZ * 2, 332 + "Playist parsing SUCCESS %d entries written", cps.entries); 333 + } 334 + else 335 + { 336 + rb->splashf(HZ * 2, "Playist parsing FAILED after %d entries", cps.entries); 337 + } 338 + 339 + finalize_new_cue(); 340 + 341 + if (!res) 342 + return PLUGIN_ERROR; 343 + return PLUGIN_OK; 344 + } 345 + 346 + /* 347 + #CUE FORMAT 348 + CATALOG 349 + CDTEXTFILE 350 + FILE 351 + FLAGS 352 + INDEX 353 + ISRC 354 + PERFORMER 355 + POSTGAP 356 + PREGAP 357 + REM 358 + SONGWRITER 359 + TITLE 360 + TRACK 361 + #CD-TEXT https://wyday.com/cuesharp/specification.php 362 + ARRANGER 363 + COMPOSER 364 + DISC_ID 365 + GENRE 366 + ISRC 367 + MESSAGE 368 + PERFORMER 369 + SONGWRITER 370 + TITLE 371 + TOC_INFO 372 + TOC_INFO2 373 + UPC_EAN 374 + SIZE_INFO 375 + */
+1
apps/plugins/viewers.config
··· 34 34 mp3,viewers/vbrfix,5 35 35 m3u,viewers/search,- 36 36 m3u,viewers/iriverify,- 37 + m3u,viewers/cue_playlist,- 37 38 lrc,apps/lrcplayer,1 38 39 lrc8,apps/lrcplayer,1 39 40 snc,apps/lrcplayer,1
+8
lib/rbcodec/metadata/metadata.c
··· 304 304 return base_type; 305 305 } 306 306 307 + const char * get_codec_string(int type) 308 + { 309 + if (type < 0 || type >= AFMT_NUM_CODECS) 310 + type = AFMT_UNKNOWN; 311 + 312 + return audio_formats[type].label; 313 + } 314 + 307 315 /* Get the basic audio type */ 308 316 bool rbcodec_format_is_atomic(int afmt) 309 317 {
+1
lib/rbcodec/metadata/metadata.h
··· 333 333 334 334 void fill_metadata_from_path(struct mp3entry *id3, const char *trackname); 335 335 int get_audio_base_codec_type(int type); 336 + const char * get_codec_string(int type); 336 337 bool rbcodec_format_is_atomic(int afmt); 337 338 bool format_buffers_with_offset(int afmt); 338 339