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:
parent
190f91114e
commit
7ed6cc98c3
28
INSTALL
28
INSTALL
|
@ -97,6 +97,8 @@ Libraries:
|
|||
from <http://developer.kde.org/~wheeler/taglib.html>
|
||||
- libplist 0.16+ (optional - iTunes XML support)
|
||||
from <http://github.com/JonathanBeck/libplist/downloads>
|
||||
- libspotify (optional - Spotify support)
|
||||
from <https://developer.spotify.com>
|
||||
|
||||
If using binary packages, remember that you need the development packages to
|
||||
build forked-daapd (usually named -dev or -devel).
|
||||
|
@ -143,14 +145,22 @@ needed.
|
|||
|
||||
To display the configure options run ./configure --help
|
||||
|
||||
FLAC and Musepack support are optional. If not enabled, metadata extraction
|
||||
will fail on these files.
|
||||
Support for Spotify is optional. Use --enable-spotify to enable this feature.
|
||||
If you enable this feature libspotify/api.h is required at compile time.
|
||||
Forked-daapd uses runtime dynamic linking to the libspotify library, so even
|
||||
though you compiled with --enable-spotify, the executable will still be able
|
||||
to run on systems without libspotify (the Spotify features will then be
|
||||
disabled).
|
||||
|
||||
Support for iTunes Music Library XML format is optional. Use --enable-itunes
|
||||
to enable this feature.
|
||||
|
||||
FLAC and Musepack support are optional, and they are probably only required
|
||||
if your version of libav/ffmpeg is very old (version 0.5 or 0.6). Use
|
||||
--enable-flac and --enable-musepack to enable.
|
||||
|
||||
Recommended build settings:
|
||||
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-flac
|
||||
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
|
||||
|
||||
After configure run the usual make, and if that went well, sudo make install
|
||||
|
||||
|
@ -189,12 +199,14 @@ The LSB header below sums it up:
|
|||
# Required-Start: $local_fs $remote_fs $network $time
|
||||
# Required-Stop: $local_fs $remote_fs $network $time
|
||||
# Should-Start: avahi
|
||||
# Should-Stop: avahi
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: media server with support for RSP, DAAP, DACP and AirPlay
|
||||
# Short-Description: DAAP/DACP (iTunes) server, support for AirPlay and Spotify
|
||||
# Description: forked-daapd is an iTunes-compatible media server for
|
||||
# sharing your media library over the local network with RSP
|
||||
# clients like the SoundBridge from Roku and DAAP clients
|
||||
# like iTunes. It can also stream music to AirPlay devices,
|
||||
# and it can be controlled by Apple Remote (and compatibles).
|
||||
# sharing your media library over the local network with DAAP
|
||||
# clients like iTunes. Like iTunes, it can be controlled by
|
||||
# Apple Remote (and compatibles) and stream music directly to
|
||||
# AirPlay devices. It also supports streaming to RSP clients
|
||||
# (Roku devices) and streaming from Spotify.
|
||||
### END INIT INFO
|
||||
|
|
|
@ -8,5 +8,5 @@ SUBDIRS = sqlext src
|
|||
man_MANS = forked-daapd.8
|
||||
|
||||
install-data-hook:
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/cache/forked-daapd
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/cache/forked-daapd/libspotify
|
||||
|
||||
|
|
64
README
64
README
|
@ -1,8 +1,10 @@
|
|||
forked-daapd
|
||||
------------
|
||||
|
||||
forked-daapd is a DAAP (iTunes) and RSP (Roku) media server, with support for
|
||||
Linux and FreeBSD.
|
||||
forked-daapd is a Linux/FreeBSD DAAP (iTunes) and RSP (Roku) media server.
|
||||
|
||||
It has support for AirPlay devices/speakers, Apple Remote (and compatibles),
|
||||
internet radio and Spotify.
|
||||
|
||||
DAAP stands for Digital Audio Access Protocol, and is the protocol used
|
||||
by iTunes and friends to share/stream media libraries over the network.
|
||||
|
@ -21,6 +23,21 @@ The original (now unmaintained) source can be found here:
|
|||
forked-daapd is a complete rewrite of mt-daapd (Firefly Media Server).
|
||||
|
||||
|
||||
Contents of this README
|
||||
-----------------------
|
||||
|
||||
- Supported clients
|
||||
- Using Remote
|
||||
- AirPlay devices/speakers
|
||||
- Local audio output
|
||||
- Supported formats
|
||||
- Streaming MPEG4
|
||||
- Playlists and internet radio
|
||||
- Artwork
|
||||
- Library
|
||||
- Spotify
|
||||
|
||||
|
||||
Supported clients
|
||||
-----------------
|
||||
|
||||
|
@ -198,12 +215,15 @@ happily write the metadata back at the end of the file after you've modified
|
|||
them. Watch out for that.
|
||||
|
||||
|
||||
Playlists
|
||||
---------
|
||||
Playlists and internet radio
|
||||
----------------------------
|
||||
|
||||
forked-daapd supports M3U playlists. Just drop your playlist somewhere in
|
||||
your library with an .m3u extension and it will pick it up.
|
||||
|
||||
If the m3u contains an http URL it will be added as an internet radio station,
|
||||
and the URL will be probed for Shoutcast (ICY) metadata.
|
||||
|
||||
Support for iTunes Music Library XML format is available as a compile-time
|
||||
option. By default, metadata from our parsers is preferred over what's in
|
||||
the iTunes DB; use itunes_overrides = true if you prefer iTunes' metadata.
|
||||
|
@ -289,3 +309,39 @@ so changes won't be noticed unless the file happens to be in a directory that
|
|||
is monitored.
|
||||
|
||||
Bottom line: symlinks are for directories only.
|
||||
|
||||
|
||||
Spotify
|
||||
-------
|
||||
|
||||
forked-daapd has *some* support for Spotify. It must be compiled with the
|
||||
--enable-spotify option (see INSTALL). You must have also have libspotify
|
||||
installed, otherwise the Spotify integration will not be available. You can
|
||||
get libspotify here:
|
||||
|
||||
- Original (binary) tar.gz, see <https://developer.spotify.com>
|
||||
- Debian package (libspotify12), see <https://apt.mopidy.com>
|
||||
|
||||
You must also have a Spotify premium account.
|
||||
|
||||
The procedure for logging in to Spotify is very much like the Remote pairing
|
||||
procedure. You must prepare a file, which should have the ending ".spotify".
|
||||
The file must have two lines: The first is your Spotify user name, and the
|
||||
second is your password. Move the file to your forked-daapd library.
|
||||
Forked-daapd will then log in and add all the music in your Spotify playlists
|
||||
to its database.
|
||||
|
||||
Spotify will automatically notify forked-daapd about playlist updates, so you
|
||||
should not need to restart forked-daapd to syncronize with Spotify.
|
||||
|
||||
For safety you should delete the ".spotify" file after first login. Forked-daapd
|
||||
will not store your password, but will still be able to log you in automatically
|
||||
afterwards, because libspotify saves a login token. You can configure the
|
||||
location of your Spotify user data in the configuration file.
|
||||
|
||||
Limitations: You will only be able to play tracks from your Spotify playlists,
|
||||
so you can't search and listen to music from the rest of the Spotify catalogue.
|
||||
You will not be able to do any playlist management through forked-daapd - use
|
||||
a Spotify client for that. You also can only listen to your music by letting
|
||||
forked-daapd do the playback - so that means you can't stream from forked-daapd
|
||||
to iTunes. Finally, Spotify artwork is not currently supported.
|
||||
|
|
15
configure.in
15
configure.in
|
@ -60,9 +60,14 @@ AC_ARG_ENABLE(itunes, AC_HELP_STRING([--enable-itunes], [Enable iTunes library s
|
|||
use_itunes=true;
|
||||
CPPFLAGS="${CPPFLAGS} -DITUNES")
|
||||
|
||||
AC_ARG_ENABLE(spotify, AC_HELP_STRING([--enable-spotify], [Enable Spotify library support]),
|
||||
use_spotify=true;
|
||||
CPPFLAGS="${CPPFLAGS} -DSPOTIFY")
|
||||
|
||||
AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue)
|
||||
AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue)
|
||||
AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = xtrue)
|
||||
AM_CONDITIONAL(COND_SPOTIFY, test x$use_spotify = xtrue)
|
||||
|
||||
AC_ARG_WITH(oss4, AC_HELP_STRING([--with-oss4=includedir], [Use OSS4 with soundcard.h in includedir (default /usr/lib/oss/include/sys)]),
|
||||
[ case "$withval" in
|
||||
|
@ -184,6 +189,16 @@ if test x$use_itunes = xtrue; then
|
|||
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
|
||||
fi
|
||||
|
||||
if test x$use_spotify = xtrue; then
|
||||
AC_CHECK_HEADER(libspotify/api.h, , AC_MSG_ERROR([libspotify/api.h not found]))
|
||||
AC_DEFINE(HAVE_SPOTIFY_H, 1, [Define to 1 if you have the <libspotify/api.h> header file.])
|
||||
dnl Don't link to libspotify, but instead enable dynamic linking
|
||||
SPOTIFY_CFLAGS="-rdynamic"
|
||||
SPOTIFY_LIBS="-ldl"
|
||||
AC_SUBST(SPOTIFY_CFLAGS)
|
||||
AC_SUBST(SPOTIFY_LIBS)
|
||||
fi
|
||||
|
||||
case "$host" in
|
||||
*-*-linux-*)
|
||||
if test x$use_oss4 != xtrue; then
|
||||
|
|
|
@ -113,3 +113,11 @@ audio {
|
|||
# AirPlay password
|
||||
# password = "s1kr3t"
|
||||
#}
|
||||
|
||||
# Spotify settings (only have effect if Spotify enabled - see README/INSTALL)
|
||||
spotify {
|
||||
# Directory where user settings should be stored (credentials)
|
||||
# settings_dir = "/var/cache/forked-daapd/libspotify"
|
||||
# Cache directory
|
||||
# cache_dir = "/tmp"
|
||||
}
|
||||
|
|
|
@ -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 = \
|
||||
|
|
|
@ -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)))
|
||||
{
|
||||
|
|
|
@ -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
132
src/db.c
|
@ -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
|
||||
|
|
14
src/db.h
14
src/db.h
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/main.c
16
src/main.c
|
@ -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();
|
||||
|
||||
|
|
125
src/player.c
125
src/player.c
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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__ */
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue