Add support for Spotify (squashed commit), and:

- Try to not return items which a client can't play
    - Remove inotify subscription to IN_MODIFY and IN_CREATE
    - Fix crash on unknown codec type in transcode.c
    - Probably added some new bugs...
This commit is contained in:
ejurgensen
2014-03-11 23:20:29 +01:00
parent 190f91114e
commit 7ed6cc98c3
24 changed files with 2203 additions and 171 deletions

View File

@@ -13,6 +13,10 @@ if COND_ITUNES
ITUNESSRC=filescanner_itunes.c
endif
if COND_SPOTIFY
SPOTIFYSRC=spotify.c spotify.h
endif
if COND_ALSA
ALSASRC=laudio_alsa.c
endif
@@ -59,13 +63,13 @@ forked_daapd_CPPFLAGS = -D_GNU_SOURCE \
forked_daapd_CFLAGS = \
@ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \
@CONFUSE_CFLAGS@ @TAGLIB_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_CFLAGS@ \
@LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@
@LIBGCRYPT_CFLAGS@ @GPG_ERROR_CFLAGS@ @ALSA_CFLAGS@ @SPOTIFY_CFLAGS@
forked_daapd_LDADD = -lrt \
@ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \
@CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ \
@LIBAVL_LIBS@ @MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_LIBS@ \
@LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBUNISTRING@
@LIBGCRYPT_LIBS@ @GPG_ERROR_LIBS@ @ALSA_LIBS@ @LIBUNISTRING@ @SPOTIFY_LIBS@
forked_daapd_SOURCES = main.c \
db.c db.h \
@@ -95,6 +99,7 @@ forked_daapd_SOURCES = main.c \
evrtsp/rtsp.c evrtp/evrtsp.h \
evrtsp/rtsp-internal.h evrtsp/log.h \
scan-wma.c \
$(SPOTIFYSRC) \
$(FLACSRC) $(MUSEPACKSRC)
nodist_forked_daapd_SOURCES = \

View File

