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>
|
from <http://developer.kde.org/~wheeler/taglib.html>
|
||||||
- libplist 0.16+ (optional - iTunes XML support)
|
- libplist 0.16+ (optional - iTunes XML support)
|
||||||
from <http://github.com/JonathanBeck/libplist/downloads>
|
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
|
If using binary packages, remember that you need the development packages to
|
||||||
build forked-daapd (usually named -dev or -devel).
|
build forked-daapd (usually named -dev or -devel).
|
||||||
|
@ -143,14 +145,22 @@ needed.
|
||||||
|
|
||||||
To display the configure options run ./configure --help
|
To display the configure options run ./configure --help
|
||||||
|
|
||||||
FLAC and Musepack support are optional. If not enabled, metadata extraction
|
Support for Spotify is optional. Use --enable-spotify to enable this feature.
|
||||||
will fail on these files.
|
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
|
Support for iTunes Music Library XML format is optional. Use --enable-itunes
|
||||||
to enable this feature.
|
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:
|
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
|
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-Start: $local_fs $remote_fs $network $time
|
||||||
# Required-Stop: $local_fs $remote_fs $network $time
|
# Required-Stop: $local_fs $remote_fs $network $time
|
||||||
# Should-Start: avahi
|
# Should-Start: avahi
|
||||||
|
# Should-Stop: avahi
|
||||||
# Default-Start: 2 3 4 5
|
# Default-Start: 2 3 4 5
|
||||||
# Default-Stop: 0 1 6
|
# 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
|
# Description: forked-daapd is an iTunes-compatible media server for
|
||||||
# sharing your media library over the local network with RSP
|
# sharing your media library over the local network with DAAP
|
||||||
# clients like the SoundBridge from Roku and DAAP clients
|
# clients like iTunes. Like iTunes, it can be controlled by
|
||||||
# like iTunes. It can also stream music to AirPlay devices,
|
# Apple Remote (and compatibles) and stream music directly to
|
||||||
# and it can be controlled by Apple Remote (and compatibles).
|
# AirPlay devices. It also supports streaming to RSP clients
|
||||||
|
# (Roku devices) and streaming from Spotify.
|
||||||
### END INIT INFO
|
### END INIT INFO
|
||||||
|
|
|
@ -8,5 +8,5 @@ SUBDIRS = sqlext src
|
||||||
man_MANS = forked-daapd.8
|
man_MANS = forked-daapd.8
|
||||||
|
|
||||||
install-data-hook:
|
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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
forked-daapd is a DAAP (iTunes) and RSP (Roku) media server, with support for
|
forked-daapd is a Linux/FreeBSD DAAP (iTunes) and RSP (Roku) media server.
|
||||||
Linux and FreeBSD.
|
|
||||||
|
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
|
DAAP stands for Digital Audio Access Protocol, and is the protocol used
|
||||||
by iTunes and friends to share/stream media libraries over the network.
|
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).
|
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
|
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.
|
them. Watch out for that.
|
||||||
|
|
||||||
|
|
||||||
Playlists
|
Playlists and internet radio
|
||||||
---------
|
----------------------------
|
||||||
|
|
||||||
forked-daapd supports M3U playlists. Just drop your playlist somewhere in
|
forked-daapd supports M3U playlists. Just drop your playlist somewhere in
|
||||||
your library with an .m3u extension and it will pick it up.
|
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
|
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
|
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.
|
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.
|
is monitored.
|
||||||
|
|
||||||
Bottom line: symlinks are for directories only.
|
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;
|
use_itunes=true;
|
||||||
CPPFLAGS="${CPPFLAGS} -DITUNES")
|
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_FLAC, test x$use_flac = xtrue)
|
||||||
AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue)
|
AM_CONDITIONAL(COND_MUSEPACK, test x$use_musepack = xtrue)
|
||||||
AM_CONDITIONAL(COND_ITUNES, test x$use_itunes = 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)]),
|
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
|
[ case "$withval" in
|
||||||
|
@ -184,6 +189,16 @@ if test x$use_itunes = xtrue; then
|
||||||
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
|
PKG_CHECK_MODULES(LIBPLIST, [ libplist >= 0.16 ])
|
||||||
fi
|
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
|
case "$host" in
|
||||||
*-*-linux-*)
|
*-*-linux-*)
|
||||||
if test x$use_oss4 != xtrue; then
|
if test x$use_oss4 != xtrue; then
|
||||||
|
|
|
@ -113,3 +113,11 @@ audio {
|
||||||
# AirPlay password
|
# AirPlay password
|
||||||
# password = "s1kr3t"
|
# 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
|
ITUNESSRC=filescanner_itunes.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if COND_SPOTIFY
|
||||||
|
SPOTIFYSRC=spotify.c spotify.h
|
||||||
|
endif
|
||||||
|
|
||||||
if COND_ALSA
|
if COND_ALSA
|
||||||
ALSASRC=laudio_alsa.c
|
ALSASRC=laudio_alsa.c
|
||||||
endif
|
endif
|
||||||
|
@ -59,13 +63,13 @@ forked_daapd_CPPFLAGS = -D_GNU_SOURCE \
|
||||||
forked_daapd_CFLAGS = \
|
forked_daapd_CFLAGS = \
|
||||||
@ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \
|
@ZLIB_CFLAGS@ @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @LIBAV_CFLAGS@ \
|
||||||
@CONFUSE_CFLAGS@ @TAGLIB_CFLAGS@ @MINIXML_CFLAGS@ @LIBPLIST_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 \
|
forked_daapd_LDADD = -lrt \
|
||||||
@ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \
|
@ZLIB_LIBS@ @AVAHI_LIBS@ @SQLITE3_LIBS@ @LIBAV_LIBS@ \
|
||||||
@CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ \
|
@CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ \
|
||||||
@LIBAVL_LIBS@ @MINIXML_LIBS@ @ANTLR3C_LIBS@ @LIBPLIST_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 \
|
forked_daapd_SOURCES = main.c \
|
||||||
db.c db.h \
|
db.c db.h \
|
||||||
|
@ -95,6 +99,7 @@ forked_daapd_SOURCES = main.c \
|
||||||
evrtsp/rtsp.c evrtp/evrtsp.h \
|
evrtsp/rtsp.c evrtp/evrtsp.h \
|
||||||
evrtsp/rtsp-internal.h evrtsp/log.h \
|
evrtsp/rtsp-internal.h evrtsp/log.h \
|
||||||
scan-wma.c \
|
scan-wma.c \
|
||||||
|
$(SPOTIFYSRC) \
|
||||||
$(FLACSRC) $(MUSEPACKSRC)
|
$(FLACSRC) $(MUSEPACKSRC)
|
||||||
|
|
||||||
nodist_forked_daapd_SOURCES = \
|
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)
|
if (strncmp(filename, "http://", strlen("http://")) == 0)
|
||||||
return -1;
|
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);
|
DPRINTF(E_SPAM, L_ART, "Trying embedded artwork in %s\n", filename);
|
||||||
|
|
||||||
src_ctx = NULL;
|
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)
|
if (strncmp(path, "http://", strlen("http://")) == 0)
|
||||||
return -1;
|
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);
|
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
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)
|
if (strncmp(path, "http://", strlen("http://")) == 0)
|
||||||
return -1;
|
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);
|
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
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)
|
if (strncmp(path, "http://", strlen("http://")) == 0)
|
||||||
return -1;
|
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);
|
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
if ((ret < 0) || (ret >= sizeof(artwork)))
|
||||||
{
|
{
|
||||||
|
|
|
@ -100,6 +100,14 @@ static cfg_opt_t sec_airplay[] =
|
||||||
CFG_END()
|
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 */
|
/* Config file structure */
|
||||||
static cfg_opt_t toplvl_cfg[] =
|
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("library", sec_library, CFGF_NONE),
|
||||||
CFG_SEC("audio", sec_audio, CFGF_NONE),
|
CFG_SEC("audio", sec_audio, CFGF_NONE),
|
||||||
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
|
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
|
||||||
|
CFG_SEC("spotify", sec_spotify, CFGF_NONE),
|
||||||
CFG_END()
|
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.title_sort ASC",
|
||||||
"ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC",
|
"ORDER BY f.album_sort ASC, f.disc ASC, f.track ASC",
|
||||||
"ORDER BY f.album_artist_sort ASC",
|
"ORDER BY f.album_artist_sort ASC",
|
||||||
|
"ORDER BY f.special_id DESC, f.title ASC",
|
||||||
};
|
};
|
||||||
|
|
||||||
static char *db_path;
|
static char *db_path;
|
||||||
|
@ -876,6 +877,7 @@ db_build_query_pls(struct query_params *qp, char **q)
|
||||||
{
|
{
|
||||||
char *query;
|
char *query;
|
||||||
char *idx;
|
char *idx;
|
||||||
|
const char *sort;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
qp->results = db_get_count("SELECT COUNT(*) FROM playlists p WHERE p.disabled = 0;");
|
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)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
sort = sort_clause[qp->sort];
|
||||||
|
|
||||||
if (idx && qp->filter)
|
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)
|
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)
|
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
|
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)
|
if (!query)
|
||||||
{
|
{
|
||||||
|
@ -1281,10 +1285,10 @@ db_build_query_browse(struct query_params *qp, char *field, char *sort_field, ch
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (qp->filter)
|
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);
|
field, field, qp->filter);
|
||||||
else
|
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);
|
field, field);
|
||||||
|
|
||||||
if (!count)
|
if (!count)
|
||||||
|
@ -1323,16 +1327,16 @@ db_build_query_browse(struct query_params *qp, char *field, char *sort_field, ch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx && qp->filter)
|
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);
|
" AND %s %s %s;", field, sort_field, field, qp->filter, sort, idx);
|
||||||
else if (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);
|
" %s %s;", field, sort_field, field, sort, idx);
|
||||||
else if (qp->filter)
|
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);
|
" AND %s %s;", field, sort_field, field, qp->filter, sort);
|
||||||
else
|
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);
|
field, sort_field, field, sort);
|
||||||
|
|
||||||
free(sort);
|
free(sort);
|
||||||
|
@ -3045,6 +3049,41 @@ db_pl_add_item_byid(int plid, int fileid)
|
||||||
#undef Q_TMPL
|
#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
|
void
|
||||||
db_pl_clear_items(int id)
|
db_pl_clear_items(int id)
|
||||||
{
|
{
|
||||||
|
@ -3427,6 +3466,79 @@ db_pairing_fetch_byguid(struct pairing_info *pi)
|
||||||
#undef Q_TMPL
|
#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 */
|
/* Speakers */
|
||||||
int
|
int
|
||||||
|
|
14
src/db.h
14
src/db.h
|
@ -21,6 +21,7 @@ enum sort_type {
|
||||||
S_NAME,
|
S_NAME,
|
||||||
S_ALBUM,
|
S_ALBUM,
|
||||||
S_ARTIST,
|
S_ARTIST,
|
||||||
|
S_PLAYLIST,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define Q_F_BROWSE (1 << 15)
|
#define Q_F_BROWSE (1 << 15)
|
||||||
|
@ -45,6 +46,7 @@ enum query_type {
|
||||||
#define ARTWORK_OWN 3
|
#define ARTWORK_OWN 3
|
||||||
#define ARTWORK_DIR 4
|
#define ARTWORK_DIR 4
|
||||||
#define ARTWORK_PARENTDIR 5
|
#define ARTWORK_PARENTDIR 5
|
||||||
|
#define ARTWORK_SPOTIFY 6
|
||||||
|
|
||||||
struct query_params {
|
struct query_params {
|
||||||
/* Query parameters, filled in by caller */
|
/* Query parameters, filled in by caller */
|
||||||
|
@ -430,6 +432,9 @@ db_pl_add_item_byid(int plid, int fileid);
|
||||||
void
|
void
|
||||||
db_pl_clear_items(int id);
|
db_pl_clear_items(int id);
|
||||||
|
|
||||||
|
int
|
||||||
|
db_pl_update(char *title, char *path, int id);
|
||||||
|
|
||||||
void
|
void
|
||||||
db_pl_delete(int id);
|
db_pl_delete(int id);
|
||||||
|
|
||||||
|
@ -459,6 +464,15 @@ db_pairing_add(struct pairing_info *pi);
|
||||||
int
|
int
|
||||||
db_pairing_fetch_byguid(struct pairing_info *pi);
|
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 */
|
/* Speakers */
|
||||||
int
|
int
|
||||||
db_speaker_save(uint64_t id, int selected, int volume);
|
db_speaker_save(uint64_t id, int selected, int volume);
|
||||||
|
|
|
@ -60,6 +60,10 @@
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "remote_pairing.h"
|
#include "remote_pairing.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
# include "spotify.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#define F_SCAN_BULK (1 << 0)
|
#define F_SCAN_BULK (1 << 0)
|
||||||
#define F_SCAN_RESCAN (1 << 1)
|
#define F_SCAN_RESCAN (1 << 1)
|
||||||
|
@ -368,25 +372,23 @@ fixup_tags(struct media_file_info *mfi)
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
struct media_file_info mfi;
|
struct media_file_info *mfi;
|
||||||
char *filename;
|
char *filename;
|
||||||
char *ext;
|
char *ext;
|
||||||
time_t stamp;
|
time_t stamp;
|
||||||
int id;
|
int id;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
filename = strrchr(file, '/');
|
filename = strrchr(path, '/');
|
||||||
if (!filename)
|
if ((!filename) || (strlen(filename) == 1))
|
||||||
{
|
filename = path;
|
||||||
DPRINTF(E_LOG, L_SCAN, "Could not determine filename for %s\n", file);
|
else
|
||||||
|
filename++;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File types which should never be processed */
|
/* File types which should never be processed */
|
||||||
ext = strrchr(file, '.');
|
ext = strrchr(path, '.');
|
||||||
if (ext)
|
if (ext)
|
||||||
{
|
{
|
||||||
if ((strcasecmp(ext, ".pls") == 0) || (strcasecmp(ext, ".url") == 0))
|
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 */
|
/* Artwork files - don't scan */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if ((strlen(filename) > 1) && ((filename[1] == '_') || (filename[1] == '.')))
|
else if ((filename[0] == '_') || (filename[0] == '.'))
|
||||||
{
|
{
|
||||||
/* Hidden files - don't scan */
|
/* Hidden files - don't scan */
|
||||||
return;
|
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)
|
if (stamp >= mtime)
|
||||||
{
|
{
|
||||||
|
@ -420,78 +422,88 @@ process_media_file(char *file, time_t mtime, off_t size, int type, struct extinf
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&mfi, 0, sizeof(struct media_file_info));
|
if (!external_mfi)
|
||||||
|
|
||||||
if (stamp)
|
|
||||||
mfi.id = db_file_id_bypath(file);
|
|
||||||
|
|
||||||
mfi.fname = strdup(filename + 1);
|
|
||||||
if (!mfi.fname)
|
|
||||||
{
|
{
|
||||||
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;
|
memset(mfi, 0, sizeof(struct media_file_info));
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
else
|
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 */
|
DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
|
||||||
if (extinf && extinf->found)
|
goto out;
|
||||||
{
|
|
||||||
mfi.artist = strdup(extinf->artist);
|
|
||||||
mfi.title = strdup(extinf->artist);
|
|
||||||
mfi.album = strdup(extinf->title);
|
|
||||||
}
|
|
||||||
ret = scan_metadata_icy(file, &mfi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type & F_SCAN_TYPE_COMPILATION)
|
if (type & F_SCAN_TYPE_COMPILATION)
|
||||||
mfi.compilation = 1;
|
mfi->compilation = 1;
|
||||||
if (type & F_SCAN_TYPE_PODCAST)
|
if (type & F_SCAN_TYPE_PODCAST)
|
||||||
mfi.media_kind = 4; /* podcast */
|
mfi->media_kind = 4; /* podcast */
|
||||||
if (type & F_SCAN_TYPE_AUDIOBOOK)
|
if (type & F_SCAN_TYPE_AUDIOBOOK)
|
||||||
mfi.media_kind = 8; /* audiobook */
|
mfi->media_kind = 8; /* audiobook */
|
||||||
|
|
||||||
if (!mfi.item_kind)
|
if (!mfi->item_kind)
|
||||||
mfi.item_kind = 2; /* music */
|
mfi->item_kind = 2; /* music */
|
||||||
if (!mfi.media_kind)
|
if (!mfi->media_kind)
|
||||||
mfi.media_kind = 1; /* music */
|
mfi->media_kind = 1; /* music */
|
||||||
|
|
||||||
unicode_fixup_mfi(&mfi);
|
unicode_fixup_mfi(mfi);
|
||||||
|
|
||||||
fixup_tags(&mfi);
|
fixup_tags(mfi);
|
||||||
|
|
||||||
if (mfi.id == 0)
|
if (mfi->id == 0)
|
||||||
db_file_add(&mfi);
|
db_file_add(mfi);
|
||||||
else
|
else
|
||||||
db_file_update(&mfi);
|
db_file_update(mfi);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
free_mfi(&mfi, 1);
|
if (!external_mfi)
|
||||||
|
free_mfi(mfi, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -594,6 +606,14 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
else if (strcmp(ext, ".spotify") == 0)
|
||||||
|
{
|
||||||
|
spotify_login(file);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else if (strcmp(ext, ".force-rescan") == 0)
|
else if (strcmp(ext, ".force-rescan") == 0)
|
||||||
{
|
{
|
||||||
if (flags & F_SCAN_BULK)
|
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 */
|
/* 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 */
|
/* 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
|
* We want to scan the new file and we want to rescan the
|
||||||
* playlist to update playlist items (relative items).
|
* playlist to update playlist items (relative items).
|
||||||
*/
|
*/
|
||||||
ie->mask |= IN_CREATE;
|
ie->mask |= IN_CLOSE_WRITE;
|
||||||
db_pl_enable_bycookie(ie->cookie, wi->path);
|
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);
|
ret = lstat(path, &sb);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -1475,6 +1495,12 @@ exit_cb(int fd, short event, void *arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
filescanner_status(void)
|
||||||
|
{
|
||||||
|
return scan_exit;
|
||||||
|
}
|
||||||
|
|
||||||
/* Thread: main */
|
/* Thread: main */
|
||||||
int
|
int
|
||||||
filescanner_init(void)
|
filescanner_init(void)
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
|
|
||||||
#include "db.h"
|
#include "db.h"
|
||||||
|
|
||||||
#define F_SCAN_TYPE_PODCAST (1 << 0)
|
#define F_SCAN_TYPE_FILE (1 << 0)
|
||||||
#define F_SCAN_TYPE_AUDIOBOOK (1 << 1)
|
#define F_SCAN_TYPE_PODCAST (F_SCAN_TYPE_FILE | (1 << 1))
|
||||||
#define F_SCAN_TYPE_COMPILATION (1 << 2)
|
#define F_SCAN_TYPE_AUDIOBOOK (F_SCAN_TYPE_FILE | (1 << 2))
|
||||||
#define F_SCAN_TYPE_URL (1 << 3)
|
#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
|
int
|
||||||
filescanner_init(void);
|
filescanner_init(void);
|
||||||
|
@ -15,15 +17,11 @@ filescanner_init(void);
|
||||||
void
|
void
|
||||||
filescanner_deinit(void);
|
filescanner_deinit(void);
|
||||||
|
|
||||||
struct extinf_ctx
|
int
|
||||||
{
|
filescanner_status(void);
|
||||||
char *artist;
|
|
||||||
char *title;
|
|
||||||
int found;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 */
|
/* Actual scanners */
|
||||||
int
|
int
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
/* Get metadata from the EXTINF tag */
|
/* Get metadata from the EXTINF tag */
|
||||||
static int
|
static int
|
||||||
extinf_get(char *string, struct extinf_ctx *extinf)
|
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
|
||||||
{
|
{
|
||||||
char *ptr;
|
char *ptr;
|
||||||
|
|
||||||
|
@ -54,20 +54,20 @@ extinf_get(char *string, struct extinf_ctx *extinf)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* New extinf found, so clear old data */
|
/* New extinf found, so clear old data */
|
||||||
if (extinf->found)
|
if (*extinf)
|
||||||
{
|
{
|
||||||
free(extinf->artist);
|
free(mfi->artist);
|
||||||
free(extinf->title);
|
free(mfi->title);
|
||||||
}
|
}
|
||||||
|
|
||||||
extinf->found = 1;
|
*extinf = 1;
|
||||||
extinf->artist = strdup(ptr + 1);
|
mfi->artist = strdup(ptr + 1);
|
||||||
|
|
||||||
ptr = strstr(extinf->artist, " -");
|
ptr = strstr(mfi->artist, " -");
|
||||||
if (ptr && strlen(ptr) > 3)
|
if (ptr && strlen(ptr) > 3)
|
||||||
extinf->title = strdup(ptr + 3);
|
mfi->title = strdup(ptr + 3);
|
||||||
else
|
else
|
||||||
extinf->title = strdup("");
|
mfi->title = strdup("");
|
||||||
if (ptr)
|
if (ptr)
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
|
|
||||||
|
@ -75,28 +75,29 @@ extinf_get(char *string, struct extinf_ctx *extinf)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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(mfi->artist);
|
||||||
free(extinf->title);
|
free(mfi->title);
|
||||||
}
|
}
|
||||||
extinf->found = 0;
|
*extinf = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
scan_m3u_playlist(char *file, time_t mtime)
|
scan_m3u_playlist(char *file, time_t mtime)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
struct media_file_info mfi;
|
||||||
struct playlist_info *pli;
|
struct playlist_info *pli;
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
struct extinf_ctx extinf;
|
|
||||||
char buf[PATH_MAX];
|
char buf[PATH_MAX];
|
||||||
char *entry;
|
char *entry;
|
||||||
char *filename;
|
char *filename;
|
||||||
char *ptr;
|
char *ptr;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
int extinf;
|
||||||
int pl_id;
|
int pl_id;
|
||||||
int mfi_id;
|
int mfi_id;
|
||||||
int ret;
|
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);
|
DPRINTF(E_INFO, L_SCAN, "Processing static playlist: %s\n", file);
|
||||||
|
|
||||||
|
memset(&mfi, 0, sizeof(struct media_file_info));
|
||||||
|
|
||||||
ret = stat(file, &sb);
|
ret = stat(file, &sb);
|
||||||
if (ret < 0)
|
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);
|
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)
|
while (fgets(buf, sizeof(buf), fp) != NULL)
|
||||||
{
|
{
|
||||||
|
@ -189,7 +192,7 @@ scan_m3u_playlist(char *file, time_t mtime)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Saves metadata in extinf if EXTINF metadata line */
|
/* Saves metadata in extinf if EXTINF metadata line */
|
||||||
if (extinf_get(buf, &extinf))
|
if (extinf_get(buf, &mfi, &extinf))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Check that first char is sane for a path */
|
/* Check that first char is sane for a path */
|
||||||
|
@ -209,10 +212,10 @@ scan_m3u_playlist(char *file, time_t mtime)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extinf.found)
|
if (extinf)
|
||||||
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", extinf.artist, extinf.title);
|
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 */
|
/* Regular file */
|
||||||
else
|
else
|
||||||
|
@ -271,7 +274,7 @@ scan_m3u_playlist(char *file, time_t mtime)
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename);
|
DPRINTF(E_WARN, L_SCAN, "Could not add %s to playlist\n", filename);
|
||||||
|
|
||||||
extinf_reset(&extinf);
|
extinf_reset(&mfi, &extinf);
|
||||||
free(filename);
|
free(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -395,8 +395,8 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
||||||
|
|
||||||
stream_cb = stream_chunk_xcode_cb;
|
stream_cb = stream_chunk_xcode_cb;
|
||||||
|
|
||||||
st->xcode = transcode_setup(mfi, &st->size, 1);
|
ret = transcode_setup(&st->xcode, mfi, &st->size, 1);
|
||||||
if (!st->xcode)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
|
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ struct uri_map {
|
||||||
|
|
||||||
struct daap_session {
|
struct daap_session {
|
||||||
int id;
|
int id;
|
||||||
|
char *user_agent;
|
||||||
struct event timeout;
|
struct event timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +132,12 @@ daap_session_free(void *item)
|
||||||
|
|
||||||
s = (struct daap_session *)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);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +160,7 @@ daap_session_timeout_cb(int fd, short what, void *arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct daap_session *
|
static struct daap_session *
|
||||||
daap_session_register(void)
|
daap_session_register(const char *user_agent)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
|
@ -175,6 +180,9 @@ daap_session_register(void)
|
||||||
|
|
||||||
next_session_id++;
|
next_session_id++;
|
||||||
|
|
||||||
|
if (user_agent)
|
||||||
|
s->user_agent = strdup(user_agent);
|
||||||
|
|
||||||
if (DAAP_SESSION_TIMEOUT > 0) {
|
if (DAAP_SESSION_TIMEOUT > 0) {
|
||||||
evtimer_set(&s->timeout, daap_session_timeout_cb, s);
|
evtimer_set(&s->timeout, daap_session_timeout_cb, s);
|
||||||
event_base_set(evbase_httpd, &s->timeout);
|
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));
|
DPRINTF(E_LOG, L_DAAP, "Could not register DAAP session: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
if (user_agent)
|
||||||
|
free(s->user_agent);
|
||||||
|
|
||||||
free(s);
|
free(s);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -466,6 +477,47 @@ daap_sort_finalize(struct sort_ctx *ctx)
|
||||||
dmap_add_int(ctx->headerlist, "mshn", ctx->misc_mshn); /* 12 */
|
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
|
static void
|
||||||
get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params *qp)
|
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);
|
free_pi(&pi, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
s = daap_session_register();
|
s = daap_session_register(ua);
|
||||||
if (!s)
|
if (!s)
|
||||||
{
|
{
|
||||||
dmap_send_error(req, "mlog", "Could not start session");
|
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
|
static void
|
||||||
daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, int playlist, struct evkeyvalq *query)
|
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 query_params qp;
|
||||||
struct db_media_file_info dbmfi;
|
struct db_media_file_info dbmfi;
|
||||||
struct evbuffer *song;
|
struct evbuffer *song;
|
||||||
|
@ -1001,6 +1054,10 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
||||||
int transcode;
|
int transcode;
|
||||||
int ret;
|
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);
|
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
|
||||||
|
|
||||||
if (playlist != -1)
|
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));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
|
if (playlist == -1)
|
||||||
|
user_agent_filter(s->user_agent, &qp);
|
||||||
|
|
||||||
sctx = NULL;
|
sctx = NULL;
|
||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
|
@ -1242,26 +1301,15 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
||||||
static void
|
static void
|
||||||
daap_reply_dbsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
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);
|
daap_reply_songlist_generic(req, evbuf, -1, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
||||||
{
|
{
|
||||||
struct daap_session *s;
|
|
||||||
int playlist;
|
int playlist;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
|
||||||
if (!s)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ret = safe_atoi32(uri[3], &playlist);
|
ret = safe_atoi32(uri[3], &playlist);
|
||||||
if (ret < 0)
|
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));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
get_query_params(query, NULL, &qp);
|
get_query_params(query, NULL, &qp);
|
||||||
qp.type = Q_PL;
|
qp.type = Q_PL;
|
||||||
|
qp.sort = S_PLAYLIST;
|
||||||
|
|
||||||
ret = db_query_start(&qp);
|
ret = db_query_start(&qp);
|
||||||
if (ret < 0)
|
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));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
|
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
|
user_agent_filter(s->user_agent, &qp);
|
||||||
|
|
||||||
param = evhttp_find_header(query, "group-type");
|
param = evhttp_find_header(query, "group-type");
|
||||||
if (strcmp(param, "artists") == 0)
|
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));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
|
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
|
user_agent_filter(s->user_agent, &qp);
|
||||||
|
|
||||||
if (strcmp(uri[3], "artists") == 0)
|
if (strcmp(uri[3], "artists") == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -43,7 +43,7 @@ static int threshold;
|
||||||
static int console;
|
static int console;
|
||||||
static char *logfilename;
|
static char *logfilename;
|
||||||
static FILE *logfile;
|
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
|
static int
|
||||||
|
|
|
@ -26,8 +26,9 @@
|
||||||
#define L_LAUDIO 17
|
#define L_LAUDIO 17
|
||||||
#define L_DMAP 18
|
#define L_DMAP 18
|
||||||
#define L_DBPERF 19
|
#define L_DBPERF 19
|
||||||
|
#define L_SPOTIFY 20
|
||||||
|
|
||||||
#define N_LOGDOMAINS 20
|
#define N_LOGDOMAINS 21
|
||||||
|
|
||||||
/* Severities */
|
/* Severities */
|
||||||
#define E_FATAL 0
|
#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"
|
# include "ffmpeg_url_evbuffer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
# include "spotify.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define PIDFILE STATEDIR "/run/" PACKAGE ".pid"
|
#define PIDFILE STATEDIR "/run/" PACKAGE ".pid"
|
||||||
|
|
||||||
|
@ -678,6 +681,15 @@ main(int argc, char **argv)
|
||||||
goto filescanner_fail;
|
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 */
|
/* Spawn player thread */
|
||||||
ret = player_init();
|
ret = player_init();
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
|
@ -786,6 +798,10 @@ main(int argc, char **argv)
|
||||||
player_deinit();
|
player_deinit();
|
||||||
|
|
||||||
player_fail:
|
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");
|
DPRINTF(E_LOG, L_MAIN, "File scanner deinit\n");
|
||||||
filescanner_deinit();
|
filescanner_deinit();
|
||||||
|
|
||||||
|
|
125
src/player.c
125
src/player.c
|
@ -59,6 +59,9 @@
|
||||||
#include "raop.h"
|
#include "raop.h"
|
||||||
#include "laudio.h"
|
#include "laudio.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY_H
|
||||||
|
# include "spotify.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef MIN
|
#ifndef MIN
|
||||||
# define MIN(a, b) ((a < b) ? a : b)
|
# define MIN(a, b) ((a < b) ? a : b)
|
||||||
|
@ -882,8 +885,19 @@ player_queue_make_pl(int plid, uint32_t *id)
|
||||||
static void
|
static void
|
||||||
source_free(struct player_source *ps)
|
source_free(struct player_source *ps)
|
||||||
{
|
{
|
||||||
if (ps->ctx)
|
switch (ps->type)
|
||||||
transcode_cleanup(ps->ctx);
|
{
|
||||||
|
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);
|
free(ps);
|
||||||
}
|
}
|
||||||
|
@ -895,11 +909,22 @@ source_stop(struct player_source *ps)
|
||||||
|
|
||||||
while (ps)
|
while (ps)
|
||||||
{
|
{
|
||||||
if (ps->ctx)
|
switch (ps->type)
|
||||||
{
|
{
|
||||||
transcode_cleanup(ps->ctx);
|
case SOURCE_FFMPEG:
|
||||||
ps->ctx = NULL;
|
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;
|
tmp = ps;
|
||||||
ps = ps->play_next;
|
ps = ps->play_next;
|
||||||
|
@ -989,7 +1014,9 @@ static int
|
||||||
source_open(struct player_source *ps, int no_md)
|
source_open(struct player_source *ps, int no_md)
|
||||||
{
|
{
|
||||||
struct media_file_info *mfi;
|
struct media_file_info *mfi;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ps->setup_done = 0;
|
||||||
ps->stream_start = 0;
|
ps->stream_start = 0;
|
||||||
ps->output_start = 0;
|
ps->output_start = 0;
|
||||||
ps->end = 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);
|
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);
|
free_mfi(mfi, 0);
|
||||||
|
|
||||||
if (!ps->ctx)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_PLAYER, "Could not open file id %d\n", ps->id);
|
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)
|
if (!no_md)
|
||||||
metadata_send(ps, (player_state == PLAY_PLAYING) ? 0 : 1);
|
metadata_send(ps, (player_state == PLAY_PLAYING) ? 0 : 1);
|
||||||
|
|
||||||
|
ps->setup_done = 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,7 +1109,7 @@ source_next(int force)
|
||||||
if (!cur_streaming)
|
if (!cur_streaming)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (cur_streaming->ctx)
|
if ((cur_streaming->type == SOURCE_FFMPEG) && cur_streaming->ctx)
|
||||||
{
|
{
|
||||||
ret = transcode_seek(cur_streaming->ctx, 0);
|
ret = transcode_seek(cur_streaming->ctx, 0);
|
||||||
|
|
||||||
|
@ -1276,10 +1318,13 @@ source_check(void)
|
||||||
{
|
{
|
||||||
cur_playing = cur_playing->play_next;
|
cur_playing = cur_playing->play_next;
|
||||||
|
|
||||||
if (ps->ctx)
|
if (ps->setup_done)
|
||||||
{
|
{
|
||||||
transcode_cleanup(ps->ctx);
|
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
|
||||||
ps->ctx = NULL;
|
{
|
||||||
|
transcode_cleanup(ps->ctx);
|
||||||
|
ps->ctx = NULL;
|
||||||
|
}
|
||||||
ps->play_next = NULL;
|
ps->play_next = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1324,10 +1369,13 @@ source_check(void)
|
||||||
cur_playing->stream_start = ps->end + 1;
|
cur_playing->stream_start = ps->end + 1;
|
||||||
cur_playing->output_start = cur_playing->stream_start;
|
cur_playing->output_start = cur_playing->stream_start;
|
||||||
|
|
||||||
if (ps->ctx)
|
if (ps->setup_done)
|
||||||
{
|
{
|
||||||
transcode_cleanup(ps->ctx);
|
if ((ps->type == SOURCE_FFMPEG) && ps->ctx)
|
||||||
ps->ctx = NULL;
|
{
|
||||||
|
transcode_cleanup(ps->ctx);
|
||||||
|
ps->ctx = NULL;
|
||||||
|
}
|
||||||
ps->play_next = 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)
|
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)
|
if (ret <= 0)
|
||||||
{
|
{
|
||||||
/* EOF or error */
|
/* EOF or error */
|
||||||
|
@ -2555,7 +2618,20 @@ playback_seek_bh(struct player_command *cmd)
|
||||||
ps->end = 0;
|
ps->end = 0;
|
||||||
|
|
||||||
/* Seek to commanded position */
|
/* 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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
playback_abort();
|
playback_abort();
|
||||||
|
@ -2596,7 +2672,20 @@ playback_pause_bh(struct player_command *cmd)
|
||||||
pos -= ps->stream_start;
|
pos -= ps->stream_start;
|
||||||
ms = (int)((pos * 1000) / 44100);
|
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)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
playback_abort();
|
playback_abort();
|
||||||
|
|
|
@ -33,6 +33,11 @@ enum repeat_mode {
|
||||||
REPEAT_ALL = 2,
|
REPEAT_ALL = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum source_type {
|
||||||
|
SOURCE_FFMPEG = 0,
|
||||||
|
SOURCE_SPOTIFY = 1,
|
||||||
|
};
|
||||||
|
|
||||||
struct spk_flags {
|
struct spk_flags {
|
||||||
unsigned selected:1;
|
unsigned selected:1;
|
||||||
unsigned has_password:1;
|
unsigned has_password:1;
|
||||||
|
@ -60,6 +65,9 @@ struct player_source
|
||||||
{
|
{
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
|
|
||||||
|
enum source_type type;
|
||||||
|
int setup_done;
|
||||||
|
|
||||||
uint64_t stream_start;
|
uint64_t stream_start;
|
||||||
uint64_t output_start;
|
uint64_t output_start;
|
||||||
uint64_t end;
|
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 "db.h"
|
||||||
#include "transcode.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)
|
#if LIBAVCODEC_VERSION_MAJOR >= 56 || (LIBAVCODEC_VERSION_MAJOR == 55 && LIBAVCODEC_VERSION_MINOR >= 18)
|
||||||
# define XCODE_BUFFER_SIZE ((192000 * 3) / 2)
|
# define XCODE_BUFFER_SIZE ((192000 * 3) / 2)
|
||||||
|
@ -462,9 +465,8 @@ transcode_seek(struct transcode_ctx *ctx, int ms)
|
||||||
return got_ms;
|
return got_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
struct transcode_ctx *
|
transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t *est_size, int wavhdr)
|
||||||
transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
|
|
||||||
{
|
{
|
||||||
struct transcode_ctx *ctx;
|
struct transcode_ctx *ctx;
|
||||||
int ret;
|
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");
|
DPRINTF(E_WARN, L_XCODE, "Could not allocate transcode context\n");
|
||||||
|
|
||||||
return NULL;
|
return -1;
|
||||||
}
|
}
|
||||||
memset(ctx, 0, sizeof(struct transcode_ctx));
|
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)));
|
DPRINTF(E_WARN, L_XCODE, "Could not open file %s: %s\n", mfi->fname, strerror(AVUNERROR(ret)));
|
||||||
|
|
||||||
free(ctx);
|
free(ctx);
|
||||||
return NULL;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 3)
|
#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)
|
if (wavhdr)
|
||||||
make_wav_header(ctx, est_size);
|
make_wav_header(ctx, est_size);
|
||||||
|
|
||||||
return ctx;
|
*nctx = ctx;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
setup_fail_codec:
|
setup_fail_codec:
|
||||||
avcodec_close(ctx->acodec);
|
avcodec_close(ctx->acodec);
|
||||||
|
@ -675,7 +679,7 @@ transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr)
|
||||||
#endif
|
#endif
|
||||||
free(ctx);
|
free(ctx);
|
||||||
|
|
||||||
return NULL;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -684,11 +688,14 @@ transcode_cleanup(struct transcode_ctx *ctx)
|
||||||
if (ctx->apacket.data)
|
if (ctx->apacket.data)
|
||||||
av_free_packet(&ctx->apacket);
|
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)
|
#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
|
#else
|
||||||
av_close_input_file(ctx->fmtctx);
|
if (ctx->fmtctx)
|
||||||
|
av_close_input_file(ctx->fmtctx);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
av_free(ctx->abuffer);
|
av_free(ctx->abuffer);
|
||||||
|
@ -717,6 +724,12 @@ transcode_needed(struct evkeyvalq *headers, char *file_codectype)
|
||||||
int size;
|
int size;
|
||||||
int i;
|
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);
|
DPRINTF(E_DBG, L_XCODE, "Determining transcoding status for codectype %s\n", file_codectype);
|
||||||
|
|
||||||
lib = cfg_getsec(cfg, "library");
|
lib = cfg_getsec(cfg, "library");
|
||||||
|
|
|
@ -12,8 +12,8 @@ transcode(struct transcode_ctx *ctx, struct evbuffer *evbuf, int wanted);
|
||||||
int
|
int
|
||||||
transcode_seek(struct transcode_ctx *ctx, int ms);
|
transcode_seek(struct transcode_ctx *ctx, int ms);
|
||||||
|
|
||||||
struct transcode_ctx *
|
int
|
||||||
transcode_setup(struct media_file_info *mfi, off_t *est_size, int wavhdr);
|
transcode_setup(struct transcode_ctx **nctx, struct media_file_info *mfi, off_t *est_size, int wavhdr);
|
||||||
|
|
||||||
void
|
void
|
||||||
transcode_cleanup(struct transcode_ctx *ctx);
|
transcode_cleanup(struct transcode_ctx *ctx);
|
||||||
|
|
Loading…
Reference in New Issue