diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/gui/skin_engine/skin_tokens.c | 2 | ||||
-rw-r--r-- | apps/plugin.c | 1 | ||||
-rw-r--r-- | apps/plugin.h | 1 | ||||
-rw-r--r-- | apps/plugins/CATEGORIES | 1 | ||||
-rw-r--r-- | apps/plugins/SOURCES | 1 | ||||
-rw-r--r-- | apps/plugins/cue_playlist.c | 375 | ||||
-rw-r--r-- | apps/plugins/viewers.config | 1 |
7 files changed, 381 insertions, 1 deletions
diff --git a/apps/gui/skin_engine/skin_tokens.c b/apps/gui/skin_engine/skin_tokens.c index ba9396ae74..082619432f 100644 --- a/apps/gui/skin_engine/skin_tokens.c +++ b/apps/gui/skin_engine/skin_tokens.c | |||
@@ -74,7 +74,7 @@ | |||
74 | static const char* get_codectype(const struct mp3entry* id3) | 74 | static const char* get_codectype(const struct mp3entry* id3) |
75 | { | 75 | { |
76 | if (id3 && id3->codectype < AFMT_NUM_CODECS) { | 76 | if (id3 && id3->codectype < AFMT_NUM_CODECS) { |
77 | return audio_formats[id3->codectype].label; | 77 | return get_codec_string(id3->codectype); |
78 | } else { | 78 | } else { |
79 | return NULL; | 79 | return NULL; |
80 | } | 80 | } |
diff --git a/apps/plugin.c b/apps/plugin.c index 7c80e5c6e1..bd849dad50 100644 --- a/apps/plugin.c +++ b/apps/plugin.c | |||
@@ -841,6 +841,7 @@ static const struct plugin_api rockbox_api = { | |||
841 | filetype_get_plugin, | 841 | filetype_get_plugin, |
842 | playlist_entries_iterate, | 842 | playlist_entries_iterate, |
843 | lang_is_rtl, | 843 | lang_is_rtl, |
844 | get_codec_string, | ||
844 | }; | 845 | }; |
845 | 846 | ||
846 | static int plugin_buffer_handle; | 847 | static int plugin_buffer_handle; |
diff --git a/apps/plugin.h b/apps/plugin.h index 4dbd9d04c0..66c895d0db 100644 --- a/apps/plugin.h +++ b/apps/plugin.h | |||
@@ -979,6 +979,7 @@ struct plugin_api { | |||
979 | struct playlist_insert_context *pl_context, | 979 | struct playlist_insert_context *pl_context, |
980 | bool (*action_cb)(const char *file_name)); | 980 | bool (*action_cb)(const char *file_name)); |
981 | int (*lang_is_rtl)(void); | 981 | int (*lang_is_rtl)(void); |
982 | const char* (*get_codec_string)(int codectype); | ||
982 | }; | 983 | }; |
983 | 984 | ||
984 | /* plugin header */ | 985 | /* plugin header */ |
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 5e22bea980..366c42c75b 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES | |||
@@ -23,6 +23,7 @@ clock,apps | |||
23 | codebuster,games | 23 | codebuster,games |
24 | credits,viewers | 24 | credits,viewers |
25 | cube,demos | 25 | cube,demos |
26 | cue_playlist,viewers | ||
26 | dart_scorer,apps | 27 | dart_scorer,apps |
27 | db_commit,apps | 28 | db_commit,apps |
28 | db_folder_select,viewers | 29 | db_folder_select,viewers |
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 8028758ef0..0b1d48d5de 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -9,6 +9,7 @@ tagcache/tagcache.c | |||
9 | chessclock.c | 9 | chessclock.c |
10 | credits.c | 10 | credits.c |
11 | cube.c | 11 | cube.c |
12 | cue_playlist.c | ||
12 | dart_scorer.c | 13 | dart_scorer.c |
13 | dict.c | 14 | dict.c |
14 | jackpot.c | 15 | jackpot.c |
diff --git a/apps/plugins/cue_playlist.c b/apps/plugins/cue_playlist.c new file mode 100644 index 0000000000..d3f64fcc50 --- /dev/null +++ b/apps/plugins/cue_playlist.c | |||
@@ -0,0 +1,375 @@ | |||
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 | */ | ||
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 45bbfeef0b..d745a29cfe 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config | |||
@@ -34,6 +34,7 @@ rvf,viewers/video,4 | |||
34 | mp3,viewers/vbrfix,5 | 34 | mp3,viewers/vbrfix,5 |
35 | m3u,viewers/search,- | 35 | m3u,viewers/search,- |
36 | m3u,viewers/iriverify,- | 36 | m3u,viewers/iriverify,- |
37 | m3u,viewers/cue_playlist,- | ||
37 | lrc,apps/lrcplayer,1 | 38 | lrc,apps/lrcplayer,1 |
38 | lrc8,apps/lrcplayer,1 | 39 | lrc8,apps/lrcplayer,1 |
39 | snc,apps/lrcplayer,1 | 40 | snc,apps/lrcplayer,1 |