@@ -739,6 +739,10 @@ artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, str
if (strncmp(filename, "http://", strlen("http://")) == 0)
return -1;
/* If Spotify item don't look for artwork */
if (strncmp(filename, "spotify:", strlen("spotify:")) == 0)
return -1;
DPRINTF(E_SPAM, L_ART, "Trying embedded artwork in %s\n", filename);
src_ctx = NULL;
@@ -854,6 +858,10 @@ artwork_get_own_image(char *path, int max_w, int max_h, int format, struct evbuf
if (strncmp(path, "http://", strlen("http://")) == 0)
return -1;
/* If Spotify item don't look for artwork */
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
return -1;
ret = snprintf(artwork, sizeof(artwork), "%s", path);
if ((ret < 0) || (ret >= sizeof(artwork)))
{
@@ -911,6 +919,10 @@ artwork_get_dir_image(char *path, int isdir, int max_w, int max_h, int format, s
if (strncmp(path, "http://", strlen("http://")) == 0)
return -1;
/* If Spotify item don't look for artwork */
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
return -1;
ret = snprintf(artwork, sizeof(artwork), "%s", path);
if ((ret < 0) || (ret >= sizeof(artwork)))
{
@@ -981,6 +993,10 @@ artwork_get_parentdir_image(char *path, int isdir, int max_w, int max_h, int for
if (strncmp(path, "http://", strlen("http://")) == 0)
return -1;
/* If Spotify item don't look for artwork */
if (strncmp(path, "spotify:", strlen("spotify:")) == 0)
return -1;
ret = snprintf(artwork, sizeof(artwork), "%s", path);
if ((ret < 0) || (ret >= sizeof(artwork)))
{

View File

@@ -100,6 +100,14 @@ static cfg_opt_t sec_airplay[] =
CFG_END()
};
/* Spotify section structure */
static cfg_opt_t sec_spotify[] =
{
CFG_STR("settings_dir", STATEDIR "/cache/" PACKAGE "/libspotify", CFGF_NONE),
CFG_STR("cache_dir", "/tmp", CFGF_NONE),
CFG_END()
};
/* Config file structure */
static cfg_opt_t toplvl_cfg[] =
{
@@ -107,6 +115,7 @@ static cfg_opt_t toplvl_cfg[] =
CFG_SEC("library", sec_library, CFGF_NONE),
CFG_SEC("audio", sec_audio, CFGF_NONE),
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
CFG_SEC("spotify", sec_spotify, CFGF_NONE),
CFG_END()
};

132
src/db.c
View File

@@ -273,6 +273,7 @@ static const char *sort_clause[] =
"ORDER BY f.title_sort ASC",
"ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC",
"ORDER BY f.album_artist_sort ASC",
"ORDER BY f.special_id DESC, f.title ASC",
};
static char *db_path;
@@ -876,6 +877,7 @@ db_build_query_pls(struct query_params *qp, char **q)
{
char *query;
char *idx;
const char *sort;
int ret;
qp->results = db_get_count("SELECT COUNT(*) FROM playlists p WHERE p.disabled = 0;");
@@ -887,14 +889,16 @@ db_build_query_pls(struct query_params *qp, char **q)
if (ret < 0)
return -1;
sort = sort_clause[qp->sort];
if (idx && qp->filter)
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s %s;", qp->filter, idx);
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s %s %s;", qp->filter, sort, idx);
else if (idx)
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 %s;", idx);
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 %s %s;", sort, idx);
else if (qp->filter)
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s;", qp->filter);
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 AND %s %s;", qp->filter, sort);
else
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0;");
query = sqlite3_mprintf("SELECT f.* FROM playlists f WHERE f.disabled = 0 %s;", sort);
if (!query)
{
@@ -1281,10 +1285,10 @@ db_build_query_browse(struct query_params *qp, char *field, char *sort_field, ch
int ret;
if (qp->filter)
count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != '' AND %s;",
count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.disabled = 0 AND f.%s != '' AND %s;",
field, field, qp->filter);
else
count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != '';",
count = sqlite3_mprintf("SELECT COUNT(DISTINCT f.%s) FROM files f WHERE f.disabled = 0 AND f.%s != '';",
field, field);
if (!count)
@@ -1323,16 +1327,16 @@ db_build_query_browse(struct query_params *qp, char *field, char *sort_field, ch
}
if (idx && qp->filter)
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != ''"
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
" AND %s %s %s;", field, sort_field, field, qp->filter, sort, idx);
else if (idx)
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != ''"
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
" %s %s;", field, sort_field, field, sort, idx);
else if (qp->filter)
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != ''"
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != ''"
" AND %s %s;", field, sort_field, field, qp->filter, sort);
else
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.data_kind = 0 AND f.disabled = 0 AND f.%s != '' %s",
query = sqlite3_mprintf("SELECT DISTINCT f.%s, f.%s FROM files f WHERE f.disabled = 0 AND f.%s != '' %s",
field, sort_field, field, sort);
free(sort);
@@ -3045,6 +3049,41 @@ db_pl_add_item_byid(int plid, int fileid)
#undef Q_TMPL
}
int
db_pl_update(char *title, char *path, int id)
{
#define Q_TMPL "UPDATE playlists SET title = '%q', db_timestamp = %" PRIi64 ", path = '%q' WHERE id = %d;"
char *query;
char *errmsg;
int ret;
query = sqlite3_mprintf(Q_TMPL, title, (int64_t)time(NULL), path, id);
if (!query)
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return -1;
}
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
ret = db_exec(query, &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Query error: %s\n", errmsg);
sqlite3_free(errmsg);
sqlite3_free(query);
return -1;
}
sqlite3_free(errmsg);
sqlite3_free(query);
return 0;
#undef Q_TMPL
}
void
db_pl_clear_items(int id)
{
@@ -3427,6 +3466,79 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
#undef Q_TMPL
}
#ifdef HAVE_SPOTIFY_H
/* Spotify */
void
db_spotify_purge(void)
{
char *queries[3] =
{
"DELETE FROM files WHERE path LIKE 'spotify:%%';",
"DELETE FROM playlistitems WHERE filepath LIKE 'spotify:%%';",
"DELETE FROM playlists WHERE path LIKE 'spotify:%%';",
};
char *errmsg;
int i;
int ret;
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
{
DPRINTF(E_DBG, L_DB, "Running spotify purge query '%s'\n", queries[i]);
ret = db_exec(queries[i], &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Purge query %d error: %s\n", i, errmsg);
sqlite3_free(errmsg);
}
else
DPRINTF(E_DBG, L_DB, "Purged %d rows\n", sqlite3_changes(hdl));
}
}
/* Spotify */
void
db_spotify_pl_delete(int id)
{
char *queries[3] = { NULL, NULL, NULL };
char *queries_tmpl[3] =
{
"DELETE FROM playlists WHERE id = %d;",
"DELETE FROM playlistitems WHERE playlistid = %d;",
"DELETE FROM files WHERE path LIKE 'spotify:%%' AND NOT path IN (SELECT filepath FROM playlistitems WHERE id <> %d);",
};
char *errmsg;
int i;
int ret;
for (i = 0; i < (sizeof(queries_tmpl) / sizeof(queries_tmpl[0])); i++)
{
queries[i] = sqlite3_mprintf(queries_tmpl[i], id);
if (!queries[i])
{
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
return;
}
}
for (i = 0; i < (sizeof(queries) / sizeof(queries[0])); i++)
{
DPRINTF(E_DBG, L_DB, "Running spotify playlist delete query '%s'\n", queries[i]);
ret = db_exec(queries[i], &errmsg);
if (ret != SQLITE_OK)
{
DPRINTF(E_LOG, L_DB, "Spotify playlist delete %d error: %s\n", i, errmsg);
sqlite3_free(errmsg);
}
else
DPRINTF(E_DBG, L_DB, "Deleted %d rows\n", sqlite3_changes(hdl));
}
}
#endif
/* Speakers */
int

View File

@@ -21,6 +21,7 @@ enum sort_type {
S_NAME,
S_ALBUM,
S_ARTIST,
S_PLAYLIST,
};
#define Q_F_BROWSE (1 << 15)
@@ -45,6 +46,7 @@ enum query_type {
#define ARTWORK_OWN 3
#define ARTWORK_DIR 4
#define ARTWORK_PARENTDIR 5
#define ARTWORK_SPOTIFY 6
struct query_params {
/* Query parameters, filled in by caller */
@@ -430,6 +432,9 @@ db_pl_add_item_byid(int plid, int fileid);
void
db_pl_clear_items(int id);
int
db_pl_update(char *title, char *path, int id);
void
db_pl_delete(int id);
@@ -459,6 +464,15 @@ db_pairing_add(struct pairing_info *pi);
int
db_pairing_fetch_byguid(struct pairing_info *pi);
#ifdef HAVE_SPOTIFY_H
/* Spotify */
void
db_spotify_purge(void);
void
db_spotify_pl_delete(int id);
#endif
/* Speakers */
int
db_speaker_save(uint64_t id, int selected, int volume);

View File

@@ -60,6 +60,10 @@
#include "misc.h"
#include "remote_pairing.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#define F_SCAN_BULK (1 << 0)
#define F_SCAN_RESCAN (1 << 1)
@@ -368,25 +372,23 @@ fixup_tags(struct media_file_info *mfi)
void
process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf_ctx *extinf)
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi)
{
struct media_file_info mfi;
struct media_file_info *mfi;
char *filename;
char *ext;
time_t stamp;
int id;
int ret;
filename = strrchr(file, '/');
if (!filename)
{
DPRINTF(E_LOG, L_SCAN, "Could not determine filename for %s\n", file);
return;
}
filename = strrchr(path, '/');
if ((!filename) || (strlen(filename) == 1))
filename = path;
else
filename++;
/* File types which should never be processed */
ext = strrchr(file, '.');
ext = strrchr(path, '.');
if (ext)
{
if ((strcasecmp(ext, ".pls") == 0) || (strcasecmp(ext, ".url") == 0))
@@ -400,7 +402,7 @@ process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf
/* Artwork files - don't scan */
return;
}
else if ((strlen(filename) > 1) && ((filename[1] == '_') || (filename[1] == '.')))
else if ((filename[0] == '_') || (filename[0] == '.'))
{
/* Hidden files - don't scan */
return;
@@ -412,7 +414,7 @@ process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf
}
}
db_file_stamp_bypath(file, &stamp, &id);
db_file_stamp_bypath(path, &stamp, &id);
if (stamp >= mtime)
{
@@ -420,78 +422,88 @@ process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf
return;
}
memset(&mfi, 0, sizeof(struct media_file_info));
if (stamp)
mfi.id = db_file_id_bypath(file);
mfi.fname = strdup(filename + 1);
if (!mfi.fname)
if (!external_mfi)
{
DPRINTF(E_WARN, L_SCAN, "Out of memory for fname\n");
mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
if (!mfi)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
return;
}
return;
}
mfi.path = strdup(file);
if (!mfi.path)
{
DPRINTF(E_WARN, L_SCAN, "Out of memory for path\n");
free(mfi.fname);
return;
}
mfi.time_modified = mtime;
mfi.file_size = size;
if (!(type & F_SCAN_TYPE_URL))
{
mfi.data_kind = 0; /* real file */
ret = scan_metadata_ffmpeg(file, &mfi);
memset(mfi, 0, sizeof(struct media_file_info));
}
else
mfi = external_mfi;
if (stamp)
mfi->id = db_file_id_bypath(path);
mfi->fname = strdup(filename);
if (!mfi->fname)
{
mfi.data_kind = 1; /* url/stream */
if (extinf && extinf->found)
{
mfi.artist = strdup(extinf->artist);
mfi.title = strdup(extinf->artist);
mfi.album = strdup(extinf->title);
}
ret = scan_metadata_icy(file, &mfi);
DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
goto out;
}
mfi->path = strdup(path);
if (!mfi->path)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
goto out;
}
mfi->time_modified = mtime;
mfi->file_size = size;
if (type & F_SCAN_TYPE_FILE)
{
mfi->data_kind = 0; /* real file */
ret = scan_metadata_ffmpeg(path, mfi);
}
else if (type & F_SCAN_TYPE_URL)
{
mfi->data_kind = 1; /* url/stream */
ret = scan_metadata_icy(path, mfi);
}
else if (type & F_SCAN_TYPE_SPOTIFY)
{
mfi->data_kind = 2; /* iTunes has no spotify data kind, but we use 2 */
ret = mfi->artist && mfi->album && mfi->title;
}
else
ret = -1;
if (ret < 0)
{
DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", file);
DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for %s\n", path);
goto out;
}
if (type & F_SCAN_TYPE_COMPILATION)
mfi.compilation = 1;
mfi->compilation = 1;
if (type & F_SCAN_TYPE_PODCAST)
mfi.media_kind = 4; /* podcast */
mfi->media_kind = 4; /* podcast */
if (type & F_SCAN_TYPE_AUDIOBOOK)
mfi.media_kind = 8; /* audiobook */
mfi->media_kind = 8; /* audiobook */
if (!mfi.item_kind)
mfi.item_kind = 2; /* music */
if (!mfi.media_kind)
mfi.media_kind = 1; /* music */
if (!mfi->item_kind)
mfi->item_kind = 2; /* music */
if (!mfi->media_kind)
mfi->media_kind = 1; /* music */
unicode_fixup_mfi(&mfi);
unicode_fixup_mfi(mfi);
fixup_tags(&mfi);
fixup_tags(mfi);
if (mfi.id == 0)
db_file_add(&mfi);
if (mfi->id == 0)
db_file_add(mfi);
else
db_file_update(&mfi);
db_file_update(mfi);
out:
free_mfi(&mfi, 1);
if (!external_mfi)
free_mfi(mfi, 0);
}
static void
@@ -594,6 +606,14 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
return;
}
#ifdef HAVE_SPOTIFY_H
else if (strcmp(ext, ".spotify") == 0)
{
spotify_login(file);
return;
}
#endif
else if (strcmp(ext, ".force-rescan") == 0)
{
if (flags & F_SCAN_BULK)
@@ -610,7 +630,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
}
/* Not any kind of special file, so let's see if it's a media file */
process_media_file(file, mtime, size, type, NULL);
filescanner_process_media(file, mtime, size, type, NULL);
}
/* Thread: scan */
@@ -1109,12 +1129,12 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
* We want to scan the new file and we want to rescan the
* playlist to update playlist items (relative items).
*/
ie->mask |= IN_CREATE;
ie->mask |= IN_CLOSE_WRITE;
db_pl_enable_bycookie(ie->cookie, wi->path);
}
}
if (ie->mask & (IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE))
if (ie->mask & IN_CLOSE_WRITE)
{
ret = lstat(path, &sb);
if (ret < 0)
@@ -1475,6 +1495,12 @@ exit_cb(int fd, short event, void *arg)
}
int
filescanner_status(void)
{
return scan_exit;
}
/* Thread: main */
int
filescanner_init(void)

