mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
[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
This commit is contained in:
parent
072228bb70
commit
7e90760a48
9 changed files with 390 additions and 1 deletions
|
@ -74,7 +74,7 @@
|
|||
static const char* get_codectype(const struct mp3entry* id3)
|
||||
{
|
||||
if (id3 && id3->codectype < AFMT_NUM_CODECS) {
|
||||
return audio_formats[id3->codectype].label;
|
||||
return get_codec_string(id3->codectype);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -841,6 +841,7 @@ static const struct plugin_api rockbox_api = {
|
|||
filetype_get_plugin,
|
||||
playlist_entries_iterate,
|
||||
lang_is_rtl,
|
||||
get_codec_string,
|
||||
};
|
||||
|
||||
static int plugin_buffer_handle;
|
||||
|
|
|
@ -979,6 +979,7 @@ struct plugin_api {
|
|||
struct playlist_insert_context *pl_context,
|
||||
bool (*action_cb)(const char *file_name));
|
||||
int (*lang_is_rtl)(void);
|
||||
const char* (*get_codec_string)(int codectype);
|
||||
};
|
||||
|
||||
/* plugin header */
|
||||
|
|
|
@ -23,6 +23,7 @@ clock,apps
|
|||
codebuster,games
|
||||
credits,viewers
|
||||
cube,demos
|
||||
cue_playlist,viewers
|
||||
dart_scorer,apps
|
||||
db_commit,apps
|
||||
db_folder_select,viewers
|
||||
|
|
|
@ -9,6 +9,7 @@ tagcache/tagcache.c
|
|||
chessclock.c
|
||||
credits.c
|
||||
cube.c
|
||||
cue_playlist.c
|
||||
dart_scorer.c
|
||||
dict.c
|
||||
jackpot.c
|
||||
|
|
375
apps/plugins/cue_playlist.c
Normal file
375
apps/plugins/cue_playlist.c
Normal file
|
@ -0,0 +1,375 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2024 William Wilgus
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/* convert supplied playlist file to a .cue file */
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
#if defined(DEBUG) || defined(SIMULATOR)
|
||||
#define logf(...) rb->debugf(__VA_ARGS__); rb->debugf("\n")
|
||||
#elif defined(ROCKBOX_HAS_LOGF)
|
||||
#define logf rb->logf
|
||||
#else
|
||||
#define logf(...) do { } while(0)
|
||||
#endif
|
||||
|
||||
#define CPS_MAX_ENTRY_SZ (4 *1024)
|
||||
#define TDINDENT " " /* prepend spaces for track data formatting */
|
||||
|
||||
static struct cps
|
||||
{
|
||||
char *buffer;
|
||||
size_t buffer_sz;
|
||||
size_t buffer_index;
|
||||
int cue_fd;
|
||||
int entries;
|
||||
} cps;
|
||||
|
||||
static int sprfunc(void *ptr, int letter)
|
||||
{
|
||||
/* callback for vuprintf */
|
||||
(void) ptr;
|
||||
if (cps.buffer_index < cps.buffer_sz - 1)
|
||||
{
|
||||
cps.buffer[cps.buffer_index++] = letter;
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void cps_printf(const char *fmt, ...)
|
||||
{
|
||||
/* NOTE! this is made for flushing the buffer to disk -- WARNING
|
||||
* Nothing is NULL terminated here unless explicitly made so.. \0 */
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
rb->vuprintf(sprfunc, NULL, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static uint32_t write_metadata_tags(struct mp3entry *id3)
|
||||
{
|
||||
/* check an ID3 and write any numeric tags and valid string tags (non empty) */
|
||||
#define ISVALID(s) (s != NULL && s[0] != '\0')
|
||||
uint32_t tag_flags = 0;
|
||||
|
||||
const char *performer = rb->str(LANG_TAGNAVI_UNTAGGED);
|
||||
if (ISVALID(id3->artist))
|
||||
performer = id3->artist;
|
||||
else if (ISVALID(id3->albumartist))
|
||||
performer = id3->albumartist;
|
||||
|
||||
const char *title = rb->str(LANG_TAGNAVI_UNTAGGED);
|
||||
if (ISVALID(id3->title))
|
||||
title = id3->title;
|
||||
|
||||
#define PERFORMER_TITLE_SZ TDINDENT "PERFORMER \"%s\"\n" \
|
||||
TDINDENT "TITLE \"%s\"\n" \
|
||||
TDINDENT "SIZE_INFO %ld\n"
|
||||
|
||||
cps_printf(PERFORMER_TITLE_SZ, performer, title, id3->filesize);
|
||||
if (ISVALID(id3->composer))
|
||||
cps_printf(TDINDENT "COMPOSER \"%s\"\n", id3->composer);
|
||||
|
||||
cps_printf(TDINDENT "INDEX 01 00:00:00\n");
|
||||
|
||||
#define ID3_TAG_NUM(theid3, TAGID, flag) \
|
||||
{cps_printf(TDINDENT "REM %s %lu\n", TAGID, (unsigned long)theid3);tag_flags |= flag;}
|
||||
#define ID3_TAG_STR(theid3, TAGID, flag) if (ISVALID(theid3)) \
|
||||
{cps_printf(TDINDENT "REM %s \"%s\"\n", TAGID, theid3);tag_flags |= flag;}
|
||||
|
||||
ID3_TAG_STR(id3->album, "ALBUM", 0x01);
|
||||
ID3_TAG_STR(id3->albumartist, "ALBUMARTIST", 0x02);
|
||||
ID3_TAG_STR(id3->comment, "COMMENT", 0x04);
|
||||
ID3_TAG_STR(id3->genre_string, "GENRE", 0x08);
|
||||
ID3_TAG_STR(id3->disc_string, "DISC", 0x10);
|
||||
ID3_TAG_STR(id3->track_string, "TRACK", 0x20);
|
||||
ID3_TAG_STR(id3->grouping, "GROUPING", 0x40);
|
||||
ID3_TAG_STR(id3->mb_track_id, "MB_TRACK_ID", 0x80);
|
||||
ID3_TAG_STR(rb->get_codec_string(id3->codectype), "ID3_CODEC", 0x100);
|
||||
|
||||
ID3_TAG_NUM(id3->discnum, "DISCNUM", 0x200);
|
||||
ID3_TAG_NUM(id3->tracknum, "TRACKNUM", 0x400);
|
||||
ID3_TAG_NUM(id3->length, "LENGTH", 0x800);
|
||||
ID3_TAG_NUM(id3->bitrate, "BITRATE", 0x1000);
|
||||
ID3_TAG_NUM(id3->frequency, "FREQUENCY", 0x2000);
|
||||
ID3_TAG_NUM(id3->track_level, "TRACK_LEVEL",0x4000);
|
||||
ID3_TAG_NUM(id3->album_level, "ALBUM_LEVEL", 0x8000);
|
||||
#undef ID3_TAG_STR
|
||||
#undef ID3_TAG_NUM
|
||||
#undef IS_VALID
|
||||
return tag_flags;
|
||||
}
|
||||
|
||||
static bool current_playlist_filename_cb(const char *filename, int attr, int index, int display_index)
|
||||
{
|
||||
/* worker function for writing the actual cue data */
|
||||
int szpos = 0; /* records position of the size string */
|
||||
int namepos = 0; /* records position of the end of filename string */
|
||||
struct mp3entry id3;
|
||||
|
||||
logf("found: %s", filename);
|
||||
|
||||
uint32_t id3_flags = 0;
|
||||
bool have_metadata = rb->get_metadata(&id3, -1, filename);
|
||||
|
||||
if (!have_metadata && !rb->file_exists(filename))
|
||||
return false;
|
||||
#define RB_ENTRY_DATA_FMT "REM RB_ENTRY_DATA " \
|
||||
"\"DISPLAY_INDEX %012u " \
|
||||
"PLAYLIST_INDEX %012u " \
|
||||
"SIZE %n%012zu TAGS %012lu\"\n"
|
||||
|
||||
const char *audiotype = "WAVE"; /* everything except MP3 */
|
||||
const char *skipped = "";;
|
||||
const char *queued = "";
|
||||
|
||||
size_t entry_start = cps.buffer_index; /* get start to calculate final size */
|
||||
|
||||
cps_printf(RB_ENTRY_DATA_FMT, display_index, index, &szpos, 0, 0UL);
|
||||
|
||||
size_t file_start = cps.buffer_index;
|
||||
cps_printf("FILE \"%s%n\"", filename, &namepos);
|
||||
|
||||
if (cps.buffer[file_start + namepos - 1] == '3')
|
||||
audiotype = "MP3";
|
||||
|
||||
cps_printf(" %s\n", audiotype);
|
||||
|
||||
if (attr & PLAYLIST_ATTR_SKIPPED)
|
||||
skipped = TDINDENT "REM SKIPPED\n";
|
||||
if (attr & PLAYLIST_ATTR_QUEUED)
|
||||
queued = TDINDENT "REM QUEUED\n";
|
||||
|
||||
cps_printf(" TRACK %d AUDIO\n%s%s", display_index, skipped, queued);
|
||||
|
||||
if (have_metadata)
|
||||
id3_flags = write_metadata_tags(&id3);
|
||||
|
||||
if (cps.buffer_index - entry_start < CPS_MAX_ENTRY_SZ)
|
||||
{
|
||||
/* place the write pointer at the size entry so we can update size + tags*/
|
||||
size_t index = cps.buffer_index;
|
||||
cps.buffer_index = entry_start + szpos;
|
||||
cps_printf("%012zu TAGS %012lu", (index - entry_start), id3_flags);
|
||||
cps.buffer_index = index; /* set the write pointer back at the end */
|
||||
}
|
||||
else
|
||||
{
|
||||
rb->splashf(HZ * 3, "Entry too large %s", filename);
|
||||
cps.buffer_index = entry_start;
|
||||
return false;
|
||||
}
|
||||
|
||||
cps.entries++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool playlist_filename_cb(const char *filename)
|
||||
{
|
||||
/* get entries from an on-disk playlist */
|
||||
return current_playlist_filename_cb(filename, 0,
|
||||
cps.entries, cps.entries);
|
||||
}
|
||||
|
||||
static bool current_playlist_get_entries(void)
|
||||
{
|
||||
/* get entries from a loaded playlist ( may have queued or skipped tracks ) */
|
||||
#if defined(HAVE_ADJUSTABLE_CPU_FREQ)
|
||||
#define cpuboost(enable) rb->cpu_boost(enable);
|
||||
#else
|
||||
#define cpuboost(enable) do{ } while(0)
|
||||
#endif
|
||||
struct playlist_track_info info;
|
||||
int count = rb->playlist_amount();
|
||||
int i, res = 0;
|
||||
logf("current playlist contains %d entries", count);
|
||||
|
||||
cpuboost(true);
|
||||
|
||||
long next_progress_tick = *rb->current_tick;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
res = rb->playlist_get_track_info(NULL, i, &info);
|
||||
int attr = info.attr;
|
||||
int index = info.index;
|
||||
int display_index = info.display_index;
|
||||
if (res < 0 || !current_playlist_filename_cb(info.filename, attr, index, display_index))
|
||||
break;
|
||||
|
||||
if (cps.buffer_index >= (cps.buffer_sz - CPS_MAX_ENTRY_SZ))
|
||||
{
|
||||
logf("Buffer full, writing to disk");
|
||||
rb->write(cps.cue_fd, cps.buffer, cps.buffer_index);
|
||||
cps.buffer_index = 0;
|
||||
}
|
||||
|
||||
if (TIME_AFTER(*rb->current_tick, next_progress_tick))
|
||||
{
|
||||
rb->splash_progress(i, count, "Processing current playlist %d", i);
|
||||
int action = rb->get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
|
||||
if (action == ACTION_STD_CANCEL)
|
||||
{
|
||||
res = -10;
|
||||
break;
|
||||
}
|
||||
if (rb->default_event_handler(action) == SYS_USB_CONNECTED)
|
||||
{
|
||||
cpuboost(false);
|
||||
return PLUGIN_USB_CONNECTED;
|
||||
}
|
||||
next_progress_tick = *rb->current_tick + HZ / 2;
|
||||
}
|
||||
rb->yield();
|
||||
}
|
||||
|
||||
cpuboost(false);
|
||||
|
||||
return res >= 0;
|
||||
#undef cpuboost
|
||||
}
|
||||
|
||||
static void init_new_cue(const char *playlist_filename)
|
||||
{
|
||||
if (cps.cue_fd >= 0)
|
||||
{
|
||||
rb->lseek(cps.cue_fd, 0, SEEK_SET);
|
||||
rb->fdprintf(cps.cue_fd, "REM COMMENT \"generated by Rockbox version: " \
|
||||
"%s\"\n", rb->rbversion);
|
||||
|
||||
rb->fdprintf(cps.cue_fd, "TITLE \"%s\"\n", playlist_filename); /* top level TITLE */
|
||||
}
|
||||
}
|
||||
|
||||
static void finalize_new_cue(void)
|
||||
{
|
||||
rb->write(cps.cue_fd, "\n", 1);
|
||||
rb->close(cps.cue_fd);
|
||||
}
|
||||
|
||||
static int create_new_cue(const char *filename)
|
||||
{
|
||||
char buf[MAX_PATH];
|
||||
if (!filename)
|
||||
filename = "/Playlists/current.cue";
|
||||
const char *dot = rb->strrchr(filename, '.');
|
||||
int dotpos = 0;
|
||||
if (dot)
|
||||
dotpos = dot - filename;
|
||||
rb->snprintf(buf, sizeof(buf), "%.*s.cue", dotpos, filename);
|
||||
cps.cue_fd = rb->open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0666);
|
||||
|
||||
init_new_cue(filename);
|
||||
|
||||
return cps.cue_fd;
|
||||
}
|
||||
|
||||
enum plugin_status plugin_start(const void* parameter)
|
||||
{
|
||||
|
||||
bool res;
|
||||
rb->splash(HZ*2, ID2P(LANG_WAIT));
|
||||
|
||||
const char *filename = parameter;
|
||||
|
||||
if (create_new_cue(filename) < 0)
|
||||
{
|
||||
rb->splashf(HZ, "creat() failed: %d", cps.cue_fd);
|
||||
return PLUGIN_ERROR;
|
||||
}
|
||||
|
||||
cps.buffer = rb->plugin_get_buffer(&cps.buffer_sz);
|
||||
if (cps.buffer != NULL)
|
||||
{
|
||||
cps.buffer_index = 0;
|
||||
#ifdef STORAGE_WANTS_ALIGN
|
||||
/* align start and length for DMA */
|
||||
STORAGE_ALIGN_BUFFER(cps.buffer, cps.buffer_sz);
|
||||
#else
|
||||
/* align start and length to 32 bit */
|
||||
ALIGN_BUFFER(cps.buffer, cps.buffer_sz, 4);
|
||||
#endif
|
||||
}
|
||||
if (cps.buffer == NULL|| cps.buffer_sz < CPS_MAX_ENTRY_SZ)
|
||||
{
|
||||
rb->splashf(HZ, "No Buffers Available :( ");
|
||||
return PLUGIN_ERROR;
|
||||
}
|
||||
|
||||
if (filename && filename[0])
|
||||
res = rb->playlist_entries_iterate(filename, NULL, &playlist_filename_cb);
|
||||
else
|
||||
res = current_playlist_get_entries();
|
||||
|
||||
if (res)
|
||||
{
|
||||
|
||||
if (cps.buffer_index > 0)
|
||||
{
|
||||
rb->write(cps.cue_fd, cps.buffer, cps.buffer_index);
|
||||
cps.buffer_index = 0;
|
||||
|
||||
}
|
||||
rb->splashf(HZ * 2,
|
||||
"Playist parsing SUCCESS %d entries written", cps.entries);
|
||||
}
|
||||
else
|
||||
{
|
||||
rb->splashf(HZ * 2, "Playist parsing FAILED after %d entries", cps.entries);
|
||||
}
|
||||
|
||||
finalize_new_cue();
|
||||
|
||||
if (!res)
|
||||
return PLUGIN_ERROR;
|
||||
return PLUGIN_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
#CUE FORMAT
|
||||
CATALOG
|
||||
CDTEXTFILE
|
||||
FILE
|
||||
FLAGS
|
||||
INDEX
|
||||
ISRC
|
||||
PERFORMER
|
||||
POSTGAP
|
||||
PREGAP
|
||||
REM
|
||||
SONGWRITER
|
||||
TITLE
|
||||
TRACK
|
||||
#CD-TEXT https://wyday.com/cuesharp/specification.php
|
||||
ARRANGER
|
||||
COMPOSER
|
||||
DISC_ID
|
||||
GENRE
|
||||
ISRC
|
||||
MESSAGE
|
||||
PERFORMER
|
||||
SONGWRITER
|
||||
TITLE
|
||||
TOC_INFO
|
||||
TOC_INFO2
|
||||
UPC_EAN
|
||||
SIZE_INFO
|
||||
*/
|
|
@ -34,6 +34,7 @@ rvf,viewers/video,4
|
|||
mp3,viewers/vbrfix,5
|
||||
m3u,viewers/search,-
|
||||
m3u,viewers/iriverify,-
|
||||
m3u,viewers/cue_playlist,-
|
||||
lrc,apps/lrcplayer,1
|
||||
lrc8,apps/lrcplayer,1
|
||||
snc,apps/lrcplayer,1
|
||||
|
|
|
@ -304,6 +304,14 @@ int get_audio_base_codec_type(int type)
|
|||
return base_type;
|
||||
}
|
||||
|
||||
const char * get_codec_string(int type)
|
||||
{
|
||||
if (type < 0 || type >= AFMT_NUM_CODECS)
|
||||
type = AFMT_UNKNOWN;
|
||||
|
||||
return audio_formats[type].label;
|
||||
}
|
||||
|
||||
/* Get the basic audio type */
|
||||
bool rbcodec_format_is_atomic(int afmt)
|
||||
{
|
||||
|
|
|
@ -333,6 +333,7 @@ void wipe_mp3entry(struct mp3entry *id3);
|
|||
|
||||
void fill_metadata_from_path(struct mp3entry *id3, const char *trackname);
|
||||
int get_audio_base_codec_type(int type);
|
||||
const char * get_codec_string(int type);
|
||||
bool rbcodec_format_is_atomic(int afmt);
|
||||
bool format_buffers_with_offset(int afmt);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue