/*
DeaDBeeF - ultimate music player for GNU/Linux systems with X11
Copyright (C) 2009 Alexey Yakovenko
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 program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "playlist.h"
#include "codec.h"
#include "streamer.h"
#include "messagepump.h"
#include "messages.h"
#include "playback.h"
#include "plugins.h"
//#include "cvorbis.h"
//#include "cdumb.h"
//#include "cmp3.h"
//#include "cgme.h"
//#include "cflac.h"
//#include "csid.h"
#define SKIP_BLANK_CUE_TRACKS 1
playItem_t *playlist_head[PL_MAX_ITERATORS];
playItem_t *playlist_tail[PL_MAX_ITERATORS];
playItem_t playlist_current;
playItem_t *playlist_current_ptr;
int pl_count = 0;
static int pl_order = 0; // 0 = linear, 1 = shuffle, 2 = random
static int pl_loop_mode = 0; // 0 = loop, 1 = don't loop, 2 = loop single
void
pl_free (void) {
while (playlist_head[PL_MAIN]) {
pl_remove (playlist_head[PL_MAIN]);
}
}
static const char *
pl_cue_skipspaces (const uint8_t *p) {
while (*p && *p <= ' ') {
p++;
}
return p;
}
static void
pl_get_qvalue_from_cue (const char *p, char *out) {
if (*p == 0) {
*out = 0;
return;
}
// seek "
while (*p && *p != '"') {
p++;
}
if (*p == 0) {
*out = 0;
return;
}
p++;
p = pl_cue_skipspaces (p);
while (*p && *p != '"') {
*out++ = *p++;
}
*out = 0;
}
static void
pl_get_value_from_cue (const char *p, char *out) {
while (*p >= ' ') {
*out++ = *p++;
}
*out = 0;
}
static float
pl_cue_parse_time (const char *p) {
char tmp[3] = {0};
const char *next = p;
int s;
while (*next && *next != ':') {
next++;
}
if ((next - p) != 2) {
return -1;
}
strncpy (tmp, p, 2);
tmp[next-p] = 0;
float mins = atoi (tmp);
next++;
p = next;
while (*next && *next != ':') {
next++;
}
if ((next - p) != 2) {
return -1;
}
strncpy (tmp, p, 2);
float sec = atoi (tmp);
next++;
p = next;
while (*next && *next != ':') {
next++;
}
if ((next - p) != 2) {
return -1;
}
strncpy (tmp, p, 2);
float frm = atoi (tmp);
return mins * 60 + sec;
}
playItem_t *
pl_insert_cue_from_buffer (playItem_t *after, const char *fname, const uint8_t *buffer, int buffersize, struct DB_decoder_s *decoder, const char *ftype) {
char performer[1024];
char albumtitle[1024];
char file[1024];
char track[1024];
char title[1024];
char start[1024];
playItem_t *prev = NULL;
while (buffersize > 0) {
const uint8_t *p = buffer;
// find end of line
while (p - buffer < buffersize && *p >= 0x20) {
p++;
}
// skip linebreak(s)
while (p - buffer < buffersize && *p < 0x20) {
*p++;
}
if (p-buffer > 2048) { // huge string, ignore
buffer = p;
buffersize -= p-buffer;
continue;
}
char str[p-buffer+1];
strncpy (str, buffer, p-buffer);
str[p-buffer] = 0;
buffersize -= p-buffer;
buffer = p;
p = pl_cue_skipspaces (str);
if (!strncmp (p, "PERFORMER ", 10)) {
pl_get_qvalue_from_cue (p + 10, performer);
// printf ("got performer: %s\n", performer);
}
else if (!strncmp (p, "TITLE ", 6)) {
if (str[0] > ' ') {
pl_get_qvalue_from_cue (p + 6, albumtitle);
// printf ("got albumtitle: %s\n", albumtitle);
}
else {
pl_get_qvalue_from_cue (p + 6, title);
// printf ("got title: %s\n", title);
}
}
else if (!strncmp (p, "FILE ", 5)) {
pl_get_qvalue_from_cue (p + 5, file);
// copy directory name
char dirname[1024];
int len = strlen (fname);
memcpy (dirname, fname, len+1);
char *p = dirname + len;
while (*p != '/') {
p--;
len--;
}
p++;
len++;
// add file name
int flen = strlen (file);
// ensure fullname fills in buffer
if (flen + len >= 1024) {
// printf ("cue file name is too long");
return NULL;
}
strcpy (p, file);
// copy full name in place of relative name
strcpy (file, dirname);
// printf ("ended up as: %s\n", file);
}
else if (!strncmp (p, "TRACK ", 6)) {
pl_get_value_from_cue (p + 6, track);
// printf ("got track: %s\n", track);
}
// else if (!strncmp (p, "PERFORMER ", 10)) {
// pl_get_qvalue_from_cue (p + 10, performer);
// }
else if (!strncmp (p, "INDEX 00 ", 9) || !strncmp (p, "INDEX 01 ", 9)) {
if (!track[0]) {
continue;
}
#if SKIP_BLANK_CUE_TRACKS
if (!title[0])
continue;
#endif
pl_get_value_from_cue (p + 9, start);
// printf ("got index0: %s\n", start);
char *p = track;
while (*p && isdigit (*p)) {
p++;
}
*p = 0;
// check that indexes have valid timestamps
float tstart = pl_cue_parse_time (start);
if (tstart < 0) {
// printf ("cue file %s has bad timestamp(s)\n", cuename);
continue;
}
if (prev) {
prev->timeend = tstart;
prev->duration = prev->timeend - prev->timestart;
// printf ("end time for prev track (%x): %f\n", prev, tstart);
}
// add this track
char str[1024];
snprintf (str, 1024, "%d. %s - %s", atoi (track), performer, title[0] ? title : "?", start, tstart);
// printf ("adding %s\n", str);
playItem_t *it = malloc (sizeof (playItem_t));
memset (it, 0, sizeof (playItem_t));
it->decoder = decoder;
it->fname = strdup (file);
it->tracknum = atoi (track);
it->timestart = tstart;
it->timeend = -1; // will be filled by next read, or by decoder
it->filetype = ftype;
after = pl_insert_item (after, it);
pl_add_meta (it, "artist", performer);
pl_add_meta (it, "album", albumtitle);
pl_add_meta (it, "track", track);
pl_add_meta (it, "title", title);
prev = it;
track[0] = 0;
}
else {
// printf ("got unknown line:\n%s\n", p);
}
}
return after;
}
playItem_t *
pl_insert_cue (playItem_t *after, const char *fname, struct DB_decoder_s *decoder, const char *ftype) {
int len = strlen (fname);
char cuename[len+5];
strcpy (cuename, fname);
strcpy (cuename+len, ".cue");
FILE *fp = fopen (cuename, "rb");
if (!fp) {
char *ptr = cuename + len-1;
while (ptr >= cuename && *ptr != '.') {
ptr--;
}
strcpy (ptr+1, "cue");
fp = fopen (cuename, "rb");
if (!fp) {
return NULL;
}
}
fseek (fp, 0, SEEK_END);
size_t sz = ftell (fp);
if (sz == 0) {
fclose (fp);
return NULL;
}
rewind (fp);
uint8_t buf[sz];
if (fread (buf, 1, sz, fp) != sz) {
fclose (fp);
return NULL;
}
fclose (fp);
return pl_insert_cue_from_buffer (after, fname, buf, sz, decoder, ftype);
}
playItem_t *
pl_insert_file (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
if (!fname) {
return NULL;
}
// detect decoder
// DB_decoder_t *decoder = NULL;
const char *eol = fname + strlen (fname) - 1;
while (eol > fname && *eol != '.') {
eol--;
}
eol++;
DB_decoder_t **decoders = plug_get_decoder_list ();
// match by decoder
for (int i = 0; decoders[i]; i++) {
if (decoders[i]->exts && decoders[i]->insert) {
const char **exts = decoders[i]->exts;
if (exts) {
for (int e = 0; exts[e]; e++) {
if (!strcasecmp (exts[e], eol)) {
playItem_t *inserted = NULL;
if ((inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname)) != NULL) {
if (cb) {
if (cb (inserted, user_data) < 0) {
*pabort = 1;
}
}
return inserted;
}
}
}
}
}
}
return NULL;
}
playItem_t *
pl_insert_dir (playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) {
struct stat buf;
lstat (dirname, &buf);
if (S_ISLNK(buf.st_mode)) {
return NULL;
}
struct dirent **namelist = NULL;
int n;
n = scandir (dirname, &namelist, NULL, alphasort);
if (n < 0)
{
if (namelist)
free (namelist);
return NULL; // not a dir or no read access
}
else
{
int i;
for (i = 0; i < n; i++)
{
// no hidden files
if (namelist[i]->d_name[0] != '.')
{
char fullname[1024];
strcpy (fullname, dirname);
strncat (fullname, "/", 1024);
strncat (fullname, namelist[i]->d_name, 1024);
playItem_t *inserted = pl_insert_dir (after, fullname, pabort, cb, user_data);
if (!inserted) {
inserted = pl_insert_file (after, fullname, pabort, cb, user_data);
}
if (inserted) {
after = inserted;
}
if (*pabort) {
break;
}
}
free (namelist[i]);
}
free (namelist);
}
return after;
}
int
pl_add_file (const char *fname, int (*cb)(playItem_t *it, void *data), void *user_data) {
int abort = 0;
if (pl_insert_file (playlist_tail[PL_MAIN], fname, &abort, cb, user_data)) {
return 0;
}
return -1;
}
int
pl_add_dir (const char *dirname, int (*cb)(playItem_t *it, void *data), void *user_data) {
int abort = 0;
if (pl_insert_dir (playlist_tail[PL_MAIN], dirname, &abort, cb, user_data)) {
return 0;
}
return -1;
// {{{ original pl_add_dir code
#if 0
struct stat buf;
lstat (dirname, &buf);
if (S_ISLNK(buf.st_mode)) {
return -1;
}
struct dirent **namelist = NULL;
int n;
n = scandir (dirname, &namelist, NULL, alphasort);
if (n < 0)
{
if (namelist)
free (namelist);
return -1; // not a dir or no read access
}
else
{
int i;
for (i = 0; i < n; i++)
{
// no hidden files
if (namelist[i]->d_name[0] != '.')
{
char fullname[1024];
strcpy (fullname, dirname);
strncat (fullname, "/", 1024);
strncat (fullname, namelist[i]->d_name, 1024);
if (pl_add_dir (fullname)) {
pl_add_file (fullname);
}
}
free (namelist[i]);
}
free (namelist);
}
return 0;
#endif
// }}}
}
int
pl_remove (playItem_t *it) {
if (!it)
return -1;
pl_count--;
if (playlist_current_ptr == it) {
playlist_current_ptr = NULL;
}
// remove from linear list
if (it->prev[PL_MAIN]) {
it->prev[PL_MAIN]->next[PL_MAIN] = it->next[PL_MAIN];
}
else {
playlist_head[PL_MAIN] = it->next[PL_MAIN];
}
if (it->next[PL_MAIN]) {
it->next[PL_MAIN]->prev[PL_MAIN] = it->prev[PL_MAIN];
}
else {
playlist_tail[PL_MAIN] = it->prev[PL_MAIN];
}
pl_item_free (it);
free (it);
return 0;
}
int
pl_getcount (void) {
return pl_count;
}
int
pl_getselcount (void) {
// FIXME: slow!
int cnt = 0;
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
if (it->selected) {
cnt++;
}
}
return cnt;
}
playItem_t *
pl_get_for_idx (int idx) {
playItem_t *it = playlist_head[PL_MAIN];
while (idx--) {
if (!it)
return NULL;
it = it->next[PL_MAIN];
}
return it;
}
int
pl_get_idx_of (playItem_t *it) {
playItem_t *c = playlist_head[PL_MAIN];
int idx = 0;
while (c && c != it) {
c = c->next[PL_MAIN];
idx++;
}
if (!c) {
return -1;
}
return idx;
}
int
pl_append_item (playItem_t *it) {
if (!playlist_tail[PL_MAIN]) {
playlist_tail[PL_MAIN] = playlist_head[PL_MAIN] = it;
}
else {
playlist_tail[PL_MAIN]->next[PL_MAIN] = it;
it->prev[PL_MAIN] = playlist_tail[PL_MAIN];
playlist_tail[PL_MAIN] = it;
}
pl_count++;
}
playItem_t *
pl_insert_item (playItem_t *after, playItem_t *it) {
if (!after) {
it->next[PL_MAIN] = playlist_head[PL_MAIN];
it->prev[PL_MAIN] = NULL;
if (playlist_head[PL_MAIN]) {
playlist_head[PL_MAIN]->prev[PL_MAIN] = it;
}
else {
playlist_tail[PL_MAIN] = it;
}
playlist_head[PL_MAIN] = it;
}
else {
it->prev[PL_MAIN] = after;
it->next[PL_MAIN] = after->next[PL_MAIN];
if (after->next[PL_MAIN]) {
after->next[PL_MAIN]->prev[PL_MAIN] = it;
}
after->next[PL_MAIN] = it;
if (after == playlist_tail[PL_MAIN]) {
playlist_tail[PL_MAIN] = it;
}
}
pl_count++;
// shuffle
it->shufflerating = rand ();
it->played = 0;
return it;
}
void
pl_item_copy (playItem_t *out, playItem_t *it) {
out->fname = strdup (it->fname);
out->decoder = it->decoder;
out->tracknum = it->tracknum;
out->timestart = it->timestart;
out->timeend = it->timeend;
out->duration = it->duration;
out->startoffset = it->startoffset;
out->endoffset = it->endoffset;
out->shufflerating = it->shufflerating;
out->filetype = it->filetype;
out->next[PL_MAIN] = it->next[PL_MAIN];
out->prev[PL_MAIN] = it->prev[PL_MAIN];
out->next[PL_SEARCH] = it->next[PL_SEARCH];
out->prev[PL_SEARCH] = it->prev[PL_SEARCH];
// copy metainfo
metaInfo_t *prev = NULL;
metaInfo_t *meta = it->meta;
while (meta) {
metaInfo_t *m = malloc (sizeof (metaInfo_t));
m->key = meta->key;
m->value = strdup (meta->value);
m->next = NULL;
if (prev) {
prev->next = m;
}
else {
out->meta = m;
}
prev = m;
meta = meta->next;
}
}
playItem_t *
pl_item_alloc (void) {
playItem_t *it = malloc (sizeof (playItem_t));
memset (it, 0, sizeof (playItem_t));
return it;
}
void
pl_item_free (playItem_t *it) {
if (it) {
if (it->fname) {
free (it->fname);
}
while (it->meta) {
metaInfo_t *m = it->meta;
it->meta = m->next;
free (m->value);
free (m);
}
memset (it, 0, sizeof (playItem_t));
}
}
int
pl_set_current (playItem_t *it) {
int ret = 0;
int from = pl_get_idx_of (playlist_current_ptr);
int to = it ? pl_get_idx_of (it) : -1;
if (playlist_current.decoder) {
plug_trigger_event (DB_EV_SONGFINISHED);
}
codec_lock ();
if (playlist_current.decoder) {
playlist_current.decoder->free ();
}
pl_item_free (&playlist_current);
playlist_current_ptr = it;
if (it && it->decoder) {
// don't do anything on fail, streamer will take care
ret = it->decoder->init (DB_PLAYITEM (it));
if (ret < 0) {
// pl_item_free (&playlist_current);
// playlist_current_ptr = NULL;
// return ret;
//// it->decoder->info.samplesPerSecond = -1;
}
}
if (playlist_current_ptr) {
streamer_reset (0);
}
if (it) {
it->played = 1;
it->started_timestamp = time (NULL);
pl_item_copy (&playlist_current, it);
}
codec_unlock ();
if (it) {
plug_trigger_event (DB_EV_SONGSTARTED);
}
messagepump_push (M_SONGCHANGED, 0, from, to);
return ret;
}
int
pl_prevsong (void) {
if (!playlist_head[PL_MAIN]) {
streamer_set_nextsong (-2, 1);
return 0;
}
if (pl_order == 1) { // shuffle
if (!playlist_current_ptr) {
return pl_nextsong (1);
}
else {
playlist_current_ptr->played = 0;
// find already played song with maximum shuffle rating below prev song
int rating = playlist_current_ptr->shufflerating;
playItem_t *pmax = NULL; // played maximum
playItem_t *amax = NULL; // absolute maximum
playItem_t *i = NULL;
for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) {
if (i != playlist_current_ptr && i->played && (!amax || i->shufflerating > amax->shufflerating)) {
amax = i;
}
if (i == playlist_current_ptr || i->shufflerating > rating || !i->played) {
continue;
}
if (!pmax || i->shufflerating > pmax->shufflerating) {
pmax = i;
}
}
playItem_t *it = pmax;
if (!it) {
// that means 1st in playlist, take amax
if (pl_loop_mode == 0) {
if (!amax) {
pl_reshuffle (NULL, &amax);
}
it = amax;
}
}
if (!it) {
return -1;
}
int r = pl_get_idx_of (it);
streamer_set_nextsong (r, 1);
return 0;
}
}
else if (pl_order == 0) { // linear
playItem_t *it = NULL;
if (playlist_current_ptr) {
it = playlist_current_ptr->prev[PL_MAIN];
}
if (!it) {
if (pl_loop_mode == 0) {
it = playlist_tail[PL_MAIN];
}
}
if (!it) {
return -1;
}
int r = pl_get_idx_of (it);
streamer_set_nextsong (r, 1);
return 0;
}
else if (pl_order == 2) { // random
pl_randomsong ();
}
return -1;
}
int
pl_nextsong (int reason) {
if (!playlist_head[PL_MAIN]) {
streamer_set_nextsong (-2, 1);
return 0;
}
if (pl_order == 1) { // shuffle
if (!playlist_current_ptr) {
// find minimal notplayed
playItem_t *pmin = NULL; // notplayed minimum
playItem_t *i = NULL;
for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) {
if (i->played) {
continue;
}
if (!pmin || i->shufflerating < pmin->shufflerating) {
pmin = i;
}
}
playItem_t *it = pmin;
if (!it) {
// all songs played, reshuffle and try again
if (pl_loop_mode == 0) { // loop
pl_reshuffle (&it, NULL);
}
}
if (!it) {
return -1;
}
int r = pl_get_idx_of (it);
streamer_set_nextsong (r, 1);
return 0;
}
else {
if (reason == 0 && pl_loop_mode == 2) { // song finished, loop mode is "loop 1 track"
int r = pl_get_idx_of (playlist_current_ptr);
streamer_set_nextsong (r, 1);
return 0;
}
// find minimal notplayed above current
int rating = playlist_current_ptr->shufflerating;
playItem_t *pmin = NULL; // notplayed minimum
playItem_t *i = NULL;
for (playItem_t *i = playlist_head[PL_MAIN]; i; i = i->next[PL_MAIN]) {
if (i->played || i->shufflerating < rating) {
continue;
}
if (!pmin || i->shufflerating < pmin->shufflerating) {
pmin = i;
}
}
playItem_t *it = pmin;
if (!it) {
// all songs played, reshuffle and try again
if (pl_loop_mode == 0) { // loop
pl_reshuffle (&it, NULL);
}
}
if (!it) {
return -1;
}
int r = pl_get_idx_of (it);
streamer_set_nextsong (r, 1);
return 0;
}
}
else if (pl_order == 0) { // linear
playItem_t *it = NULL;
if (playlist_current_ptr) {
if (reason == 0 && pl_loop_mode == 2) {
int r = pl_get_idx_of (playlist_current_ptr);
streamer_set_nextsong (r, 1);
return 0;
}
it = playlist_current_ptr->next[PL_MAIN];
}
if (!it) {
if (pl_loop_mode == 0) {
it = playlist_head[PL_MAIN];
}
else {
streamer_set_nextsong (-2, 1);
return 0;
}
}
if (!it) {
return -1;
}
int r = pl_get_idx_of (it);
streamer_set_nextsong (r, 1);
return 0;
}
else if (pl_order == 2) { // random
if (reason == 0 && pl_loop_mode == 2 && playlist_current_ptr) {
int r = pl_get_idx_of (playlist_current_ptr);
streamer_set_nextsong (r, 1);
return 0;
}
return pl_randomsong ();
}
return -1;
}
int
pl_randomsong (void) {
if (!pl_getcount ()) {
return -1;
}
int r = (float)rand ()/RAND_MAX * pl_getcount ();
streamer_set_nextsong (r, 1);
return 0;
}
void
pl_start_current (void) {
codec_lock ();
playItem_t *it = playlist_current_ptr;
if (it && it->decoder) {
// don't do anything on fail, streamer will take care
it->decoder->free ();
it->decoder->init (DB_PLAYITEM (it));
}
codec_unlock ();
}
void
pl_add_meta (playItem_t *it, const char *key, const char *value) {
// check if it's already set
metaInfo_t *m = it->meta;
while (m) {
if (!strcasecmp (key, m->key)) {
return;
}
m = m->next;
}
// add
char str[256];
if (!value || !*value) {
if (!strcasecmp (key, "title")) {
int len = 256;
// cut filename without path and extension
const char *pext = it->fname + strlen (it->fname) - 1;
while (pext >= it->fname && *pext != '.') {
pext--;
}
const char *pname = pext;
while (pname >= it->fname && *pname != '/') {
pname--;
}
if (*pname == '/') {
pname++;
}
strncpy (str, pname, pext-pname);
str[pext-pname] = 0;
value = str;
}
else {
return;
}
}
m = malloc (sizeof (metaInfo_t));
m->key = key;
m->value = strdup (value);
// strncpy (m->value, value, META_FIELD_SIZE-1);
// m->value[META_FIELD_SIZE-1] = 0;
m->next = it->meta;
it->meta = m;
}
void
pl_format_item_display_name (playItem_t *it, char *str, int len) {
const char *artist = pl_find_meta (it, "artist");
const char *title = pl_find_meta (it, "title");
if (!artist) {
artist = "Unknown artist";
}
if (!title) {
title = "Unknown title";
}
snprintf (str, len, "%s - %s", artist, title);
#if 0
// artist - title
const char *track = pl_find_meta (it, "track");
const char *artist = pl_find_meta (it, "artist");
const char *album = pl_find_meta (it, "album");
const char *title = pl_find_meta (it, "title");
if (*track == '?' && *album == '?' && *artist != '?' && *title != '?') {
snprintf (str, len, "%s - %s", artist, title);
}
else if (*artist != '?' && *track != '?' && *title != '?') {
snprintf (str, len, "%s. %s - %s", track, artist, title);
}
else if (*artist == '?' && *track != '?' && *album != '?') {
snprintf (str, len, "%s. %s", track, album);
}
else if (*artist != '?' && *track != '?' && *album != '?') {
snprintf (str, len, "%s. %s - %s", track, artist, album);
}
else if (*artist != '?' && *title != '?') {
snprintf (str, len, "%s - %s", artist, title);
}
else if (*artist != '?') {
snprintf (str, len, "%s", artist);
}
else if (*title != '?') {
snprintf (str, len, "%s", title);
}
else {
// cut filename without path and extension
char *pext = it->fname + strlen (it->fname) - 1;
while (pext >= it->fname && *pext != '.') {
pext--;
}
char *pname = pext;
while (pname >= it->fname && *pname != '/') {
pname--;
}
if (*pname == '/') {
pname++;
}
strncpy (str, pname, pext-pname);
str[pext-pname] = 0;
}
#endif
}
const char *
pl_find_meta (playItem_t *it, const char *key) {
metaInfo_t *m = it->meta;
while (m) {
if (!strcasecmp (key, m->key)) {
return m->value;
}
m = m->next;
}
return NULL;
}
void
pl_delete_selected (void) {
playItem_t *next = NULL;
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = next) {
next = it->next[PL_MAIN];
if (it->selected) {
pl_remove (it);
}
}
}
void
pl_crop_selected (void) {
playItem_t *next = NULL;
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = next) {
next = it->next[PL_MAIN];
if (!it->selected) {
pl_remove (it);
}
}
}
void
pl_set_order (int order) {
pl_order = order;
}
void
pl_set_loop_mode (int mode) {
pl_loop_mode = mode;
}
int
pl_save (const char *fname) {
const char magic[] = "DBPL";
uint8_t majorver = 1;
uint8_t minorver = 0;
FILE *fp = fopen (fname, "w+b");
if (!fp) {
return -1;
}
if (fwrite (magic, 1, 4, fp) != 4) {
goto save_fail;
}
if (fwrite (&majorver, 1, 1, fp) != 1) {
goto save_fail;
}
if (fwrite (&minorver, 1, 1, fp) != 1) {
goto save_fail;
}
uint32_t cnt = pl_count;
if (fwrite (&cnt, 1, 4, fp) != 4) {
goto save_fail;
}
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
uint16_t l;
uint8_t ll;
l = strlen (it->fname);
if (fwrite (&l, 1, 2, fp) != 2) {
goto save_fail;
}
if (fwrite (it->fname, 1, l, fp) != l) {
goto save_fail;
}
ll = strlen (it->decoder->id);
if (fwrite (&ll, 1, 1, fp) != 1) {
goto save_fail;
}
if (fwrite (it->decoder->id, 1, ll, fp) != ll) {
goto save_fail;
}
l = it->tracknum;
if (fwrite (&l, 1, 2, fp) != 2) {
goto save_fail;
}
if (fwrite (&it->timestart, 1, 4, fp) != 4) {
goto save_fail;
}
if (fwrite (&it->timeend, 1, 4, fp) != 4) {
goto save_fail;
}
if (fwrite (&it->duration, 1, 4, fp) != 4) {
goto save_fail;
}
uint8_t ft = it->filetype ? strlen (it->filetype) : 0;
if (fwrite (&ft, 1, 1, fp) != 1) {
goto save_fail;
}
if (ft) {
if (fwrite (it->filetype, 1, ft, fp) != ft) {
goto save_fail;
}
}
int16_t nm = 0;
metaInfo_t *m;
for (m = it->meta; m; m = m->next) {
nm++;
}
if (fwrite (&nm, 1, 2, fp) != 2) {
goto save_fail;
}
for (m = it->meta; m; m = m->next) {
l = strlen (m->key);
if (fwrite (&l, 1, 2, fp) != 2) {
goto save_fail;
}
if (l) {
if (fwrite (m->key, 1, l, fp) != l) {
goto save_fail;
}
}
l = strlen (m->value);
if (fwrite (&l, 1, 2, fp) != 2) {
goto save_fail;
}
if (l) {
if (fwrite (m->value, 1, l, fp) != l) {
goto save_fail;
}
}
}
}
fclose (fp);
return 0;
save_fail:
fclose (fp);
unlink (fname);
return -1;
}
int
pl_load (const char *fname) {
pl_free ();
DB_decoder_t **decoders = plug_get_decoder_list ();
uint8_t majorver = 1;
uint8_t minorver = 0;
FILE *fp = fopen (fname, "rb");
if (!fp) {
return -1;
}
char magic[4];
if (fread (magic, 1, 4, fp) != 4) {
goto load_fail;
}
if (strncmp (magic, "DBPL", 4)) {
goto load_fail;
}
if (fread (&majorver, 1, 1, fp) != 1) {
goto load_fail;
}
if (majorver != 1) {
goto load_fail;
}
if (fread (&minorver, 1, 1, fp) != 1) {
goto load_fail;
}
if (minorver != 0) {
goto load_fail;
}
uint32_t cnt;
if (fread (&cnt, 1, 4, fp) != 4) {
goto load_fail;
}
playItem_t *it = NULL;
for (uint32_t i = 0; i < cnt; i++) {
it = malloc (sizeof (playItem_t));
if (!it) {
goto load_fail;
}
memset (it, 0, sizeof (playItem_t));
uint16_t l;
// fname
if (fread (&l, 1, 2, fp) != 2) {
goto load_fail;
}
it->fname = malloc (l+1);
if (fread (it->fname, 1, l, fp) != l) {
goto load_fail;
}
it->fname[l] = 0;
// decoder
uint8_t ll;
if (fread (&ll, 1, 1, fp) != 1) {
goto load_fail;
}
if (ll >= 20) {
goto load_fail;
}
char decoder[20];
if (fread (decoder, 1, ll, fp) != ll) {
goto load_fail;
}
decoder[ll] = 0;
for (int c = 0; decoders[c]; c++) {
if (!strcmp (decoder, decoders[c]->id)) {
it->decoder = decoders[c];
}
}
if (!it->decoder) {
goto load_fail;
}
// tracknum
if (fread (&l, 1, 2, fp) != 2) {
goto load_fail;
}
it->tracknum = l;
// timestart
if (fread (&it->timestart, 1, 4, fp) != 4) {
goto load_fail;
}
// timeend
if (fread (&it->timeend, 1, 4, fp) != 4) {
goto load_fail;
}
// duration
if (fread (&it->duration, 1, 4, fp) != 4) {
goto load_fail;
}
// get const filetype string from decoder
uint8_t ft;
if (fread (&ft, 1, 1, fp) != 1) {
goto load_fail;
}
if (ft) {
char ftype[ft+1];
if (fread (ftype, 1, ft, fp) != ft) {
goto load_fail;
}
ftype[ft] = 0;
if (it->decoder && it->decoder->filetypes) {
for (int i = 0; it->decoder->filetypes[i]; i++) {
if (!strcasecmp (it->decoder->filetypes[i], ftype)) {
it->filetype = it->decoder->filetypes[i];
break;
}
}
}
}
// printf ("loading file %s\n", it->fname);
int16_t nm = 0;
if (fread (&nm, 1, 2, fp) != 2) {
goto load_fail;
}
for (int i = 0; i < nm; i++) {
char key[1024];
char value[1024];
const char *valid_keys[] = {
"title",
"artist",
"album",
"vendor",
"year",
"genre",
"comment",
"track",
"band",
NULL
};
if (fread (&l, 1, 2, fp) != 2) {
goto load_fail;
}
if (!l || l >= 1024) {
goto load_fail;
}
if (fread (key, 1, l, fp) != l) {
goto load_fail;
}
key[l] = 0;
if (fread (&l, 1, 2, fp) != 2) {
goto load_fail;
}
if (!l || l >= 1024) {
goto load_fail;
}
if (fread (value, 1, l, fp) != l) {
goto load_fail;
}
value[l] = 0;
//printf ("%s=%s\n", key, value);
for (int n = 0; valid_keys[n]; n++) {
if (!strcmp (valid_keys[n], key)) {
pl_add_meta (it, valid_keys[n], value);
break;
}
}
}
pl_insert_item (playlist_tail[PL_MAIN], it);
}
fclose (fp);
return 0;
load_fail:
fclose (fp);
if (it) {
pl_item_free (it);
}
pl_free ();
return -1;
}
void
pl_select_all (void) {
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
it->selected = 1;
}
}
void
pl_reshuffle (playItem_t **ppmin, playItem_t **ppmax) {
playItem_t *pmin = NULL;
playItem_t *pmax = NULL;
for (playItem_t *it = playlist_head[PL_MAIN]; it; it = it->next[PL_MAIN]) {
it->shufflerating = rand ();
if (!pmin || it->shufflerating < pmin->shufflerating) {
pmin = it;
}
if (!pmax || it->shufflerating > pmax->shufflerating) {
pmax = it;
}
it->played = 0;
}
if (ppmin) {
*ppmin = pmin;
}
if (ppmax) {
*ppmax = pmax;
}
}