aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS11
-rw-r--r--configure.ac9
-rw-r--r--doc/mpdconf.example22
-rw-r--r--doc/protocol.xml18
-rw-r--r--src/decoder/sidplay_plugin.cxx257
-rwxr-xr-xsrc/decoder/vorbis_plugin.c2
-rw-r--r--src/output_init.c1
-rw-r--r--src/update.c3
8 files changed, 308 insertions, 15 deletions
diff --git a/NEWS b/NEWS
index ae53f52c5..e311d94f9 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,9 @@ ver 0.16 (20??/??/??)
- sndfile: new decoder plugin based on libsndfile
- flac: load external cue sheet when no internal one
- mpg123: new decoder plugin based on libmpg123
+ - sidplay: support sub-tunes
+ - sidplay: implemented songlength database
+ - sidplay: support seeking
* encoders:
- twolame: new encoder plugin based on libtwolame
* output:
@@ -32,6 +35,14 @@ ver 0.16 (20??/??/??)
* obey $(sysconfdir) for default mpd.conf location
+ver 0.15.3 (2009/08/29)
+* decoders:
+ - vorbis: faster tag scanning with ov_test_callback()
+* output:
+ - fix stuttering due to uninitialized variable
+* update: don't re-read unchanged container files
+
+
ver 0.15.2 (2009/08/15)
* tags:
- ape: check the tag size (fixes integer underflow)
diff --git a/configure.ac b/configure.ac
index 681feae6c..e730c6977 100644
--- a/configure.ac
+++ b/configure.ac
@@ -554,12 +554,17 @@ if test x$enable_sidplay != xno; then
# library name triggers an autoconf bug
AC_CHECK_LIB(resid-builder, main,
[found_sidplay=yes], [found_sidplay=no])
+
+ if test x$found_sidplay = xyes; then
+ AC_HAVE_LIBRARY(sidutils,, [found_sidplay=no])
+ fi
+
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
- [libresid-builder not found])
+ [libresid-builder or libsidutils not found])
fi
if test x$enable_sidplay = xyes; then
- AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder")
+ AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder -lsidutils")
AC_SUBST(SIDPLAY_CFLAGS,)
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 4e3b19a5a..8ce3a1b9f 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -363,3 +363,25 @@ input {
#id3v1_encoding "ISO-8859-1"
#
###############################################################################
+
+
+# SIDPlay decoder #############################################################
+#
+# songlength_database:
+# Location of your songlengths file, as distributed with the HVSC.
+# The sidplay plugin checks this for matching MD5 fingerprints.
+# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
+#
+# default_songlength:
+# This is the default playing time in seconds for songs not in the
+# songlength database, or in case you're not using a database.
+# A value of 0 means play indefinitely.
+#
+#decoder {
+# plugin "sidplay"
+# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
+# default_songlength "120"
+#}
+#
+###############################################################################
+
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 95728197b..30f8792c8 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -278,6 +278,24 @@
</listitem>
<listitem>
<para>
+ <varname>nextsong</varname>:
+ <footnoteref linkend="since_0_15"/>
+ <returnvalue> playlist song number of the next
+ song to be played
+ </returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>nextsongid</varname>:
+ <footnoteref linkend="since_0_15"/>
+ <returnvalue>playlist songid of the next song
+ to be played
+ </returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
<varname>time</varname>:
<returnvalue>total time elapsed (of current
playing/paused song)</returnvalue>
diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx
index 54ab746e2..5228dbe89 100644
--- a/src/decoder/sidplay_plugin.cxx
+++ b/src/decoder/sidplay_plugin.cxx
@@ -21,14 +21,165 @@ extern "C" {
#include "../decoder_api.h"
}
+#include <errno.h>
+#include <stdlib.h>
#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sidplay"
+#define SUBTUNE_PREFIX "tune_"
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool
+sidplay_init(const struct config_param *param)
+{
+ GError *err=NULL;
+ gchar *songlen_data;
+ gsize songlen_data_size;
+
+ /* read the songlengths database file */
+ songlength_file=config_get_block_string(param,
+ "songlength_database", NULL);
+ if(songlength_file) {
+ if(g_file_get_contents(songlength_file, &songlen_data, &songlen_data_size, NULL)) {
+ /* replace any ; comment characters with # */
+ for(int i=0; i<songlen_data_size; i++)
+ if(songlen_data[i]==';') songlen_data[i]='#';
+
+ songlength_database=g_key_file_new();
+ if(!g_key_file_load_from_data(songlength_database,
+ songlen_data, songlen_data_size,
+ G_KEY_FILE_NONE, &err)) {
+ g_warning("unable to parse songlengths file %s: %s",
+ songlength_file, err->message);
+ g_key_file_free(songlength_database);
+ songlength_database=NULL;
+ }
+ g_key_file_set_list_separator(songlength_database, ' ');
+ g_free(songlen_data);
+ } else {
+ g_warning("unable to read songlengths file %s: %s",
+ songlength_file, err->message);
+ }
+ }
+
+ default_songlength=config_get_block_unsigned(param,
+ "default_songlength", 0);
+
+ all_files_are_containers=config_get_block_bool(param,
+ "all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ return true;
+}
+
+void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ char *path_container=g_strdup(path_fs);
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, NULL, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static int
+get_song_length(const char *path_fs)
+{
+ if (songlength_database == NULL)
+ return -1;
+
+ gchar *sid_file=get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ g_free(sid_file);
+ if(!tune) {
+ g_warning("failed to load file for calculating md5 sum");
+ return -1;
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ int song_num=get_song_num(path_fs);
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, NULL);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return -1;
+ }
+
+ int minutes=strtol(values[song_num-1], NULL, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, NULL, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return (minutes*60)+seconds;
+}
+
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
@@ -36,13 +187,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* load the tune */
- SidTune tune(path_fs, NULL, true);
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune) {
g_warning("failed to load file");
return;
}
- tune.selectSong(1);
+ int song_num=get_song_num(path_fs);
+ tune.selectSong(song_num);
+
+ int song_len=get_song_length(path_fs);
+ if(song_len==-1) song_len=default_songlength;
/* initialize the player */
@@ -105,10 +262,12 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
struct audio_format audio_format;
audio_format_init(&audio_format, 48000, 16, 2);
- decoder_initialized(decoder, &audio_format, false, -1);
+ decoder_initialized(decoder, &audio_format, true, (float)song_len);
/* .. and play */
+ float data_time=0;
+ int timebase=player.timebase();
enum decoder_command cmd;
do {
char buffer[4096];
@@ -119,29 +278,105 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, nbytes,
- 0, 0, NULL);
- } while (cmd == DECODE_COMMAND_NONE);
+ data_time, 0, NULL);
+
+ data_time=player.time()/timebase;
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ int target_time=decoder_seek_where(decoder);
+
+ /* can't rewind so return to zero and seek forward */
+ if(target_time<data_time) {
+ player.stop();
+ data_time=0;
+ }
+
+ /* ignore data until target time is reached */
+ while(data_time<target_time) {
+ nbytes=player.play(buffer, sizeof(buffer));
+ if(nbytes==0)
+ break;
+ data_time=player.time()/timebase;
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if(song_len && data_time>=(float)song_len)
+ break;
+
+ } while (cmd != DECODE_COMMAND_STOP);
}
static struct tag *
sidplay_tag_dup(const char *path_fs)
{
- SidTune tune(path_fs, NULL, true);
+ int song_num=get_song_num(path_fs);
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune)
return NULL;
const SidTuneInfo &info = tune.getInfo();
struct tag *tag = tag_new();
+ /* title */
+ const char *title;
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
-
+ title=info.infoString[0];
+ else
+ title="";
+
+ if(info.songs>1) {
+ char *tag_title=g_strdup_printf("%s (%d/%d)",
+ title, song_num, info.songs);
+ tag_add_item(tag, TAG_ITEM_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_ITEM_TITLE, title);
+
+ /* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_ITEM_TRACK, track);
+ g_free(track);
+
+ /* time */
+ int song_len=get_song_length(path_fs);
+ if(song_len!=-1) tag->time=song_len;
+
return tag;
}
+static char *
+sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs, NULL, true);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return NULL;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ char *subtune= g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.sid", tnum);
+ return subtune;
+ } else
+ return NULL;
+}
+
static const char *const sidplay_suffixes[] = {
"sid",
NULL
@@ -150,12 +385,12 @@ static const char *const sidplay_suffixes[] = {
extern const struct decoder_plugin sidplay_decoder_plugin;
const struct decoder_plugin sidplay_decoder_plugin = {
"sidplay",
- NULL, /* init() */
- NULL, /* finish() */
+ sidplay_init,
+ sidplay_finish,
NULL, /* stream_decode() */
sidplay_file_decode,
sidplay_tag_dup,
- NULL, /* container_scan */
+ sidplay_container_scan,
sidplay_suffixes,
NULL, /* mime_types */
};
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index 39a075176..4548d1b57 100755
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -382,7 +382,7 @@ vorbis_tag_dup(const char *file)
return NULL;
}
- if (ov_open(fp, &vf, NULL, 0) < 0) {
+ if (ov_test_callbacks(fp, &vf, NULL, 0, OV_CALLBACKS_STREAMONLY) < 0) {
fclose(fp);
return NULL;
}
diff --git a/src/output_init.c b/src/output_init.c
index 08873ac20..66444e9a5 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -178,6 +178,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->plugin = plugin;
ao->enabled = config_get_block_bool(param, "enabled", true);
ao->open = false;
+ ao->pause = false;
ao->fail_timer = NULL;
/* set up the filter chain */
diff --git a/src/update.c b/src/update.c
index 6778a5989..cfe574e17 100644
--- a/src/update.c
+++ b/src/update.c
@@ -502,7 +502,8 @@ update_regular_file(struct directory *directory,
{
struct song* song = songvec_find(&directory->songs, name);
- if (plugin->container_scan != NULL)
+ if (!(song != NULL && st->st_mtime == song->mtime) &&
+ plugin->container_scan != NULL)
{
if (update_container_file(directory, name, st, plugin))
{