summaryrefslogtreecommitdiff
path: root/apps/plugins/cue_playlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/cue_playlist.c')
-rw-r--r--apps/plugins/cue_playlist.c375
1 files changed, 375 insertions, 0 deletions
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
37static 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
46static 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
58void 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
68static 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
122static 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
186static 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
193static 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
250static 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
262static void finalize_new_cue(void)
263{
264 rb->write(cps.cue_fd, "\n", 1);
265 rb->close(cps.cue_fd);
266}
267
268static 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
285enum 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*/