View File

@@ -4,10 +4,12 @@
#include "db.h"
#define F_SCAN_TYPE_PODCAST (1 << 0)
#define F_SCAN_TYPE_AUDIOBOOK (1 << 1)
#define F_SCAN_TYPE_COMPILATION (1 << 2)
#define F_SCAN_TYPE_URL (1 << 3)
#define F_SCAN_TYPE_FILE (1 << 0)
#define F_SCAN_TYPE_PODCAST (F_SCAN_TYPE_FILE | (1 << 1))
#define F_SCAN_TYPE_AUDIOBOOK (F_SCAN_TYPE_FILE | (1 << 2))
#define F_SCAN_TYPE_COMPILATION (F_SCAN_TYPE_FILE | (1 << 3))
#define F_SCAN_TYPE_URL (1 << 4)
#define F_SCAN_TYPE_SPOTIFY (1 << 5)
int
filescanner_init(void);
@@ -15,15 +17,11 @@ filescanner_init(void);
void
filescanner_deinit(void);
struct extinf_ctx
{
char *artist;
char *title;
int found;
};
int
filescanner_status(void);
void
process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf_ctx *extinf);
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi);
/* Actual scanners */
int

View File

@@ -42,7 +42,7 @@
/* Get metadata from the EXTINF tag */
static int
extinf_get(char *string, struct extinf_ctx *extinf)
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
{
char *ptr;
@@ -54,20 +54,20 @@ extinf_get(char *string, struct extinf_ctx *extinf)
return 0;
/* New extinf found, so clear old data */
if (extinf->found)
if (*extinf)
{
free(extinf->artist);
free(extinf->title);
free(mfi->artist);
free(mfi->title);
}
extinf->found = 1;
extinf->artist = strdup(ptr + 1);
*extinf = 1;
mfi->artist = strdup(ptr + 1);
ptr = strstr(extinf->artist, " -");
ptr = strstr(mfi->artist, " -");
if (ptr && strlen(ptr) > 3)
extinf->title = strdup(ptr + 3);
mfi->title = strdup(ptr + 3);
else
extinf->title = strdup("");
mfi->title = strdup("");
if (ptr)
*ptr = '\0';
@@ -75,28 +75,29 @@ extinf_get(char *string, struct extinf_ctx *extinf)
}
static void
extinf_reset(struct extinf_ctx *extinf)
extinf_reset(struct media_file_info *mfi, int *extinf)
{
if (extinf->found)
if (*extinf)
{
free(extinf->artist);
free(extinf->title);
free(mfi->artist);
free(mfi->title);
}
extinf->found = 0;
*extinf = 0;
}
void
scan_m3u_playlist(char *file, time_t mtime)
{
FILE *fp;
struct media_file_info mfi;
struct playlist_info *pli;
struct stat sb;
struct extinf_ctx extinf;
char buf[PATH_MAX];
char *entry;
char *filename;
char *ptr;
size_t len;
int extinf;
int pl_id;
int mfi_id;
int ret;
@@ -104,6 +105,8 @@ scan_m3u_playlist(char *file, time_t mtime)
DPRINTF(E_INFO, L_SCAN, "Processing static playlist: %s\n", file);
memset(&mfi, 0, sizeof(struct media_file_info));
ret = stat(file, &sb);
if (ret < 0)
{
@@ -167,7 +170,7 @@ scan_m3u_playlist(char *file, time_t mtime)
DPRINTF(E_INFO, L_SCAN, "Added playlist as id %d\n", pl_id);
}
extinf.found = 0;
extinf = 0;
while (fgets(buf, sizeof(buf), fp) != NULL)
{
@@ -189,7 +192,7 @@ scan_m3u_playlist(char *file, time_t mtime)
continue;
/* Saves metadata in extinf if EXTINF metadata line */
if (extinf_get(buf, &extinf))
if (extinf_get(buf, &mfi, &extinf))
continue;
/* Check that first char is sane for a path */
@@ -209,10 +212,10 @@ scan_m3u_playlist(char *file, time_t mtime)
continue;
}
if (extinf.found)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", extinf.artist, extinf.title);
if (extinf)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title);
process_media_file(filename, mtime, 0, F_SCAN_TYPE_URL, &extinf);
filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi);
}
/* Regular file */
else
@@ -271,7 +274,7 @@ scan_m3u_playlist(char *file, time_t mtime)
if (ret < 0)
DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename);
extinf_reset(&extinf);
extinf_reset(&mfi, &extinf);
free(filename);
}

