/* DeaDBeeF - ultimate music player for GNU/Linux systems with X11 Copyright (C) 2009-2011 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __linux__ #define _POSIX_C_SOURCE #endif #include #include #include "gettext.h" #include "playlist.h" #include "streamer.h" #include "messagepump.h" #include "plugins.h" #include "junklib.h" #include "vfs.h" #include "conf.h" #include "utf8.h" #include "common.h" #include "threading.h" #include "metacache.h" #include "volume.h" #include "pltmeta.h" #define DISABLE_LOCKING 0 #define DEBUG_LOCKING 0 // file format revision history // 1.1->1.2 changelog: // added flags field // 1.0->1.1 changelog: // added sample-accurate seek positions for sub-tracks // 1.1->1.2 changelog: // added flags field // 1.2->1.3 changelog: // removed legacy data used for compat with 0.4.4 // note: ddb-0.5.0 should keep using 1.2 playlist format // 1.3 support is designed for transition to ddb-0.6.0 #define PLAYLIST_MAJOR_VER 1 #define PLAYLIST_MINOR_VER 2 #if (PLAYLIST_MINOR_VER<2) #error writing playlists in format <1.2 is not supported #endif //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) #define SKIP_BLANK_CUE_TRACKS 0 #define min(x,y) ((x)<(y)?(x):(y)) #define PLAYQUEUE_SIZE 100 static playItem_t *playqueue[100]; static int playqueue_count = 0; static int playlists_count = 0; static playlist_t *playlists_head = NULL; static playlist_t *playlist = NULL; // current playlist static int plt_loading = 0; // disable sending event about playlist switch, config regen, etc #if !DISABLE_LOCKING static uintptr_t mutex; static uintptr_t mutex_plt; #endif #define LOCK {pl_lock();} #define UNLOCK {pl_unlock();} // used at startup to prevent crashes static playlist_t dummy_playlist = { .refc = 1 }; static int pl_order; // mirrors "playback.order" config variable static int no_remove_notify; static playlist_t *addfiles_playlist; // current playlist for adding files/folders; set in pl_add_files_begin void pl_set_order (int order) { if (pl_order != order && (pl_order == PLAYBACK_ORDER_SHUFFLE_TRACKS || PLAYBACK_ORDER_SHUFFLE_ALBUMS)) { pl_order = order; for (playlist_t *plt = playlists_head; plt; plt = plt->next) { plt_reshuffle (plt, NULL, NULL); } } } int pl_get_order (void) { return pl_order; } int pl_init (void) { playlist = &dummy_playlist; #if !DISABLE_LOCKING mutex = mutex_create (); mutex_plt = mutex_create (); #endif return 0; } void pl_free (void) { trace ("pl_free\n"); LOCK; pl_playqueue_clear (); plt_loading = 1; while (playlists_head) { for (playItem_t *it = playlists_head->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { if (it->_refc > 1) { fprintf (stderr, "\033[0;31mWARNING: playitem %p %s has refc=%d at delete time\033[37;0m\n", it, pl_find_meta (it, ":URI"), it->_refc); } } plt_remove (0); } plt_loading = 0; UNLOCK; #if !DISABLE_LOCKING if (mutex) { mutex_free (mutex); mutex = 0; } if (mutex_plt) { mutex_free (mutex_plt); mutex_plt = 0; } #endif } #if DEBUG_LOCKING int pl_lock_cnt = 0; #endif //pthread_t pl_lock_tid = 0; void pl_lock (void) { #if !DISABLE_LOCKING mutex_lock (mutex); //pl_lock_tid = pthread_self(); #if DEBUG_LOCKING pl_lock_cnt++; printf ("pcnt: %d\n", pl_lock_cnt); #endif #endif } void pl_unlock (void) { #if !DISABLE_LOCKING mutex_unlock (mutex); #if DEBUG_LOCKING pl_lock_cnt--; printf ("pcnt: %d\n", pl_lock_cnt); #endif #endif } static void pl_item_free (playItem_t *it); static void plt_gen_conf (void) { if (plt_loading) { return; } LOCK; int cnt = plt_get_count (); int i; conf_remove_items ("playlist.tab."); playlist_t *p = playlists_head; for (i = 0; i < cnt; i++, p = p->next) { char s[100]; snprintf (s, sizeof (s), "playlist.tab.%02d", i); conf_set_str (s, p->title); } UNLOCK; } playlist_t * plt_get_list (void) { return playlists_head; } playlist_t * plt_get_curr (void) { LOCK; playlist_t *plt = playlist; if (plt) { plt_ref (plt); assert (plt->refc > 1); } UNLOCK; return plt; } playlist_t * plt_get_for_idx (int idx) { LOCK; playlist_t *p = playlists_head; for (int i = 0; p && i <= idx; i++, p = p->next) { if (i == idx) { plt_ref (p); UNLOCK; return p; } } UNLOCK; return NULL; } void plt_ref (playlist_t *plt) { LOCK; plt->refc++; UNLOCK; } void plt_unref (playlist_t *plt) { LOCK; plt->refc--; if (plt->refc < 0) { trace ("\033[0;31mplaylist: bad refcount on playlist %p (%s)\033[37;0m\n", plt, plt->title); } if (plt->refc <= 0) { plt_free (plt); } UNLOCK; } int plt_get_count (void) { return playlists_count; } playItem_t * plt_get_head (int plt) { playlist_t *p = playlists_head; for (int i = 0; p && i <= plt; i++, p = p->next) { if (i == plt) { if (p->head[PL_MAIN]) { pl_item_ref (p->head[PL_MAIN]); } return p->head[PL_MAIN]; } } return NULL; } int plt_get_sel_count (int plt) { playlist_t *p = playlists_head; for (int i = 0; p && i <= plt; i++, p = p->next) { if (i == plt) { int cnt = 0; LOCK; for (playItem_t *it = p->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { if (it->selected) { cnt++; } } UNLOCK; return cnt; } } return 0; } int plt_add (int before, const char *title) { assert (before >= 0); trace ("plt_add\n"); if (plt_get_count () >= 100) { fprintf (stderr, "can't create more than 100 playlists. sorry.\n"); return -1; } playlist_t *plt = malloc (sizeof (playlist_t)); memset (plt, 0, sizeof (playlist_t)); plt->refc = 1; plt->title = strdup (title); plt_modified (plt); LOCK; playlist_t *p_before = NULL; playlist_t *p_after = playlists_head; int i; for (i = 0; i < before; i++) { if (i >= before+1) { break; } p_before = p_after; if (p_after) { p_after = p_after->next; } else { p_after = playlists_head; } } if (p_before) { p_before->next = plt; } else { playlists_head = plt; } plt->next = p_after; playlists_count++; if (!playlist) { playlist = plt; if (!plt_loading) { // shift files for (int i = playlists_count-1; i >= before+1; i--) { char path1[PATH_MAX]; char path2[PATH_MAX]; if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } int err = rename (path1, path2); if (err != 0) { fprintf (stderr, "playlist rename failed: %s\n", strerror (errno)); } } } } UNLOCK; plt_gen_conf (); if (!plt_loading) { pl_save_n (before); conf_save (); messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); } return playlists_count-1; } // NOTE: caller must ensure that configuration is saved after that call void plt_remove (int plt) { int i; assert (plt >= 0 && plt < playlists_count); LOCK; // find playlist and notify streamer playlist_t *p = playlists_head; playlist_t *prev = NULL; for (i = 0; p && i < plt; i++) { prev = p; p = p->next; } if (!plt_loading) { // move files (will decrease number of files by 1) for (int i = plt+1; i < playlists_count; i++) { char path1[PATH_MAX]; char path2[PATH_MAX]; if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i-1) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path2)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } int err = rename (path2, path1); if (err != 0) { fprintf (stderr, "playlist rename failed: %s\n", strerror (errno)); } } } if (!plt_loading && (playlists_head && !playlists_head->next)) { trace ("warning: deleting last playlist\n"); pl_clear (); free (playlist->title); playlist->title = strdup (_("Default")); UNLOCK; plt_gen_conf (); conf_save (); pl_save_n (0); messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); return; } if (i != plt) { trace ("plt_remove %d failed\n", i); } if (p) { if (!prev) { playlists_head = p->next; } else { prev->next = p->next; } } playlist_t *next = p->next; playlist_t *old = playlist; playlist = p; playlist = old; if (p == playlist) { if (next) { playlist = next; } else { playlist = prev ? prev : playlists_head; } } plt_unref (p); playlists_count--; UNLOCK; plt_gen_conf (); conf_save (); if (!plt_loading) { messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); } } int plt_find (const char *name) { playlist_t *p = playlists_head; int i = -1; for (i = 0; p; i++, p = p->next) { if (!strcmp (p->title, name)) { return i; } } return -1; } void plt_set_curr (playlist_t *plt) { LOCK; if (plt != playlist) { playlist = plt; if (!plt_loading) { messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); conf_set_int ("playlist.current", plt_get_curr_idx ()); conf_save (); } } UNLOCK; } void plt_set_curr_idx (int plt) { int i; LOCK; playlist_t *p = playlists_head; for (i = 0; p && p->next && i < plt; i++) { p = p->next; } if (p != playlist) { playlist = p; if (!plt_loading) { messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); conf_set_int ("playlist.current", plt); conf_save (); } } UNLOCK; } int plt_get_curr_idx(void) { int i; LOCK; playlist_t *p = playlists_head; for (i = 0; p && i < playlists_count; i++) { if (p == playlist) { UNLOCK; return i; } p = p->next; } UNLOCK; return -1; } int plt_get_idx_of (playlist_t *plt) { int i; LOCK; playlist_t *p = playlists_head; for (i = 0; p && i < playlists_count; i++) { if (p == plt) { UNLOCK; return i; } p = p->next; } UNLOCK; return -1; } int plt_get_title (playlist_t *p, char *buffer, int bufsize) { int i; LOCK; if (!buffer) { int l = strlen (p->title); UNLOCK; return l; } strncpy (buffer, p->title, bufsize); buffer[bufsize-1] = 0; UNLOCK; return 0; } int plt_set_title (playlist_t *p, const char *title) { int i; LOCK; free (p->title); p->title = strdup (title); plt_gen_conf (); UNLOCK; conf_save (); if (!plt_loading) { messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); } return 0; } void plt_modified (playlist_t *plt) { pl_lock (); plt->modification_idx++; pl_unlock (); } int plt_get_modification_idx (playlist_t *plt) { pl_lock (); int idx = plt->modification_idx; pl_unlock (); return idx; } void plt_free (playlist_t *plt) { LOCK; streamer_notify_playlist_deleted (plt); plt_clear (plt); free (plt->title); while (plt->meta) { DB_metaInfo_t *m = plt->meta; plt->meta = m->next; metacache_remove_string (m->key); metacache_remove_string (m->value); free (m); } free (plt); UNLOCK; } void plt_move (int from, int to) { if (from == to) { return; } trace ("plt_move %d -> %d\n", from, to); int i; LOCK; playlist_t *p = playlists_head; playlist_t *pfrom = NULL; playlist_t *prev = NULL; playlist_t *ins = NULL; // move 'from' to temp file char path1[PATH_MAX]; char temp[PATH_MAX]; if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, from) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); UNLOCK; return; } if (snprintf (temp, sizeof (temp), "%s/playlists/temp.dbpl", dbconfdir) > sizeof (temp)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); UNLOCK; return; } trace ("will rename %s->%s\n", path1, temp); struct stat st; int err = stat (path1, &st); if (!err) { trace ("rename %s->%s\n", path1, temp); int err = rename (path1, temp); if (err != 0) { fprintf (stderr, "playlist rename %s->%s failed: %s\n", path1, temp, strerror (errno)); UNLOCK; return; } } // remove 'from' from list for (i = 0; p && i <= from; i++) { if (i == from) { pfrom = p; if (prev) { prev->next = p->next; } else { playlists_head = p->next; } break; } prev = p; p = p->next; } // shift files to fill the gap for (int i = from; i < playlists_count-1; i++) { char path2[PATH_MAX]; if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } trace ("will rename %s->%s\n", path2, path1); int err = stat (path2, &st); if (!err) { trace ("rename %s->%s\n", path2, path1); int err = rename (path2, path1); if (err != 0) { fprintf (stderr, "playlist rename %s->%s failed: %s\n", path2, path1, strerror (errno)); } } } // open new gap for (int i = playlists_count-2; i >= to; i--) { char path2[PATH_MAX]; if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } if (snprintf (path2, sizeof (path2), "%s/playlists/%d.dbpl", dbconfdir, i+1) > sizeof (path2)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); continue; } trace ("will rename %s->%s\n", path1, path2); int err = stat (path1, &st); if (!err) { trace ("rename %s->%s\n", path1, path2); int err = rename (path1, path2); if (err != 0) { fprintf (stderr, "playlist rename %s->%s failed: %s\n", path1, path2, strerror (errno)); } } } // move temp file if (snprintf (path1, sizeof (path1), "%s/playlists/%d.dbpl", dbconfdir, to) > sizeof (path1)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); } else { int err = stat (temp, &st); if (!err) { trace ("move %s->%s\n", temp, path1); int err = rename (temp, path1); if (err != 0) { fprintf (stderr, "playlist rename %s->%s failed: %s\n", temp, path1, strerror (errno)); } } } // move pointers if (to == 0) { // insert 'from' as head pfrom->next = playlists_head; playlists_head = pfrom; } else { // insert 'from' in the middle p = playlists_head; for (i = 0; p && i < to; i++) { if (i == to-1) { playlist_t *next = p->next; p->next = pfrom; pfrom->next = next; break; } prev = p; p = p->next; } } UNLOCK; plt_gen_conf (); conf_save (); } void plt_clear (playlist_t *plt) { LOCK; while (plt->head[PL_MAIN]) { pl_remove_item (plt->head[PL_MAIN]); } plt->current_row[PL_MAIN] = -1; plt->current_row[PL_SEARCH] = -1; plt_modified (plt); UNLOCK; } void pl_clear (void) { plt_clear (playlist); } static const uint8_t * pl_str_skipspaces (const uint8_t *p, const uint8_t *end) { while (p < end && *p <= ' ') { p++; } return p; } static const uint8_t * pl_cue_skipspaces (const uint8_t *p) { while (*p && *p <= ' ') { p++; } return p; } static void pl_get_qvalue_from_cue (const uint8_t *p, int sz, char *out) { char *str = out; if (*p == 0) { *out = 0; return; } p = pl_cue_skipspaces (p); if (*p == 0) { *out = 0; return; } if (*p == '"') { p++; p = pl_cue_skipspaces (p); while (*p && *p != '"' && sz > 1) { sz--; *out++ = *p++; } *out = 0; } else { while (*p && *p > 0x20) { sz--; *out++ = *p++; } *out = 0; } const char *charset = junk_detect_charset (str); if (!charset) { return; } // recode int l = strlen (str); char in[l+1]; memcpy (in, str, l+1); junk_recode (in, l, str, sz, charset); } static void pl_get_value_from_cue (const char *p, int sz, char *out) { while (*p >= ' ' && sz > 1) { sz--; *out++ = *p++; } while (out > p && (*(out-1) == 0x20 || *(out-1) == 0x8)) { out--; } *out = 0; } static float pl_cue_parse_time (const char *p) { char *endptr; long mins = strtol(p, &endptr, 10); if (endptr - p < 1 || *endptr != ':') { return -1; } p = endptr + 1; long sec = strtol(p, &endptr, 10); if (endptr - p != 2 || *endptr != ':') { return -1; } p = endptr + 1; long frm = strtol(p, &endptr, 10); if (endptr - p != 2 || *endptr != '\0') { return -1; } return mins * 60.f + sec + frm / 75.f; } static playItem_t * pl_process_cue_track (playItem_t *after, const char *fname, playItem_t **prev, char *track, char *index00, char *index01, char *pregap, char *title, char *albumperformer, char *performer, char *albumtitle, char *genre, char *date, char *replaygain_album_gain, char *replaygain_album_peak, char *replaygain_track_gain, char *replaygain_track_peak, const char *decoder_id, const char *ftype, int samplerate) { if (!track[0]) { trace ("pl_process_cue_track: invalid track (file=%s, title=%s)\n", fname, title); return after; } if (!index00[0] && !index01[0]) { trace ("pl_process_cue_track: invalid index (file=%s, title=%s, track=%s)\n", fname, title, track); return after; } #if SKIP_BLANK_CUE_TRACKS if (!title[0]) { trace ("pl_process_cue_track: invalid title (file=%s, title=%s, track=%s)\n", fname, title, track); return after; } #endif // fix track number char *p = track; while (*p && isdigit (*p)) { p++; } *p = 0; // check that indexes have valid timestamps //float f_index00 = index00[0] ? pl_cue_parse_time (index00) : 0; float f_index01 = index01[0] ? pl_cue_parse_time (index01) : 0; float f_pregap = pregap[0] ? pl_cue_parse_time (pregap) : 0; if (*prev) { float prevtime = 0; if (pregap[0] && index01[0]) { // PREGAP command prevtime = f_index01 - f_pregap; } // else if (index00[0] && index01[0]) { // // pregap in index 00 // prevtime = f_index00; // } else if (index01[0]) { // no pregap prevtime = f_index01; } else { trace ("pl_process_cue_track: invalid pregap or index01 (pregap=%s, index01=%s)\n", pregap, index01); return after; } (*prev)->endsample = (prevtime * samplerate) - 1; pl_set_item_duration (*prev, (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate); if (pl_get_item_duration (*prev) < 0) { // might be bad cuesheet file, try to fix trace ("cuesheet seems to be corrupted, trying workaround\n"); //trace ("[bad:] calc endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->endsample, prevtime, samplerate, (*prev)->duration); prevtime = f_index01; (*prev)->endsample = (prevtime * samplerate) - 1; float dur = (float)((*prev)->endsample - (*prev)->startsample + 1) / samplerate; pl_set_item_duration (*prev, dur); if (dur > 0) { trace ("success :-D\n"); } else { trace ("fail :-(\n"); } } trace ("startsample=%d, endsample=%d, prevtime=%f, samplerate=%d, prev track duration=%f\n", (*prev)->startsample, (*prev)->endsample, prevtime, samplerate, (*prev)->_duration); } // non-compliant hack to handle tracks which only store pregap info if (!index01[0]) { *prev = NULL; trace ("pl_process_cue_track: invalid index01 (pregap=%s, index01=%s)\n", pregap, index01); return after; } playItem_t *it = pl_item_alloc_init (fname, decoder_id); pl_set_meta_int (it, ":TRACKNUM", atoi (track)); it->startsample = index01[0] ? f_index01 * samplerate : 0; it->endsample = -1; // will be filled by next read, or by decoder pl_replace_meta (it, ":FILETYPE", ftype); if (performer[0]) { pl_add_meta (it, "artist", performer); if (albumperformer[0]) { pl_add_meta (it, "album artist", albumperformer); } } else if (albumperformer[0]) { pl_add_meta (it, "artist", albumperformer); } if (albumtitle[0]) { pl_add_meta (it, "album", albumtitle); } if (track[0]) { pl_add_meta (it, "track", track); } if (title[0]) { pl_add_meta (it, "title", title); } if (genre[0]) { pl_add_meta (it, "genre", genre); } if (date[0]) { pl_add_meta (it, "year", date); } if (replaygain_album_gain[0]) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (replaygain_album_gain)); } if (replaygain_album_peak[0]) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (replaygain_album_peak)); } if (replaygain_track_gain[0]) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (replaygain_track_gain)); } if (replaygain_track_peak[0]) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (replaygain_track_peak)); } it->_flags |= DDB_IS_SUBTRACK | DDB_TAG_CUESHEET; after = pl_insert_item (after, it); pl_item_unref (it); *prev = it; return it; } playItem_t * pl_insert_cue_from_buffer (playItem_t *after, playItem_t *origin, const uint8_t *buffer, int buffersize, int numsamples, int samplerate) { LOCK; playItem_t *ins = after; trace ("pl_insert_cue_from_buffer numsamples=%d, samplerate=%d\n", numsamples, samplerate); char albumperformer[256] = ""; char performer[256] = ""; char albumtitle[256] = ""; char genre[256] = ""; char date[256] = ""; char track[256] = ""; char title[256] = ""; char pregap[256] = ""; char index00[256] = ""; char index01[256] = ""; char replaygain_album_gain[256] = ""; char replaygain_album_peak[256] = ""; char replaygain_track_gain[256] = ""; char replaygain_track_peak[256] = ""; 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 buffersize -= p-buffer; buffer = p; 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); // trace ("cue line: %s\n", p); if (!strncmp (p, "PERFORMER ", 10)) { if (!track[0]) { pl_get_qvalue_from_cue (p + 10, sizeof (albumperformer), albumperformer); } else { pl_get_qvalue_from_cue (p + 10, sizeof (performer), performer); } trace ("cue: got performer: %s\n", performer); } else if (!strncmp (p, "TITLE ", 6)) { if (str[0] > ' ' && !albumtitle[0]) { pl_get_qvalue_from_cue (p + 6, sizeof (albumtitle), albumtitle); trace ("cue: got albumtitle: %s\n", albumtitle); } else { pl_get_qvalue_from_cue (p + 6, sizeof (title), title); trace ("cue: got title: %s\n", title); } } else if (!strncmp (p, "REM GENRE ", 10)) { pl_get_qvalue_from_cue (p + 10, sizeof (genre), genre); } else if (!strncmp (p, "REM DATE ", 9)) { pl_get_value_from_cue (p + 9, sizeof (date), date); } else if (!strncmp (p, "TRACK ", 6)) { trace ("cue: adding track: %s %s %s\n", pl_find_meta (origin, ":URI"), title, track); if (title[0]) { // add previous track const char *filetype = pl_find_meta (origin, ":FILETYPE"); after = pl_process_cue_track (after, pl_find_meta (origin, ":URI"), &prev, track, index00, index01, pregap, title, albumperformer, performer, albumtitle, genre, date, replaygain_album_gain, replaygain_album_peak, replaygain_track_gain, replaygain_track_peak, pl_find_meta (origin, ":DECODER"), filetype, samplerate); trace ("cue: added %p (%p)\n", after); } track[0] = 0; title[0] = 0; pregap[0] = 0; index00[0] = 0; index01[0] = 0; replaygain_track_gain[0] = 0; replaygain_track_peak[0] = 0; performer[0] = 0; pl_get_value_from_cue (p + 6, sizeof (track), track); trace ("cue: got track: %s\n", track); } else if (!strncmp (p, "REM REPLAYGAIN_ALBUM_GAIN ", 26)) { pl_get_value_from_cue (p + 26, sizeof (replaygain_album_gain), replaygain_album_gain); } else if (!strncmp (p, "REM REPLAYGAIN_ALBUM_PEAK ", 26)) { pl_get_value_from_cue (p + 26, sizeof (replaygain_album_peak), replaygain_album_peak); } else if (!strncmp (p, "REM REPLAYGAIN_TRACK_GAIN ", 26)) { pl_get_value_from_cue (p + 26, sizeof (replaygain_track_gain), replaygain_track_gain); } else if (!strncmp (p, "REM REPLAYGAIN_TRACK_PEAK ", 26)) { pl_get_value_from_cue (p + 26, sizeof (replaygain_track_peak), replaygain_track_peak); } else if (!strncmp (p, "PREGAP ", 7)) { pl_get_value_from_cue (p + 7, sizeof (pregap), pregap); } else if (!strncmp (p, "INDEX 00 ", 9)) { pl_get_value_from_cue (p + 9, sizeof (index00), index00); } else if (!strncmp (p, "INDEX 01 ", 9)) { pl_get_value_from_cue (p + 9, sizeof (index01), index01); } else { // fprintf (stderr, "got unknown line:\n%s\n", p); } } if (!title[0]) { UNLOCK; return NULL; } const char *filetype = pl_find_meta (origin, ":FILETYPE"); after = pl_process_cue_track (after, pl_find_meta (origin, ":URI"), &prev, track, index00, index01, pregap, title, albumperformer, performer, albumtitle, genre, date, replaygain_album_gain, replaygain_album_peak, replaygain_track_gain, replaygain_track_peak, pl_find_meta (origin, ":DECODER"), filetype, samplerate); if (after) { trace ("last track endsample: %d\n", numsamples-1); after->endsample = numsamples-1; pl_set_item_duration (after, (float)(after->endsample - after->startsample + 1) / samplerate); } // add caller ref if (after && after != ins) { pl_item_ref (after); } // copy metadata from embedded tags playItem_t *first = ins ? ins->next[PL_MAIN] : playlist->head[PL_MAIN]; uint32_t f = pl_get_item_flags (origin); f |= DDB_TAG_CUESHEET | DDB_IS_SUBTRACK; if (pl_find_meta (origin, "cuesheet")) { f |= DDB_HAS_EMBEDDED_CUESHEET; } pl_set_item_flags (origin, f); pl_items_copy_junk (origin, first, after); UNLOCK; return after; } playItem_t * pl_insert_cue (playItem_t *after, playItem_t *origin, int numsamples, int samplerate) { trace ("pl_insert_cue numsamples=%d, samplerate=%d\n", numsamples, samplerate); const char *fname = pl_find_meta (origin, ":URI"); 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, origin, buf, sz, numsamples, samplerate); } playItem_t * pl_insert_m3u (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { trace ("enter pl_insert_m3u\n"); // skip all empty lines and comments DB_FILE *fp = vfs_fopen (fname); if (!fp) { trace ("failed to open file %s\n", fname); return NULL; } int sz = vfs_fgetlength (fp); if (sz > 1024*1024) { vfs_fclose (fp); trace ("file %s is too large to be a playlist\n", fname); return NULL; } trace ("loading m3u...\n"); uint8_t buffer[sz]; vfs_fread (buffer, 1, sz, fp); vfs_fclose (fp); const uint8_t *p = buffer; const uint8_t *end = buffer+sz; LOCK; while (p < end) { p = pl_str_skipspaces (p, end); if (p >= end) { break; } if (*p == '#') { while (p < end && *p >= 0x20) { p++; } if (p >= end) { break; } continue; } const uint8_t *e = p; while (e < end && *e >= 0x20) { e++; } int n = e-p; uint8_t nm[n+1]; memcpy (nm, p, n); nm[n] = 0; trace ("pl_insert_m3u: adding file %s\n", nm); playItem_t *it = pl_insert_file (after, nm, pabort, cb, user_data); if (it) { after = it; } if (pabort && *pabort) { UNLOCK; return after; } p = e; if (p >= end) { break; } } UNLOCK; trace ("leave pl_insert_m3u\n"); return after; } // that has to be opened with vfs functions to allow loading from http, as // referenced from M3U. playItem_t * pl_insert_pls (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { DB_FILE *fp = vfs_fopen (fname); if (!fp) { trace ("failed to open file %s\n", fname); return NULL; } int sz = vfs_fgetlength (fp); if (sz > 1024*1024) { vfs_fclose (fp); trace ("file %s is too large to be a playlist\n", fname); return NULL; } if (sz < 10) { vfs_fclose (fp); trace ("file %s is too small to be a playlist (%d)\n", fname, sz); return NULL; } vfs_rewind (fp); uint8_t buffer[sz]; vfs_fread (buffer, 1, sz, fp); vfs_fclose (fp); // 1st line must be "[playlist]" const uint8_t *p = buffer; const uint8_t *end = buffer+sz; if (strncasecmp (p, "[playlist]", 10)) { trace ("file %s doesn't begin with [playlist]\n", fname); return NULL; } p += 10; p = pl_str_skipspaces (p, end); if (p >= end) { trace ("file %s finished before numberofentries had been read\n", fname); return NULL; } // fetch all tracks char url[1024] = ""; char title[1024] = ""; char length[20] = ""; int lastidx = -1; LOCK; while (p < end) { p = pl_str_skipspaces (p, end); if (p >= end) { break; } if (end-p < 6) { break; } const uint8_t *e; int n; if (!strncasecmp (p, "numberofentries=", 16) || !strncasecmp (p, "version=", 8)) { while (p < end && *p >= 0x20) { p++; } continue; } else if (!strncasecmp (p, "file", 4)) { int idx = atoi (p + 4); if (url[0] && idx != lastidx && lastidx != -1) { // add track playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data); if (it) { after = it; pl_set_item_duration (it, atoi (length)); if (title[0]) { pl_delete_all_meta (it); pl_add_meta (it, "title", title); } } if (pabort && *pabort) { UNLOCK; return after; } url[0] = 0; title[0] = 0; length[0] = 0; } lastidx = idx; p += 4; while (p < end && *p != '=') { p++; } p++; if (p >= end) { break; } e = p; while (e < end && *e >= 0x20) { e++; } n = e-p; n = min (n, sizeof (url)-1); memcpy (url, p, n); url[n] = 0; trace ("url: %s\n", url); p = ++e; } else if (!strncasecmp (p, "title", 5)) { int idx = atoi (p + 5); if (url[0] && idx != lastidx && lastidx != -1) { // add track playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data); if (it) { after = it; pl_set_item_duration (it, atoi (length)); if (title[0]) { pl_delete_all_meta (it); pl_add_meta (it, "title", title); } } if (pabort && *pabort) { UNLOCK; return after; } url[0] = 0; title[0] = 0; length[0] = 0; } lastidx = idx; p += 5; while (p < end && *p != '=') { p++; } p++; if (p >= end) { break; } e = p; while (e < end && *e >= 0x20) { e++; } n = e-p; n = min (n, sizeof (title)-1); memcpy (title, p, n); title[n] = 0; trace ("title: %s\n", title); p = ++e; } else if (!strncasecmp (p, "length", 6)) { int idx = atoi (p + 6); if (url[0] && idx != lastidx && lastidx != -1) { // add track playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data); if (it) { after = it; if (title[0]) { pl_delete_all_meta (it); pl_add_meta (it, "title", title); } } if (pabort && *pabort) { UNLOCK; return after; } url[0] = 0; title[0] = 0; length[0] = 0; } lastidx = idx; p += 6; // skip = while (p < end && *p != '=') { p++; } p++; if (p >= end) { break; } e = p; while (e < end && *e >= 0x20) { e++; } n = e-p; n = min (n, sizeof (length)-1); memcpy (length, p, n); } else { trace ("invalid entry in pls file: %s\n", p); break; } while (e < end && *e < 0x20) { e++; } p = e; } if (url[0]) { playItem_t *it = pl_insert_file (after, url, pabort, cb, user_data); if (it) { after = it; if (title[0]) { pl_delete_all_meta (it); pl_add_meta (it, "title", title); } } } UNLOCK; return after; } static int follow_symlinks = 0; static int ignore_archives = 0; playItem_t * pl_insert_dir_int (DB_vfs_t *vfs, playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data); playItem_t * pl_insert_file (playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { trace ("count: %d\n", playlist->count[PL_MAIN]); trace ("pl_insert_file %s\n", fname); if (!fname || !(*fname)) { return NULL; } // check if that is supported container format if (!ignore_archives) { DB_vfs_t **vfsplugs = plug_get_vfs_list (); for (int i = 0; vfsplugs[i]; i++) { if (vfsplugs[i]->is_container) { trace ("%s cont test\n", fname); if (vfsplugs[i]->is_container (fname)) { trace ("inserting %s via vfs %s\n", fname, vfsplugs[i]->plugin.id); playItem_t *it = pl_insert_dir_int (vfsplugs[i], after, fname, pabort, cb, user_data); if (it) { return it; } } } } } // detect decoder const char *eol = strrchr (fname, '.'); if (!eol) { return NULL; } eol++; const char *fn = strrchr (fname, '/'); if (!fn) { fn = fname; } else { fn++; } // detect pls/m3u files // they must be handled before checking for http://, // so that remote playlist files referenced from other playlist files could // be loaded correctly if (!strncmp (eol, "m3u", 3) || !strncmp (eol, "m3u8", 4)) { return pl_insert_m3u (after, fname, pabort, cb, user_data); } else if (!strncmp (eol, "pls", 3)) { return pl_insert_pls (after, fname, pabort, cb, user_data); } // add all posible streams as special-case: // set decoder to NULL, and filetype to "content" // streamer is responsible to determine content type on 1st access and // update decoder and filetype fields if (strncasecmp (fname, "file://", 7)) { const char *p = fname; int detect_on_access = 1; // check if it's URI for (; *p; p++) { if (!strncmp (p, "://", 3)) { break; } if (!isalpha (*p)) { detect_on_access = 0; break; } } if (detect_on_access) { // find vfs plugin DB_vfs_t **vfsplugs = plug_get_vfs_list (); for (int i = 0; vfsplugs[i]; i++) { if (vfsplugs[i]->get_schemes) { const char **sch = vfsplugs[i]->get_schemes (); int s = 0; for (s = 0; sch[s]; s++) { if (!strncasecmp (sch[s], fname, strlen (sch[s]))) { break; } } if (sch[s]) { if (!vfsplugs[i]->is_streaming || !vfsplugs[i]->is_streaming()) { detect_on_access = 0; } break; } } } } if (detect_on_access && *p == ':') { // check for wrong chars like CR/LF, TAB, etc // they are not allowed and might lead to corrupt display in GUI int32_t i = 0; while (fname[i]) { uint32_t c = u8_nextchar (fname, &i); if (c < 0x20) { return NULL; } } playItem_t *it = pl_item_alloc_init (fname, NULL); pl_replace_meta (it, ":FILETYPE", "content"); after = plt_insert_item (addfiles_playlist ? addfiles_playlist : playlist, after, it); pl_item_unref (it); return after; } } else { fname += 7; } DB_decoder_t **decoders = plug_get_decoder_list (); // match by decoder for (int i = 0; decoders[i]; i++) { trace ("matching decoder %d(%s)...\n", i, decoders[i]->plugin.id); if (decoders[i]->exts && decoders[i]->insert) { const char **exts = decoders[i]->exts; for (int e = 0; exts[e]; e++) { if (!strcasecmp (exts[e], eol)) { playItem_t *inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname); if (inserted != NULL) { if (cb && cb (inserted, user_data) < 0) { *pabort = 1; } return inserted; } } } } if (decoders[i]->prefixes && decoders[i]->insert) { const char **prefixes = decoders[i]->prefixes; for (int e = 0; prefixes[e]; e++) { if (!strncasecmp (prefixes[e], fn, strlen(prefixes[e])) && *(fn + strlen (prefixes[e])) == '.') { playItem_t *inserted = (playItem_t *)decoders[i]->insert (DB_PLAYITEM (after), fname); if (inserted != NULL) { if (cb && cb (inserted, user_data) < 0) { *pabort = 1; } return inserted; } } } } } trace ("no decoder found for %s\n", fname); return NULL; } static int dirent_alphasort (const struct dirent **a, const struct dirent **b) { return strcmp ((*a)->d_name, (*b)->d_name); } playItem_t * pl_insert_dir_int (DB_vfs_t *vfs, playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { if (!strncmp (dirname, "file://", 7)) { dirname += 7; } if (!follow_symlinks && !vfs) { struct stat buf; lstat (dirname, &buf); if (S_ISLNK(buf.st_mode)) { return NULL; } } struct dirent **namelist = NULL; int n; if (vfs && vfs->scandir) { n = vfs->scandir (dirname, &namelist, NULL, dirent_alphasort); } else { n = scandir (dirname, &namelist, NULL, dirent_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] != '.') { playItem_t *inserted = NULL; if (!vfs) { char fullname[PATH_MAX]; snprintf (fullname, sizeof (fullname), "%s/%s", dirname, namelist[i]->d_name); inserted = pl_insert_dir_int (vfs, after, fullname, pabort, cb, user_data); if (!inserted) { inserted = pl_insert_file (after, fullname, pabort, cb, user_data); } } else { inserted = pl_insert_file (after, namelist[i]->d_name, pabort, cb, user_data); } if (inserted) { after = inserted; } if (*pabort) { break; } } free (namelist[i]); } free (namelist); } return after; } playItem_t * pl_insert_dir (playItem_t *after, const char *dirname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { follow_symlinks = conf_get_int ("add_folders_follow_symlinks", 0); ignore_archives = conf_get_int ("ignore_archives", 1); playItem_t *ret = pl_insert_dir_int (NULL, after, dirname, pabort, cb, user_data); ignore_archives = 0; return ret; } int pl_add_file (const char *fname, int (*cb)(playItem_t *it, void *data), void *user_data) { int abort = 0; playItem_t *it = pl_insert_file (addfiles_playlist->tail[PL_MAIN], fname, &abort, cb, user_data); if (it) { // pl_insert_file doesn't hold reference, don't unref here return 0; } return -1; } int pl_add_dir (const char *dirname, int (*cb)(playItem_t *it, void *data), void *user_data) { int abort = 0; playItem_t *it = pl_insert_dir (addfiles_playlist->tail[PL_MAIN], dirname, &abort, cb, user_data); if (it) { // pl_insert_file doesn't hold reference, don't unref here return 0; } return -1; } int pl_add_files_begin (playlist_t *plt) { pl_lock (); if (addfiles_playlist) { pl_unlock (); return -1; } addfiles_playlist = plt; if (addfiles_playlist) { plt_ref (addfiles_playlist); } pl_unlock (); trace ("adding to playlist %d (%s)\n", plt, addfiles_playlist->title); return 0; } void pl_add_files_end (void) { trace ("end adding to playlist %s\n", addfiles_playlist->title); pl_lock (); if (addfiles_playlist) { plt_unref (addfiles_playlist); } addfiles_playlist = NULL; pl_unlock (); } int plt_remove_item (playlist_t *playlist, playItem_t *it) { if (!it) return -1; if (!no_remove_notify) { streamer_song_removed_notify (it); } pl_playqueue_remove (it); // remove from both lists LOCK; for (int iter = PL_MAIN; iter <= PL_SEARCH; iter++) { if (it->prev[iter] || it->next[iter] || playlist->head[iter] == it || playlist->tail[iter] == it) { playlist->count[iter]--; } if (it->prev[iter]) { it->prev[iter]->next[iter] = it->next[iter]; } else { playlist->head[iter] = it->next[iter]; } if (it->next[iter]) { it->next[iter]->prev[iter] = it->prev[iter]; } else { playlist->tail[iter] = it->prev[iter]; } } // totaltime float dur = pl_get_item_duration (it); if (dur > 0) { playlist->totaltime -= dur; if (playlist->totaltime < 0) { playlist->totaltime = 0; } } plt_modified (playlist); pl_item_unref (it); UNLOCK; return 0; } int pl_remove_item (playItem_t *it) { return plt_remove_item (playlist, it); } int pl_getcount (int iter) { if (!playlist) { return 0; } return playlist->count[iter]; } int pl_getselcount (void) { // FIXME: slow! int cnt = 0; LOCK; for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { if (it->selected) { cnt++; } } UNLOCK; return cnt; } playItem_t * pl_get_for_idx_and_iter (int idx, int iter) { LOCK; playItem_t *it = playlist->head[iter]; while (idx--) { if (!it) { UNLOCK; return NULL; } it = it->next[iter]; } if (it) { pl_item_ref (it); } UNLOCK; return it; } playItem_t * pl_get_for_idx (int idx) { return pl_get_for_idx_and_iter (idx, PL_MAIN); } int pl_get_idx_of (playItem_t *it) { return pl_get_idx_of_iter (it, PL_MAIN); } int pl_get_idx_of_iter (playItem_t *it, int iter) { LOCK; playItem_t *c = playlist->head[iter]; int idx = 0; while (c && c != it) { c = c->next[iter]; idx++; } if (!c) { UNLOCK; return -1; } UNLOCK; return idx; } playItem_t * plt_insert_item (playlist_t *playlist, playItem_t *after, playItem_t *it) { LOCK; pl_item_ref (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; } } it->in_playlist = 1; playlist->count[PL_MAIN]++; // shuffle playItem_t *prev = it->prev[PL_MAIN]; if (pl_order == PLAYBACK_ORDER_SHUFFLE_ALBUMS && prev && pl_find_meta (prev, "album") == pl_find_meta (it, "album") && pl_find_meta (prev, "artist") == pl_find_meta (it, "artist")) { it->shufflerating = prev->shufflerating; } else { it->shufflerating = rand (); } it->played = 0; // totaltime float dur = pl_get_item_duration (it); if (dur > 0) { playlist->totaltime += dur; } plt_modified (playlist); UNLOCK; return it; } playItem_t * pl_insert_item (playItem_t *after, playItem_t *it) { return plt_insert_item (addfiles_playlist ? addfiles_playlist : playlist, after, it); } void pl_item_copy (playItem_t *out, playItem_t *it) { LOCK; out->startsample = it->startsample; out->endsample = it->endsample; out->shufflerating = it->shufflerating; out->_duration = it->_duration; out->started_timestamp = it->started_timestamp; 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]; out->_refc = 1; // copy metainfo DB_metaInfo_t *prev = NULL; DB_metaInfo_t *meta = it->meta; while (meta) { DB_metaInfo_t *m = malloc (sizeof (DB_metaInfo_t)); memset (m, 0, sizeof (DB_metaInfo_t)); m->key = metacache_add_string (meta->key); m->value = metacache_add_string (meta->value); m->next = NULL; if (prev) { prev->next = m; } else { out->meta = m; } prev = m; meta = meta->next; } UNLOCK; } playItem_t * pl_item_alloc (void) { playItem_t *it = malloc (sizeof (playItem_t)); memset (it, 0, sizeof (playItem_t)); it->_refc = 1; return it; } playItem_t * pl_item_alloc_init (const char *fname, const char *decoder_id) { playItem_t *it = pl_item_alloc (); pl_add_meta (it, ":URI", fname); pl_add_meta (it, ":DECODER", decoder_id); return it; } void pl_item_ref (playItem_t *it) { LOCK; it->_refc++; //trace ("\033[0;34m+it %p: refc=%d: %s\033[37;0m\n", it, it->_refc, pl_find_meta (it, ":URI")); UNLOCK; } static void pl_item_free (playItem_t *it) { LOCK; if (it) { while (it->meta) { DB_metaInfo_t *m = it->meta; it->meta = m->next; metacache_remove_string (m->key); metacache_remove_string (m->value); free (m); } free (it); } UNLOCK; } void pl_item_unref (playItem_t *it) { LOCK; it->_refc--; //trace ("\033[0;31m-it %p: refc=%d: %s\033[37;0m\n", it, it->_refc, pl_find_meta (it, ":URI")); if (it->_refc < 0) { trace ("\033[0;31mplaylist: bad refcount on item %p\033[37;0m\n", it); } if (it->_refc <= 0) { //printf ("\033[0;31mdeleted %s\033[37;0m\n", pl_find_meta (it, ":URI")); pl_item_free (it); } UNLOCK; } int pl_delete_selected (void) { LOCK; int i = 0; int ret = -1; playItem_t *next = NULL; for (playItem_t *it = playlist->head[PL_MAIN]; it; it = next, i++) { next = it->next[PL_MAIN]; if (it->selected) { if (ret == -1) { ret = i; } pl_remove_item (it); } } if (playlist->current_row[PL_MAIN] >= playlist->count[PL_MAIN]) { playlist->current_row[PL_MAIN] = playlist->count[PL_MAIN] - 1; } if (playlist->current_row[PL_SEARCH] >= playlist->count[PL_SEARCH]) { playlist->current_row[PL_SEARCH] = playlist->count[PL_SEARCH] - 1; } UNLOCK; return ret; } void pl_crop_selected (void) { LOCK; 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_item (it); } } UNLOCK; } int plt_save (playlist_t *plt, playItem_t *first, playItem_t *last, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { LOCK; const char *ext = strrchr (fname, '.'); if (ext) { DB_playlist_t **plug = deadbeef->plug_get_playlist_list (); for (int i = 0; plug[i]; i++) { if (plug[i]->extensions && plug[i]->load) { const char **exts = plug[i]->extensions; if (exts && plug[i]->save) { for (int e = 0; exts[e]; e++) { if (!strcasecmp (exts[e], ext+1)) { int res = plug[i]->save (fname, (DB_playItem_t *)playlist->head[PL_MAIN], NULL); UNLOCK; return res; } } } } } } const char magic[] = "DBPL"; uint8_t majorver = PLAYLIST_MAJOR_VER; uint8_t minorver = PLAYLIST_MINOR_VER; FILE *fp = fopen (fname, "w+b"); if (!fp) { UNLOCK; 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 = plt->count[PL_MAIN]; if (fwrite (&cnt, 1, 4, fp) != 4) { goto save_fail; } for (playItem_t *it = plt->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { uint16_t l; uint8_t ll; #if (PLAYLIST_MINOR_VER==2) const char *fname = pl_find_meta (it, ":URI"); l = strlen (fname); if (fwrite (&l, 1, 2, fp) != 2) { goto save_fail; } if (fwrite (fname, 1, l, fp) != l) { goto save_fail; } const char *decoder_id = pl_find_meta (it, ":DECODER"); if (decoder_id) { ll = strlen (decoder_id); if (fwrite (&ll, 1, 1, fp) != 1) { goto save_fail; } if (fwrite (decoder_id, 1, ll, fp) != ll) { goto save_fail; } } else { ll = 0; if (fwrite (&ll, 1, 1, fp) != 1) { goto save_fail; } } l = pl_find_meta_int (it, ":TRACKNUM", 0); if (fwrite (&l, 1, 2, fp) != 2) { goto save_fail; } #endif if (fwrite (&it->startsample, 1, 4, fp) != 4) { goto save_fail; } if (fwrite (&it->endsample, 1, 4, fp) != 4) { goto save_fail; } if (fwrite (&it->_duration, 1, 4, fp) != 4) { goto save_fail; } #if (PLAYLIST_MINOR_VER==2) const char *filetype = pl_find_meta (it, ":FILETYPE"); if (!filetype) { filetype = ""; } uint8_t ft = strlen (filetype); if (fwrite (&ft, 1, 1, fp) != 1) { goto save_fail; } if (ft) { if (fwrite (filetype, 1, ft, fp) != ft) { goto save_fail; } } float rg_albumgain = pl_get_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN); float rg_albumpeak = pl_get_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK); float rg_trackgain = pl_get_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN); float rg_trackpeak = pl_get_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK); if (fwrite (&rg_albumgain, 1, 4, fp) != 4) { goto save_fail; } if (fwrite (&rg_albumpeak, 1, 4, fp) != 4) { goto save_fail; } if (fwrite (&rg_trackgain, 1, 4, fp) != 4) { goto save_fail; } if (fwrite (&rg_trackpeak, 1, 4, fp) != 4) { goto save_fail; } #endif if (fwrite (&it->_flags, 1, 4, fp) != 4) { goto save_fail; } int16_t nm = 0; DB_metaInfo_t *m; for (m = it->meta; m; m = m->next) { if (m->key[0] == '_') { continue; // skip reserved names } nm++; } if (fwrite (&nm, 1, 2, fp) != 2) { goto save_fail; } for (m = it->meta; m; m = m->next) { if (m->key[0] == '_') { continue; // skip reserved names } 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; } } } } // write playlist metadata int16_t nm = 0; DB_metaInfo_t *m; for (m = plt->meta; m; m = m->next) { nm++; } if (fwrite (&nm, 1, 2, fp) != 2) { goto save_fail; } for (m = plt->meta; m; m = m->next) { uint16_t l; 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; } } } UNLOCK; fclose (fp); return 0; save_fail: UNLOCK; fclose (fp); unlink (fname); return -1; } int pl_save_n (int n) { char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/playlists", dbconfdir) > sizeof (path)) { fprintf (stderr, "error: failed to make path string for playlists folder\n"); return -1; } // make folder mkdir (path, 0755); LOCK; int err = 0; plt_loading = 1; if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, n) > sizeof (path)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); UNLOCK; return -1; } int i; playlist_t *plt; for (i = 0, plt = playlists_head; plt && i < n; i++, plt = plt->next); err = plt_save (plt, NULL, NULL, path, NULL, NULL, NULL); plt_loading = 0; UNLOCK; return err; } int pl_save_current (void) { return pl_save_n (plt_get_curr_idx ()); } int pl_save_all (void) { trace ("pl_save_all\n"); char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/playlists", dbconfdir) > sizeof (path)) { fprintf (stderr, "error: failed to make path string for playlists folder\n"); return -1; } // make folder mkdir (path, 0755); LOCK; playlist_t *p = playlists_head; int i; int cnt = plt_get_count (); int curr = plt_get_curr_idx (); int err = 0; plt_loading = 1; for (i = 0; i < cnt; i++, p = p->next) { if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path)) { fprintf (stderr, "error: failed to make path string for playlist file\n"); err = -1; break; } err = plt_save (p, NULL, NULL, path, NULL, NULL, NULL); if (err < 0) { break; } } plt_loading = 0; UNLOCK; return err; } playItem_t * plt_load (playlist_t *plt, playItem_t *after, const char *fname, int *pabort, int (*cb)(playItem_t *it, void *data), void *user_data) { FILE *fp = fopen (fname, "rb"); if (!fp) { trace ("plt_load: failed to open %s\n", fname); return NULL; } // try plugins 1st const char *ext = strrchr (fname, '.'); if (ext) { ext++; DB_playlist_t **plug = plug_get_playlist_list (); int p, e; for (p = 0; plug[p]; p++) { for (e = 0; plug[p]->extensions[e]; e++) { if (plug[p]->load && !strcasecmp (ext, plug[p]->extensions[e])) { DB_playItem_t *it = plug[p]->load ((DB_playItem_t *)after, fname, pabort, (int (*)(DB_playItem_t *, void *))cb, user_data); return (playItem_t *)it; } } } } trace ("plt_load: loading dbpl\n"); uint8_t majorver; uint8_t minorver; playItem_t *it = NULL; char magic[4]; if (fread (magic, 1, 4, fp) != 4) { goto load_fail; } if (strncmp (magic, "DBPL", 4)) { trace ("bad signature\n"); goto load_fail; } if (fread (&majorver, 1, 1, fp) != 1) { goto load_fail; } if (majorver != PLAYLIST_MAJOR_VER) { trace ("bad majorver=%d\n", majorver); goto load_fail; } if (fread (&minorver, 1, 1, fp) != 1) { goto load_fail; } if (minorver < 1) { trace ("bad minorver=%d\n", minorver); goto load_fail; } trace ("playlist version=%d.%d\n", majorver, minorver); uint32_t cnt; if (fread (&cnt, 1, 4, fp) != 4) { goto load_fail; } playItem_t *last_added = NULL; for (uint32_t i = 0; i < cnt; i++) { it = pl_item_alloc (); if (!it) { goto load_fail; } uint16_t l; int16_t tracknum = 0; if (minorver <= 2) { // fname if (fread (&l, 1, 2, fp) != 2) { goto load_fail; } char fname[l+1]; if (fread (fname, 1, l, fp) != l) { goto load_fail; } fname[l] = 0; pl_add_meta (it, ":URI", fname); // decoder uint8_t ll; if (fread (&ll, 1, 1, fp) != 1) { goto load_fail; } if (ll >= 20) { goto load_fail; } char decoder_id[20] = ""; if (ll) { if (fread (decoder_id, 1, ll, fp) != ll) { goto load_fail; } decoder_id[ll] = 0; pl_add_meta (it, ":DECODER", decoder_id); } // tracknum if (fread (&tracknum, 1, 2, fp) != 2) { goto load_fail; } pl_set_meta_int (it, ":TRACKNUM", tracknum); } // startsample if (fread (&it->startsample, 1, 4, fp) != 4) { goto load_fail; } // endsample if (fread (&it->endsample, 1, 4, fp) != 4) { goto load_fail; } // duration if (fread (&it->_duration, 1, 4, fp) != 4) { goto load_fail; } char s[100]; pl_format_time (it->_duration, s, sizeof(s)); pl_replace_meta (it, ":DURATION", s); if (minorver <= 2) { // legacy filetype support 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; pl_replace_meta (it, ":FILETYPE", ftype); } float f; if (fread (&f, 1, 4, fp) != 4) { goto load_fail; } if (f != 0) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, f); } if (fread (&f, 1, 4, fp) != 4) { goto load_fail; } if (f == 0) { f = 1; } if (f != 1) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, f); } if (fread (&f, 1, 4, fp) != 4) { goto load_fail; } if (f != 0) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, f); } if (fread (&f, 1, 4, fp) != 4) { goto load_fail; } if (f == 0) { f = 1; } if (f != 1) { pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, f); } } uint32_t flg = 0; if (minorver >= 2) { if (fread (&flg, 1, 4, fp) != 4) { goto load_fail; } } else { if (it->startsample > 0 || it->endsample > 0 || tracknum > 0) { flg |= DDB_IS_SUBTRACK; } } pl_set_item_flags (it, flg); int16_t nm = 0; if (fread (&nm, 1, 2, fp) != 2) { goto load_fail; } for (int i = 0; i < nm; i++) { if (fread (&l, 1, 2, fp) != 2) { goto load_fail; } if (l >= 20000) { goto load_fail; } char key[l+1]; 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 >= 20000) { // skip fseek (fp, l, SEEK_CUR); } else { char value[l+1]; int res = fread (value, 1, l, fp); if (res != l) { trace ("read error: requested %d, got %d\n", l, res); goto load_fail; } value[l] = 0; if (key[0] == ':') { // to avoid storage conflicts -- give more priority to metadata pl_replace_meta (it, key, value); } else { pl_add_meta (it, key, value); } } } plt_insert_item (plt, plt->tail[PL_MAIN], it); if (last_added) { pl_item_unref (last_added); } last_added = it; } // load playlist metadata int16_t nm = 0; // for backwards format compatibility, don't fail if metadata is not found if (fread (&nm, 1, 2, fp) == 2) { for (int i = 0; i < nm; i++) { int16_t l; if (fread (&l, 1, 2, fp) != 2) { goto load_fail; } if (l < 0 || l >= 20000) { goto load_fail; } char key[l+1]; 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<0 || l >= 20000) { // skip fseek (fp, l, SEEK_CUR); } else { char value[l+1]; int res = fread (value, 1, l, fp); if (res != l) { trace ("read error: requested %d, got %d\n", l, res); goto load_fail; } value[l] = 0; plt_add_meta (plt, key, value); } } } if (fp) { fclose (fp); } trace ("plt_load: success\n"); return last_added; load_fail: fprintf (stderr, "playlist load fail (%s)!\n", fname); if (fp) { fclose (fp); } return last_added; } int pl_load_all (void) { int i = 0; int err = 0; char path[1024]; DB_conf_item_t *it = conf_find ("playlist.tab.", NULL); if (!it) { // fprintf (stderr, "INFO: loading legacy default playlist\n"); // legacy (0.3.3 and earlier) char defpl[1024]; // $HOME/.config/deadbeef/default.dbpl if (snprintf (defpl, sizeof (defpl), "%s/default.dbpl", dbconfdir) > sizeof (defpl)) { fprintf (stderr, "error: cannot make string with default playlist path\n"); return -1; } if (plt_add (plt_get_count (), _("Default")) < 0) { return -1; } playlist_t *plt = plt_get_for_idx (0); playItem_t *it = plt_load (plt, NULL, defpl, NULL, NULL, NULL); if (it) { pl_item_unref (it); } plt_unref (plt); return 0; } trace ("pl_load_all started\n"); LOCK; trace ("locked\n"); plt_loading = 1; while (it) { fprintf (stderr, "INFO: loading playlist %s\n", it->value); if (!err) { if (plt_add (plt_get_count (), it->value) < 0) { return -1; } plt_set_curr_idx (plt_get_count () - 1); } err = 0; if (snprintf (path, sizeof (path), "%s/playlists/%d.dbpl", dbconfdir, i) > sizeof (path)) { fprintf (stderr, "error: failed to make path string for playlist filename\n"); err = -1; } else { fprintf (stderr, "INFO: from file %s\n", path); playlist_t *plt = plt_get_curr (); playItem_t *trk = plt_load (plt, NULL, path, NULL, NULL, NULL); if (trk) { pl_item_unref (trk); } plt_unref (plt); if (!it) { fprintf (stderr, "WARNING: there were errors while loading playlist '%s' (%s)\n", it->value, path); } } it = conf_find ("playlist.tab.", it); trace ("conf_find returned %p (%s)\n", it, it ? it->value : "null"); i++; } plt_set_curr (0); plt_loading = 0; plt_gen_conf (); messagepump_push (DB_EV_PLAYLISTSWITCHED, 0, 0, 0); UNLOCK; trace ("pl_load_all finished\n"); return err; } void pl_select_all (void) { LOCK; for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { it->selected = 1; } UNLOCK; } void plt_reshuffle (playlist_t *playlist, playItem_t **ppmin, playItem_t **ppmax) { LOCK; playItem_t *pmin = NULL; playItem_t *pmax = NULL; playItem_t *prev = NULL; for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { if (pl_order == PLAYBACK_ORDER_SHUFFLE_ALBUMS && prev && pl_find_meta (prev, "album") == pl_find_meta (it, "album") && pl_find_meta (prev, "artist") == pl_find_meta (it, "artist")) { it->shufflerating = prev->shufflerating; } else { it->shufflerating = rand (); } if (!pmin || it->shufflerating < pmin->shufflerating) { pmin = it; } if (!pmax || it->shufflerating > pmax->shufflerating) { pmax = it; } it->played = 0; prev = it; } if (ppmin) { *ppmin = pmin; } if (ppmax) { *ppmax = pmax; } UNLOCK; } void pl_reshuffle (playItem_t **ppmin, playItem_t **ppmax) { plt_reshuffle (playlist, ppmin, ppmax); } void pl_set_item_duration (playItem_t *it, float duration) { LOCK; if (it->in_playlist) { if (it->_duration > 0) { playlist->totaltime -= it->_duration; } if (duration > 0) { playlist->totaltime += duration; } if (playlist->totaltime < 0) { playlist->totaltime = 0; } } it->_duration = duration; char s[100]; pl_format_time (it->_duration, s, sizeof(s)); pl_replace_meta (it, ":DURATION", s); UNLOCK; } float pl_get_item_duration (playItem_t *it) { return it->_duration; } static const char *rg_keys[] = { ":REPLAYGAIN_ALBUMGAIN", ":REPLAYGAIN_ALBUMPEAK", ":REPLAYGAIN_TRACKGAIN", ":REPLAYGAIN_TRACKPEAK" }; void pl_set_item_replaygain (playItem_t *it, int idx, float value) { char s[100]; switch (idx) { case DDB_REPLAYGAIN_ALBUMGAIN: case DDB_REPLAYGAIN_TRACKGAIN: snprintf (s, sizeof (s), "%0.2f dB", value); break; case DDB_REPLAYGAIN_ALBUMPEAK: case DDB_REPLAYGAIN_TRACKPEAK: snprintf (s, sizeof (s), "%0.6f", value); break; default: return; } pl_replace_meta (it, rg_keys[idx], s); } float pl_get_item_replaygain (playItem_t *it, int idx) { if (idx < 0 || idx > DDB_REPLAYGAIN_TRACKPEAK) { return 0; } switch (idx) { case DDB_REPLAYGAIN_ALBUMGAIN: case DDB_REPLAYGAIN_TRACKGAIN: return pl_find_meta_float (it, rg_keys[idx], 0); case DDB_REPLAYGAIN_ALBUMPEAK: case DDB_REPLAYGAIN_TRACKPEAK: return pl_find_meta_float (it, rg_keys[idx], 1); } } int pl_format_item_queue (playItem_t *it, char *s, int size) { LOCK; *s = 0; int initsize = size; const char *val = pl_find_meta (it, "_playing"); while (val && *val) { while (*val && *val != '=') { val++; } if (*val == '=') { // found value val++; if (!(*val)) { break; } const char *e = NULL; if (*val == '"') { val++; e = val; while (*e && *e != '"') { e++; } } else { e = val; while (*e && *e != ' ') { e++; } } int n = e - val; if (n > size-1) { n = size-1; } strncpy (s, val, n); s += n; *s++ = ' '; *s = 0; size -= n+1; val = e; if (*val) { val++; } while (*val && *val == ' ') { val++; } } } if (!playqueue_count) { UNLOCK; return 0; } int qinitsize = size; int init = 1; int len; for (int i = 0; i < playqueue_count; i++) { if (size <= 0) { break; } if (playqueue[i] == it) { if (init) { init = 0; s[0] = '('; s++; size--; len = snprintf (s, size, "%d", i+1); } else { len = snprintf (s, size, ",%d", i+1); } s += len; size -= len; } } if (size != qinitsize && size > 0) { len = snprintf (s, size, ")"); s += len; size -= len; } UNLOCK; return initsize-size; } void pl_format_time (float t, char *dur, int size) { if (t >= 0) { int hourdur = t / (60 * 60); int mindur = (t - hourdur * 60 * 60) / 60; int secdur = t - hourdur*60*60 - mindur * 60; if (hourdur) { snprintf (dur, size, "%d:%02d:%02d", hourdur, mindur, secdur); } else { snprintf (dur, size, "%d:%02d", mindur, secdur); } } else { strcpy (dur, "-:--"); } } static const char * pl_format_duration (playItem_t *it, const char *ret, char *dur, int size) { if (ret) { return ret; } pl_format_time (pl_get_item_duration (it), dur, size); return dur; } static const char * pl_format_elapsed (const char *ret, char *elapsed, int size) { if (ret) { return ret; } float playpos = streamer_get_playpos (); pl_format_time (playpos, elapsed, size); return elapsed; } // this function allows to escape special chars substituted for conversions // @escape_chars: list of escapable characters terminated with 0, or NULL if none static int pl_format_title_int (const char *escape_chars, playItem_t *it, int idx, char *s, int size, int id, const char *fmt) { char dur[50]; char elp[50]; char fno[50]; char tags[200]; char dirname[PATH_MAX]; const char *duration = NULL; const char *elapsed = NULL; char *ss = s; LOCK; if (id != -1 && it) { const char *text = NULL; switch (id) { case DB_COLUMN_FILENUMBER: if (idx == -1) { idx = pl_get_idx_of (it); } snprintf (fno, sizeof (fno), "%d", idx+1); text = fno; break; case DB_COLUMN_PLAYING: UNLOCK; return pl_format_item_queue (it, s, size); } if (text) { strncpy (s, text, size); UNLOCK; for (ss = s; *ss; ss++) { if (*ss == '\n') { *ss = ';'; } } return strlen (s); } else { s[0] = 0; } UNLOCK; return 0; } int n = size-1; while (*fmt && n > 0) { if (*fmt != '%') { *s++ = *fmt; n--; } else { fmt++; const char *meta = NULL; if (*fmt == 0) { break; } else if (!it && *fmt != 'V') { // only %V (version) works without track pointer } else if (*fmt == '@') { const char *e = fmt; e++; while (*e && *e != '@') { e++; } if (*e == '@') { char nm[100]; int l = e-fmt-1; l = min (l, sizeof (nm)-1); strncpy (nm, fmt+1, l); nm[l] = 0; meta = pl_find_meta (it, nm); if (!meta) { meta = "?"; } fmt = e; } } else if (*fmt == 'a') { meta = pl_find_meta (it, "artist"); if (!meta) { meta = "Unknown artist"; } } else if (*fmt == 't') { meta = pl_find_meta (it, "title"); if (!meta) { const char *f = pl_find_meta (it, ":URI"); if (f) { const char *start = strrchr (f, '/'); if (start) { start++; } else { start = f; } const char *end = strrchr (start, '.'); if (end) { int n = end-start; n = min (end-start, sizeof (dirname)-1); strncpy (dirname, start, n); dirname[n] = 0; meta = dirname; } else { meta = "?"; } } else { meta = "?"; } } } else if (*fmt == 'b') { meta = pl_find_meta (it, "album"); if (!meta) { meta = "Unknown album"; } } else if (*fmt == 'B') { meta = pl_find_meta (it, "band"); if (!meta) { meta = pl_find_meta (it, "album artist"); if (!meta) { meta = pl_find_meta (it, "albumartist"); if (!meta) { meta = pl_find_meta (it, "artist"); } } } } else if (*fmt == 'C') { meta = pl_find_meta (it, "composer"); } else if (*fmt == 'n') { meta = pl_find_meta (it, "track"); if (meta) { // check if it's numbers only const char *p = meta; while (*p) { if (!isdigit (*p)) { break; } p++; } if (!(*p)) { snprintf (fno, sizeof (fno), "%02d", atoi (meta)); meta = fno; } } } else if (*fmt == 'N') { meta = pl_find_meta (it, "numtracks"); } else if (*fmt == 'y') { meta = pl_find_meta (it, "year"); } else if (*fmt == 'g') { meta = pl_find_meta (it, "genre"); } else if (*fmt == 'c') { meta = pl_find_meta (it, "comment"); } else if (*fmt == 'r') { meta = pl_find_meta (it, "copyright"); } else if (*fmt == 'l') { const char *value = (duration = pl_format_duration (it, duration, dur, sizeof (dur))); while (n > 0 && *value) { *s++ = *value++; n--; } } else if (*fmt == 'e') { // what a hack.. const char *value = (elapsed = pl_format_elapsed (elapsed, elp, sizeof (elp))); while (n > 0 && *value) { *s++ = *value++; n--; } } else if (*fmt == 'f') { const char *f = pl_find_meta (it, ":URI"); meta = strrchr (f, '/'); if (meta) { meta++; } else { meta = f; } } else if (*fmt == 'F') { meta = pl_find_meta (it, ":URI"); } else if (*fmt == 'T') { char *t = tags; char *e = tags + sizeof (tags); int c; *t = 0; if (it->_flags & DDB_TAG_ID3V1) { c = snprintf (t, e-t, "ID3v1 | "); t += c; } if (it->_flags & DDB_TAG_ID3V22) { c = snprintf (t, e-t, "ID3v2.2 | "); t += c; } if (it->_flags & DDB_TAG_ID3V23) { c = snprintf (t, e-t, "ID3v2.3 | "); t += c; } if (it->_flags & DDB_TAG_ID3V24) { c = snprintf (t, e-t, "ID3v2.4 | "); t += c; } if (it->_flags & DDB_TAG_APEV2) { c = snprintf (t, e-t, "APEv2 | "); t += c; } if (it->_flags & DDB_TAG_VORBISCOMMENTS) { c = snprintf (t, e-t, "VorbisComments | "); t += c; } if (it->_flags & DDB_TAG_CUESHEET) { c = snprintf (t, e-t, "CueSheet | "); t += c; } if (it->_flags & DDB_TAG_ICY) { c = snprintf (t, e-t, "Icy | "); t += c; } if (t != tags) { *(t - 3) = 0; } meta = tags; } else if (*fmt == 'd') { // directory const char *f = pl_find_meta (it, ":URI"); const char *end = strrchr (f, '/'); if (!end) { meta = ""; // got relative path without folder (should not happen) } else { const char *start = end; start--; while (start > f && (*start != '/')) { start--; } if (*start == '/') { start++; } // copy int len = end-start; len = min (len, sizeof (dirname)-1); strncpy (dirname, start, len); dirname[len] = 0; meta = dirname; } } else if (*fmt == 'D') { const char *f = pl_find_meta (it, ":URI"); // directory with path const char *end = strrchr (f, '/'); if (!end) { meta = ""; // got relative path without folder (should not happen) } else { // copy int len = end - f; len = min (len, sizeof (dirname)-1); strncpy (dirname, f, len); dirname[len] = 0; meta = dirname; } } else if (*fmt == 'V') { meta = VERSION; } else { *s++ = *fmt; n--; } if (meta) { const char *value = meta; if (escape_chars) { // need space for at least 2 single-quotes if (n < 2) { goto error; } *s++ = '\''; n--; while (n > 2 && *value) { const char *e = escape_chars; for (; *e; e++) { if (*value == *e) { if (n < 2) { // doesn't fit into output buffer, return // empty string and error code *ss = 0; return -1; } *s++ = '\\'; n--; *s++ = *value++; n--; } else { *s++ = *value++; } } } if (n < 1) { fprintf (stderr, "pl_format_title_int: got unpredicted state while formatting escaped string. please report a bug.\n"); *ss = 0; // should never happen return -1; } *s++ = '\''; n--; } else { while (n > 0 && *value) { *s++ = *value++; n--; } } } } fmt++; } error: *s = 0; UNLOCK; // replace all \n with ; while (*ss) { if (*ss == '\n') { *ss = ';'; } ss++; } return size - n - 1; } int pl_format_title (playItem_t *it, int idx, char *s, int size, int id, const char *fmt) { return pl_format_title_int (NULL, it, idx, s, size, id, fmt); } int pl_format_title_escaped (playItem_t *it, int idx, char *s, int size, int id, const char *fmt) { return pl_format_title_int ("'", it, idx, s, size, id, fmt); } static int pl_sort_is_duration; static int pl_sort_is_track; static int pl_sort_ascending; static int pl_sort_id; static const char *pl_sort_format; int strcasecmp_numeric (const char *a, const char *b) { if (isdigit (*a) && isdigit (*b)) { int anum = *a-'0'; const char *ae = a+1; while (*ae && isdigit (*ae)) { anum *= 10; anum += *ae-'0'; ae++; } int bnum = *b-'0'; const char *be = b+1; while (*be && isdigit (*be)) { bnum *= 10; bnum += *be-'0'; be++; } if (anum == bnum) { return u8_strcasecmp (ae, be); } return anum - bnum; } return u8_strcasecmp (a,b); } static int pl_sort_compare_str (playItem_t *a, playItem_t *b) { if (pl_sort_is_duration) { float dur_a = a->_duration * 100000; float dur_b = b->_duration * 100000; return !pl_sort_ascending ? dur_b - dur_a : dur_a - dur_b; } else if (pl_sort_is_track) { int t1; int t2; const char *t; t = pl_find_meta (a, "track"); if (t && !isdigit (*t)) { t1 = 999999; } else { t1 = t ? atoi (t) : -1; } t = pl_find_meta (b, "track"); if (t && !isdigit (*t)) { t2 = 999999; } else { t2 = t ? atoi (t) : -1; } return !pl_sort_ascending ? t2 - t1 : t1 - t2; } else { char tmp1[1024]; char tmp2[1024]; pl_format_title (a, -1, tmp1, sizeof (tmp1), pl_sort_id, pl_sort_format); pl_format_title (b, -1, tmp2, sizeof (tmp2), pl_sort_id, pl_sort_format); int res = strcasecmp_numeric (tmp1, tmp2); if (!pl_sort_ascending) { res = -res; } return res; } } static int qsort_cmp_func (const void *a, const void *b) { playItem_t *aa = *((playItem_t **)a); playItem_t *bb = *((playItem_t **)b); return pl_sort_compare_str (aa, bb); } void pl_sort (int iter, int id, const char *format, int ascending) { if (id == DB_COLUMN_FILENUMBER || !playlist->head[iter] || !playlist->head[iter]->next[iter]) { return; } LOCK; struct timeval tm1; gettimeofday (&tm1, NULL); pl_sort_ascending = ascending; trace ("ascending: %d\n", ascending); pl_sort_id = id; pl_sort_format = format; if (format && id == -1 && !strcmp (format, "%l")) { pl_sort_is_duration = 1; } else { pl_sort_is_duration = 0; } if (format && id == -1 && !strcmp (format, "%n")) { pl_sort_is_track = 1; } else { pl_sort_is_track = 0; } playItem_t **array = malloc (playlist->count[iter] * sizeof (playItem_t *)); int idx = 0; for (playItem_t *it = playlist->head[iter]; it; it = it->next[iter], idx++) { array[idx] = it; } qsort (array, playlist->count[iter], sizeof (playItem_t *), qsort_cmp_func); playItem_t *prev = NULL; playlist->head[iter] = 0; for (idx = 0; idx < playlist->count[iter]; idx++) { playItem_t *it = array[idx]; it->prev[iter] = prev; it->next[iter] = NULL; if (!prev) { playlist->head[iter] = it; } else { prev->next[iter] = it; } prev = it; } playlist->tail[iter] = array[playlist->count[iter]-1]; free (array); struct timeval tm2; gettimeofday (&tm2, NULL); int ms = (tm2.tv_sec*1000+tm2.tv_usec/1000) - (tm1.tv_sec*1000+tm1.tv_usec/1000); trace ("sort time: %f seconds\n", ms / 1000.f); plt_modified (playlist); UNLOCK; } void pl_reset_cursor (void) { int i; LOCK; for (i = 0; i < PL_MAX_ITERATORS; i++) { playlist->current_row[i] = -1; } UNLOCK; } float pl_get_totaltime (void) { return playlist->totaltime; } void pl_set_selected (playItem_t *it, int sel) { LOCK; it->selected = sel; UNLOCK; } int pl_is_selected (playItem_t *it) { return it->selected; } playItem_t * pl_get_first (int iter) { if (!playlist) { return NULL; } playItem_t *p = playlist->head[iter]; if (p) { pl_item_ref (p); } return p; } playItem_t * pl_get_last (int iter) { playItem_t *p = playlist->tail[iter]; if (p) { pl_item_ref (p); } return p; } playItem_t * pl_get_next (playItem_t *it, int iter) { playItem_t *next = it ? it->next[iter] : NULL; if (next) { pl_item_ref (next); } return next; } playItem_t * pl_get_prev (playItem_t *it, int iter) { playItem_t *prev = it ? it->prev[iter] : NULL; if (prev) { pl_item_ref (prev); } return prev; } int pl_get_cursor (int iter) { if (!playlist) { return -1; } return playlist->current_row[iter]; } void pl_set_cursor (int iter, int cursor) { LOCK; playlist->current_row[iter] = cursor; UNLOCK; } // this function must move items in playlist // list of items is indexes[count] // drop_before is insertion point void pl_move_items (int iter, int plt_from, playItem_t *drop_before, uint32_t *indexes, int count) { LOCK; playlist_t *playlist = plt_get_for_idx (plt_from); playlist_t *to = plt_get_curr (); if (!playlist || !to) { if (to) { plt_unref (to); } if (playlist) { plt_unref (playlist); } UNLOCK; return; } // don't let streamer think that current song was removed no_remove_notify = 1; // unlink items from playlist, and link together playItem_t *head = NULL; playItem_t *tail = NULL; int processed = 0; int idx = 0; playItem_t *next = NULL; // find insertion point playItem_t *drop_after = NULL; if (drop_before) { drop_after = drop_before->prev[iter]; } else { drop_after = to->tail[iter]; } for (playItem_t *it = playlist->head[iter]; it && processed < count; it = next, idx++) { next = it->next[iter]; if (idx == indexes[processed]) { pl_item_ref (it); if (drop_after == it) { drop_after = it->prev[PL_MAIN]; } plt_remove_item (playlist, it); plt_insert_item (to, drop_after, it); pl_item_unref (it); drop_after = it; processed++; } } no_remove_notify = 0; if (to) { plt_unref (to); } if (playlist) { plt_unref (playlist); } UNLOCK; } void pl_copy_items (int iter, int plt_from, playItem_t *before, uint32_t *indices, int cnt) { pl_lock (); playlist_t *from = plt_get_for_idx (plt_from); playlist_t *to = plt_get_curr (); if (!from || !to) { if (to) { plt_unref (to); } if (from) { plt_unref (from); } pl_unlock (); return; } for (int i = 0; i < cnt; i++) { playItem_t *it = from->head[iter]; int idx = 0; while (it && idx < indices[i]) { it = it->next[iter]; idx++; } if (!it) { trace ("pl_copy_items: warning: item %d not found in source playlist\n", indices[i]); continue; } playItem_t *it_new = pl_item_alloc (); pl_item_copy (it_new, it); playItem_t *after = before ? before->prev[iter] : to->tail[iter]; pl_insert_item (after, it_new); pl_item_unref (it_new); } if (to) { plt_unref (to); } if (from) { plt_unref (from); } pl_unlock (); } void pl_search_reset (void) { LOCK; while (playlist->head[PL_SEARCH]) { playItem_t *next = playlist->head[PL_SEARCH]->next[PL_SEARCH]; playlist->head[PL_SEARCH]->selected = 0; playlist->head[PL_SEARCH]->next[PL_SEARCH] = NULL; playlist->head[PL_SEARCH]->prev[PL_SEARCH] = NULL; playlist->head[PL_SEARCH] = next; } playlist->tail[PL_SEARCH] = NULL; playlist->count[PL_SEARCH] = 0; UNLOCK; } void pl_search_process (const char *text) { LOCK; pl_search_reset (); // convert text to lowercase, to save some cycles char lc[1000]; int n = sizeof (lc)-1; const char *p = text; char *out = lc; while (*p) { int32_t i = 0; char s[10]; const char *next; u8_nextchar (p, &i); int l = u8_tolower (p, i, s); n -= l; if (n < 0) { break; } memcpy (out, s, l); p += i; out += l; } *out = 0; const char *cuesheet = metacache_add_string ("cuesheet"); const char *log = metacache_add_string("log"); static int cmpidx = 0; cmpidx++; if (cmpidx > 127) { cmpidx = 1; } for (playItem_t *it = playlist->head[PL_MAIN]; it; it = it->next[PL_MAIN]) { it->selected = 0; if (*text) { DB_metaInfo_t *m = NULL; for (m = it->meta; m; m = m->next) { if (m->key[0] == ':' || m->key[0] == '_') { break; } if (m->key!=cuesheet && m->key!=log) { char cmp = *(m->value-1); if (abs (cmp) == cmpidx) { if (cmp > 0) { it->next[PL_SEARCH] = NULL; if (playlist->tail[PL_SEARCH]) { playlist->tail[PL_SEARCH]->next[PL_SEARCH] = it; playlist->tail[PL_SEARCH] = it; } else { playlist->head[PL_SEARCH] = playlist->tail[PL_SEARCH] = it; } it->selected = 1; playlist->count[PL_SEARCH]++; break; } } else if (utfcasestr_fast (m->value, lc)) { //fprintf (stderr, "%s -> %s match (%s.%s)\n", text, m->value, pl_find_meta (it, ":URI"), m->key); // add to list it->next[PL_SEARCH] = NULL; if (playlist->tail[PL_SEARCH]) { playlist->tail[PL_SEARCH]->next[PL_SEARCH] = it; playlist->tail[PL_SEARCH] = it; } else { playlist->head[PL_SEARCH] = playlist->tail[PL_SEARCH] = it; } it->selected = 1; playlist->count[PL_SEARCH]++; *((char *)m->value-1) = cmpidx; break; } else { *((char *)m->value-1) = -cmpidx; } } } } } metacache_remove_string (cuesheet); metacache_remove_string(log); UNLOCK; } int pl_playqueue_push (playItem_t *it) { if (playqueue_count == PLAYQUEUE_SIZE) { trace ("playqueue is full\n"); return -1; } LOCK; pl_item_ref (it); playqueue[playqueue_count++] = it; for (int i = 0; i < playqueue_count; i++) { plug_trigger_event_trackinfochanged (playqueue[i]); } UNLOCK; return 0; } void pl_playqueue_clear (void) { LOCK; int cnt = playqueue_count; playqueue_count = 0; int i; for (i = 0; i < cnt; i++) { plug_trigger_event_trackinfochanged (playqueue[i]); } for (i = 0; i < cnt; i++) { pl_item_unref (playqueue[i]); } UNLOCK; } void pl_playqueue_pop (void) { if (!playqueue_count) { return; } LOCK; if (playqueue_count == 1) { playqueue_count = 0; plug_trigger_event_trackinfochanged (playqueue[0]); pl_item_unref (playqueue[0]); UNLOCK; return; } playItem_t *it = playqueue[0]; memmove (&playqueue[0], &playqueue[1], (playqueue_count-1) * sizeof (playItem_t*)); playqueue_count--; plug_trigger_event_trackinfochanged (it); for (int i = 0; i < playqueue_count; i++) { plug_trigger_event_trackinfochanged (playqueue[i]); } pl_item_unref (it); UNLOCK; } void pl_playqueue_remove (playItem_t *it) { LOCK; for (;;) { int i; for (i = 0; i < playqueue_count; i++) { if (playqueue[i] == it) { if (i < playqueue_count-1) { memmove (&playqueue[i], &playqueue[i+1], (playqueue_count-i) * sizeof (playItem_t*)); } plug_trigger_event_trackinfochanged (it); pl_item_unref (it); playqueue_count--; break; } } if (i == playqueue_count) { break; } } for (int i = 0; i < playqueue_count; i++) { plug_trigger_event_trackinfochanged (playqueue[i]); } UNLOCK; } int pl_playqueue_test (playItem_t *it) { LOCK; for (int i = 0; i < playqueue_count; i++) { if (playqueue[i] == it) { UNLOCK; return i; } } UNLOCK; return -1; } playItem_t * pl_playqueue_getnext (void) { LOCK; if (playqueue_count > 0) { playItem_t *val = playqueue[0]; pl_item_ref (val); UNLOCK; return val; } UNLOCK; return NULL; } int pl_playqueue_getcount (void) { return playqueue_count; } void pl_items_copy_junk (playItem_t *from, playItem_t *first, playItem_t *last) { LOCK; DB_metaInfo_t *meta = from->meta; while (meta) { playItem_t *i; for (i = first; ; i = i->next[PL_MAIN]) { i->_flags = from->_flags; pl_add_meta (i, meta->key, meta->value); if (i == last) { break; } } meta = meta->next; } UNLOCK; } uint32_t pl_get_item_flags (playItem_t *it) { LOCK; uint32_t flags = it->_flags; UNLOCK; return flags; } void pl_set_item_flags (playItem_t *it, uint32_t flags) { LOCK; it->_flags = flags; char s[200]; pl_format_title (it, -1, s, sizeof (s), -1, "%T"); pl_replace_meta (it, ":TAGS", s); pl_replace_meta (it, ":HAS_EMBEDDED_CUESHEET", (flags & DDB_HAS_EMBEDDED_CUESHEET) ? _("Yes") : _("No")); UNLOCK; }