View File

@@ -395,8 +395,8 @@ httpd_stream_file(struct evhttp_request *req, int id)
stream_cb = stream_chunk_xcode_cb;
st->xcode = transcode_setup(mfi, &st->size, 1);
if (!st->xcode)
ret = transcode_setup(&st->xcode, mfi, &st->size, 1);
if (ret < 0)
{
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");

View File

@@ -73,7 +73,7 @@ struct uri_map {
struct daap_session {
int id;
char *user_agent;
struct event timeout;
};
@@ -132,7 +132,12 @@ daap_session_free(void *item)
s = (struct daap_session *)item;
if (event_initialized(&s->timeout)) evtimer_del(&s->timeout);
if (event_initialized(&s->timeout))
evtimer_del(&s->timeout);
if (s->user_agent)
free(s->user_agent);
free(s);
}
@@ -155,7 +160,7 @@ daap_session_timeout_cb(int fd, short what, void *arg)
}
static struct daap_session *
daap_session_register(void)
daap_session_register(const char *user_agent)
{
struct timeval tv;
struct daap_session *s;
@@ -175,6 +180,9 @@ daap_session_register(void)
next_session_id++;
if (user_agent)
s->user_agent = strdup(user_agent);
if (DAAP_SESSION_TIMEOUT > 0) {
evtimer_set(&s->timeout, daap_session_timeout_cb, s);
event_base_set(evbase_httpd, &s->timeout);
@@ -185,6 +193,9 @@ daap_session_register(void)
{
DPRINTF(E_LOG, L_DAAP, "Could not register DAAP session: %s\n", strerror(errno));
if (user_agent)
free(s->user_agent);
free(s);
return NULL;
}
@@ -466,6 +477,47 @@ daap_sort_finalize(struct sort_ctx *ctx)
dmap_add_int(ctx->headerlist, "mshn", ctx->misc_mshn); /* 12 */
}
/* We try not to return items that the client cannot play (like Spotify and
* internet streams in iTunes), or which are inappropriate (like internet streams
* in the album tab in Remote
*/
static void
user_agent_filter(const char *user_agent, struct query_params *qp)
{
char *filter;
char *buffer;
int len;
if (!user_agent)
return;
if (strcasestr(user_agent, "itunes"))
filter = strdup("(f.data_kind = 0)"); // Only real files
else if (strcasestr(user_agent, "daap"))
filter = strdup("(f.data_kind = 0)"); // Only real files
else if (strcasestr(user_agent, "remote"))
filter = strdup("(f.data_kind <> 1)"); // No internet radio
else if (strcasestr(user_agent, "android"))
filter = strdup("(f.data_kind <> 1)"); // No internet radio
else
return;
if (qp->filter)
{
len = strlen(qp->filter) + strlen(" AND ") + strlen(filter);
buffer = (char *)malloc(len + 1);
snprintf(buffer, len + 1, "%s AND %s", qp->filter, filter);
free(qp->filter);
qp->filter = strdup(buffer);
free(buffer);
}
else
qp->filter = strdup(filter);
DPRINTF(E_DBG, L_DAAP, "SQL filter w/client mod: %s\n", qp->filter);
free(filter);
}
static void
get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params *qp)
@@ -807,7 +859,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
free_pi(&pi, 1);
}
s = daap_session_register();
s = daap_session_register(ua);
if (!s)
{
dmap_send_error(req, "mlog", "Could not start session");
@@ -987,6 +1039,7 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
static void
daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, int playlist, struct evkeyvalq *query)
{
struct daap_session *s;
struct query_params qp;
struct db_media_file_info dbmfi;
struct evbuffer *song;
@@ -1001,6 +1054,10 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
int transcode;
int ret;
s = daap_session_find(req, query, evbuf);
if (!s)
return;
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
if (playlist != -1)
@@ -1082,6 +1139,8 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
memset(&qp, 0, sizeof(struct query_params));
get_query_params(query, &sort_headers, &qp);
if (playlist == -1)
user_agent_filter(s->user_agent, &qp);
sctx = NULL;
if (sort_headers)
@@ -1242,26 +1301,15 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
static void
daap_reply_dbsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
{
struct daap_session *s;
s = daap_session_find(req, query, evbuf);
if (!s)
return;
daap_reply_songlist_generic(req, evbuf, -1, query);
}
static void
daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
{
struct daap_session *s;
int playlist;
int ret;
s = daap_session_find(req, query, evbuf);
if (!s)
return;
ret = safe_atoi32(uri[3], &playlist);
if (ret < 0)
{
@@ -1364,6 +1412,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
memset(&qp, 0, sizeof(struct query_params));
get_query_params(query, NULL, &qp);
qp.type = Q_PL;
qp.sort = S_PLAYLIST;
ret = db_query_start(&qp);
if (ret < 0)
@@ -1545,6 +1594,7 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
memset(&qp, 0, sizeof(struct query_params));
get_query_params(query, &sort_headers, &qp);
user_agent_filter(s->user_agent, &qp);
param = evhttp_find_header(query, "group-type");
if (strcmp(param, "artists") == 0)
@@ -1833,6 +1883,7 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
memset(&qp, 0, sizeof(struct query_params));
get_query_params(query, &sort_headers, &qp);
user_agent_filter(s->user_agent, &qp);
if (strcmp(uri[3], "artists") == 0)
{

View File

@@ -43,7 +43,7 @@ static int threshold;
static int console;
static char *logfilename;
static FILE *logfile;
static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf" };
static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify" };
static int

View File

@@ -26,8 +26,9 @@
#define L_LAUDIO 17
#define L_DMAP 18
#define L_DBPERF 19
#define L_SPOTIFY 20
#define N_LOGDOMAINS 20
#define N_LOGDOMAINS 21
/* Severities */
#define E_FATAL 0

View File

@@ -67,6 +67,9 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
# include "ffmpeg_url_evbuffer.h"
#endif
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#define PIDFILE STATEDIR "/run/" PACKAGE ".pid"
@@ -678,6 +681,15 @@ main(int argc, char **argv)
goto filescanner_fail;
}
#ifdef HAVE_SPOTIFY_H
/* Spawn Spotify thread */
ret = spotify_init();
if (ret < 0)
{
DPRINTF(E_INFO, L_MAIN, "Spotify thread not started\n");;
}
#endif
/* Spawn player thread */
ret = player_init();
if (ret != 0)
@@ -786,6 +798,10 @@ main(int argc, char **argv)
player_deinit();
player_fail:
#ifdef HAVE_SPOTIFY_H
DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n");
spotify_deinit();
#endif
DPRINTF(E_LOG, L_MAIN, "File scanner deinit\n");
filescanner_deinit();

View File

@@ -59,6 +59,9 @@
#include "raop.h"
#include "laudio.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#ifndef MIN
# define MIN(a, b) ((a < b) ? a : b)
@@ -882,8 +885,19 @@ player_queue_make_pl(int plid, uint32_t *id)
static void
source_free(struct player_source *ps)
{
if (ps->ctx)
transcode_cleanup(ps->ctx);
switch (ps->type)
{
case SOURCE_FFMPEG:
if (ps->ctx)
transcode_cleanup(ps->ctx);
break;
case SOURCE_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
spotify_playback_stop();
#endif
break;
}
free(ps);
}
@@ -895,11 +909,22 @@ source_stop(struct player_source *ps)
while (ps)
{
if (ps->ctx)
switch (ps->type)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
}
case SOURCE_FFMPEG:
if (ps->ctx)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
}
break;
case SOURCE_SPOTIFY:
#ifdef HAVE_SPOTIFY_H
spotify_playback_stop();
#endif
break;
}
tmp = ps;
ps = ps->play_next;
@@ -989,7 +1014,9 @@ static int
source_open(struct player_source *ps, int no_md)
{
struct media_file_info *mfi;
int ret;
ps->setup_done = 0;
ps->stream_start = 0;
ps->output_start = 0;
ps->end = 0;
@@ -1013,11 +1040,24 @@ source_open(struct player_source *ps, int no_md)
DPRINTF(E_DBG, L_PLAYER, "Opening %s\n", mfi->path);
ps->ctx = transcode_setup(mfi, NULL, 0);
if (strncmp(mfi->path, "spotify:", strlen("spotify:")) == 0)
{
ps->type = SOURCE_SPOTIFY;
#ifdef HAVE_SPOTIFY_H
ret = spotify_playback_play(mfi);
#else
ret = -1;
#endif
}
else
{
ps->type = SOURCE_FFMPEG;
ret = transcode_setup(&ps->ctx, mfi, NULL, 0);
}
free_mfi(mfi, 0);
if (!ps->ctx)
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not open file id %d\n", ps->id);
@@ -1027,6 +1067,8 @@ source_open(struct player_source *ps, int no_md)
if (!no_md)
metadata_send(ps, (player_state == PLAY_PLAYING) ? 0 : 1);
ps->setup_done = 1;
return 0;
}
@@ -1067,7 +1109,7 @@ source_next(int force)
if (!cur_streaming)
break;
if (cur_streaming->ctx)
if ((cur_streaming->type == SOURCE_FFMPEG) && cur_streaming->ctx)
{
ret = transcode_seek(cur_streaming->ctx, 0);
@@ -1276,10 +1318,13 @@ source_check(void)
{
cur_playing = cur_playing->play_next;
if (ps->ctx)
if (ps->setup_done)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
}
ps->play_next = NULL;
}
}
@@ -1324,10 +1369,13 @@ source_check(void)
cur_playing->stream_start = ps->end + 1;
cur_playing->output_start = cur_playing->stream_start;
if (ps->ctx)
if (ps->setup_done)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
{
transcode_cleanup(ps->ctx);
ps->ctx = NULL;
}
ps->play_next = NULL;
}
}
@@ -1371,7 +1419,22 @@ source_read(uint8_t *buf, int len, uint64_t rtptime)
if (EVBUFFER_LENGTH(audio_buf) == 0)
{
ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes);
switch (cur_streaming->type)
{
case SOURCE_FFMPEG:
ret = transcode(cur_streaming->ctx, audio_buf, len - nbytes);
break;
#ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY:
ret = spotify_audio_get(audio_buf, len - nbytes);
break;
#endif
default:
ret = -1;
}
if (ret <= 0)
{
/* EOF or error */
@@ -2555,7 +2618,20 @@ playback_seek_bh(struct player_command *cmd)
ps->end = 0;
/* Seek to commanded position */
ret = transcode_seek(ps->ctx, ms);
switch (ps->type)
{
case SOURCE_FFMPEG:
ret = transcode_seek(ps->ctx, ms);
break;
#ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY:
ret = spotify_playback_seek(ms);
break;
#endif
default:
ret = -1;
}
if (ret < 0)
{
playback_abort();
@@ -2596,7 +2672,20 @@ playback_pause_bh(struct player_command *cmd)
pos -= ps->stream_start;
ms = (int)((pos * 1000) / 44100);
ret = transcode_seek(ps->ctx, ms);
switch (ps->type)
{
case SOURCE_FFMPEG:
ret = transcode_seek(ps->ctx, ms);
break;
#ifdef HAVE_SPOTIFY_H
case SOURCE_SPOTIFY:
ret = spotify_playback_seek(ms);
break;
#endif
default:
ret = -1;
}
if (ret < 0)
{
playback_abort();

View File

@@ -33,6 +33,11 @@ enum repeat_mode {
REPEAT_ALL = 2,
};
enum source_type {
SOURCE_FFMPEG = 0,
SOURCE_SPOTIFY = 1,
};
struct spk_flags {
unsigned selected:1;
unsigned has_password:1;
@@ -60,6 +65,9 @@ struct player_source
{
uint32_t id;
enum source_type type;
int setup_done;
uint64_t stream_start;
uint64_t output_start;
uint64_t end;

1548
src/spotify.c Normal file

File diff suppressed because it is too large Load Diff

32
src/spotify.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef __SPOTIFY_H__
#define __SPOTIFY_H__
#include "db.h"
#include "evhttp/evhttp.h"
int
spotify_playback_play(struct media_file_info *mfi);
int
spotify_playback_pause(void);
int
spotify_playback_stop(void);
int
spotify_playback_seek(int ms);
int
spotify_audio_get(struct evbuffer *evbuf, int wanted);
void
spotify_login(char *path);
int
spotify_init(void);
void
spotify_deinit(void);
#endif /* !__SPOTIFY_H__ */

View File

@@ -54,6 +54,9 @@
#include "db.h"
#include "transcode.h"
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#if LIBAVCODEC_VERSION_MAJOR >= 56 || (LIBAVCODEC_VERSION_MAJOR == 55 && LIBAVCODEC_VERSION_MINOR >= 18)
# define XCODE_BUFFER_SIZE ((192000 * 3) / 2)
@@ -462,9 +465,8 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
return got_ms;
}
struct transcode_ctx *
transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
int
transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t *est_size, int wavhdr)
{
struct transcode_ctx *ctx;
int ret;
@@ -474,7 +476,7 @@ transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
{
DPRINTF(E_WARN, L_XCODE, "Could not allocate transcode context\n");
return NULL;
return -1;
}
memset(ctx, 0, sizeof(struct transcode_ctx));
@@ -488,7 +490,7 @@ transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
DPRINTF(E_WARN, L_XCODE, "Could not open file %s: %s\n", mfi->fname, strerror(AVUNERROR(ret)));
free(ctx);
return NULL;
return -1;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
@@ -662,7 +664,9 @@ transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
if (wavhdr)
make_wav_header(ctx, est_size);
return ctx;
*nctx = ctx;
return 0;
setup_fail_codec:
avcodec_close(ctx->acodec);
@@ -675,7 +679,7 @@ transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
#endif
free(ctx);
return NULL;
return -1;
}
void
@@ -684,11 +688,14 @@ transcode_cleanup(struct transcode_ctx *ctx)
if (ctx->apacket.data)
av_free_packet(&ctx->apacket);
avcodec_close(ctx->acodec);
if (ctx->acodec)
avcodec_close(ctx->acodec);
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21)
avformat_close_input(&ctx->fmtctx);
if (ctx->fmtctx)
avformat_close_input(&ctx->fmtctx);
#else
av_close_input_file(ctx->fmtctx);
if (ctx->fmtctx)
av_close_input_file(ctx->fmtctx);
#endif
av_free(ctx->abuffer);
@@ -717,6 +724,12 @@ transcode_needed(struct evkeyvalq *headers, char *file_codectype)
int size;
int i;
if (!file_codectype)
{
DPRINTF(E_LOG, L_XCODE, "Can't proceed, codectype is unknown (null)\n");
return -1;
}
DPRINTF(E_DBG, L_XCODE, "Determining transcoding status for codectype %s\n", file_codectype);
lib = cfg_getsec(cfg, "library");

View File

@@ -12,8 +12,8 @@ transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted);
int
transcode_seek(struct transcode_ctx *ctx, int ms);
struct transcode_ctx *
transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr);
int
transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t *est_size, int wavhdr);
void
transcode_cleanup(struct transcode_ctx *ctx);