From 65ad29846084755eef3f4bcec761bed921563a24 Mon Sep 17 00:00:00 2001
From: Avuton Olrich <avuton@gmail.com>
Date: Sun, 30 May 2010 08:59:00 -0700
Subject: [PATCH 01/28] Modify version string to post-release version
 0.15.11~git

---
 NEWS         | 3 +++
 configure.ac | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index ca4e7ecc4..ef5cc0091 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+ver 0.15.11 (2010/??/??)
+
+
 ver 0.15.10 (2010/05/30)
 * input:
   - mms: fix memory leak in error handler
diff --git a/configure.ac b/configure.ac
index fbe1de92f..9249ada70 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
 AC_PREREQ(2.60)
-AC_INIT(mpd, 0.15.10, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.15.11~git, musicpd-dev-team@lists.sourceforge.net)
 AC_CONFIG_SRCDIR([src/main.c])
 AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
 AM_CONFIG_HEADER(config.h)

From c7e89ea3a38b8c022a754d779e6ae1100a2b19af Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 19 Jul 2009 17:59:35 +0200
Subject: [PATCH 02/28] tag_ape: converted apeItems and tagItems to global vars

Don't initialize those arrays each time tag_ape_load() is called.
---
 src/tag_ape.c | 40 ++++++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/tag_ape.c b/src/tag_ape.c
index 7cbf32208..2ffc9b80b 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -25,6 +25,26 @@
 #include <assert.h>
 #include <stdio.h>
 
+static const char *const apeItems[7] = {
+	"title",
+	"artist",
+	"album",
+	"comment",
+	"genre",
+	"track",
+	"year"
+};
+
+static const int tagItems[7] = {
+	TAG_ITEM_TITLE,
+	TAG_ITEM_ARTIST,
+	TAG_ITEM_ALBUM,
+	TAG_ITEM_COMMENT,
+	TAG_ITEM_GENRE,
+	TAG_ITEM_TRACK,
+	TAG_ITEM_DATE,
+};
+
 struct tag *
 tag_ape_load(const char *file)
 {
@@ -48,26 +68,6 @@ tag_ape_load(const char *file)
 		unsigned char reserved[8];
 	} footer;
 
-	const char *apeItems[7] = {
-		"title",
-		"artist",
-		"album",
-		"comment",
-		"genre",
-		"track",
-		"year"
-	};
-
-	int tagItems[7] = {
-		TAG_ITEM_TITLE,
-		TAG_ITEM_ARTIST,
-		TAG_ITEM_ALBUM,
-		TAG_ITEM_COMMENT,
-		TAG_ITEM_GENRE,
-		TAG_ITEM_TRACK,
-		TAG_ITEM_DATE,
-	};
-
 	fp = fopen(file, "r");
 	if (!fp)
 		return NULL;

From 7cca55549b247f14e40d4d5f21aceb88d0261b12 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 19 Jul 2009 17:59:36 +0200
Subject: [PATCH 03/28] tag_ape: moved code to tag_ape_import_item()

Improve code readability.
---
 src/tag_ape.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/src/tag_ape.c b/src/tag_ape.c
index 2ffc9b80b..5dc13d9c5 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -45,6 +45,26 @@ static const int tagItems[7] = {
 	TAG_ITEM_DATE,
 };
 
+static struct tag *
+tag_ape_import_item(struct tag *tag, unsigned long flags,
+		    const char *key, const char *value, size_t value_length)
+{
+	/* we only care about utf-8 text tags */
+	if ((flags & (0x3 << 1)) != 0)
+		return tag;
+
+	for (unsigned i = 0; i < 7; i++) {
+		if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
+			if (tag == NULL)
+				tag = tag_new();
+			tag_add_item_n(tag, tagItems[i],
+				       value, value_length);
+		}
+	}
+
+	return tag;
+}
+
 struct tag *
 tag_ape_load(const char *file)
 {
@@ -56,7 +76,6 @@ tag_ape_load(const char *file)
 	size_t tagLen;
 	size_t size;
 	unsigned long flags;
-	int i;
 	char *key;
 
 	struct {
@@ -127,17 +146,8 @@ tag_ape_load(const char *file)
 		if (tagLen < size)
 			goto fail;
 
-		/* we only care about utf-8 text tags */
-		if (!(flags & (0x3 << 1))) {
-			for (i = 0; i < 7; i++) {
-				if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
-					if (!ret)
-						ret = tag_new();
-					tag_add_item_n(ret, tagItems[i],
-						       p, size);
-				}
-			}
-		}
+		ret = tag_ape_import_item(ret, flags, key, p, size);
+
 		p += size;
 		tagLen -= size;
 	}

From 026bd158724886026b16245093e749cbe47d24bd Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 19 Jul 2009 18:04:42 +0200
Subject: [PATCH 04/28] tag_ape: simplified the apeItems array

Make "enum tag_type" the array index, and convert apeItems to a sparse
array.
---
 src/tag_ape.c | 34 ++++++++++++----------------------
 1 file changed, 12 insertions(+), 22 deletions(-)

diff --git a/src/tag_ape.c b/src/tag_ape.c
index 5dc13d9c5..e3b848bfc 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -25,24 +25,14 @@
 #include <assert.h>
 #include <stdio.h>
 
-static const char *const apeItems[7] = {
-	"title",
-	"artist",
-	"album",
-	"comment",
-	"genre",
-	"track",
-	"year"
-};
-
-static const int tagItems[7] = {
-	TAG_ITEM_TITLE,
-	TAG_ITEM_ARTIST,
-	TAG_ITEM_ALBUM,
-	TAG_ITEM_COMMENT,
-	TAG_ITEM_GENRE,
-	TAG_ITEM_TRACK,
-	TAG_ITEM_DATE,
+static const char *const ape_tag_names[] = {
+	[TAG_ITEM_TITLE] = "title",
+	[TAG_ITEM_ARTIST] = "artist",
+	[TAG_ITEM_ALBUM] = "album",
+	[TAG_ITEM_COMMENT] = "comment",
+	[TAG_ITEM_GENRE] = "genre",
+	[TAG_ITEM_TRACK] = "track",
+	[TAG_ITEM_DATE] = "year"
 };
 
 static struct tag *
@@ -53,12 +43,12 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
 	if ((flags & (0x3 << 1)) != 0)
 		return tag;
 
-	for (unsigned i = 0; i < 7; i++) {
-		if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
+	for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) {
+		if (ape_tag_names[i] != NULL &&
+		    g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) {
 			if (tag == NULL)
 				tag = tag_new();
-			tag_add_item_n(tag, tagItems[i],
-				       value, value_length);
+			tag_add_item_n(tag, i, value, value_length);
 		}
 	}
 

From 9328558fc7245613a620ea77e26d19861a23f1ce Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 30 May 2010 22:29:48 +0200
Subject: [PATCH 05/28] tag_ape: support album artist

I took this tag name from a MusePack sample file I got from a user.
It is not documented in the APE specification:

 http://wiki.hydrogenaudio.org/index.php?title=APE_key

People seem to be using undocumented extensions to the specification
anyway, and the best we can do is attempt to support them.
---
 NEWS          | 2 ++
 src/tag_ape.c | 1 +
 2 files changed, 3 insertions(+)

diff --git a/NEWS b/NEWS
index ef5cc0091..fe0ec6e6b 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,6 @@
 ver 0.15.11 (2010/??/??)
+* tags:
+  - ape: support album artist
 
 
 ver 0.15.10 (2010/05/30)
diff --git a/src/tag_ape.c b/src/tag_ape.c
index e3b848bfc..6d8e0c743 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -29,6 +29,7 @@ static const char *const ape_tag_names[] = {
 	[TAG_ITEM_TITLE] = "title",
 	[TAG_ITEM_ARTIST] = "artist",
 	[TAG_ITEM_ALBUM] = "album",
+	[TAG_ITEM_ALBUM_ARTIST] = "album artist",
 	[TAG_ITEM_COMMENT] = "comment",
 	[TAG_ITEM_GENRE] = "genre",
 	[TAG_ITEM_TRACK] = "track",

From 5092eaf1ccf7785c70d21a814f44a4b9437f63c0 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:18:27 +0200
Subject: [PATCH 06/28] tag_ape: move table lookup to tag_table.h

Allow code sharing.
---
 Makefile.am     |  1 +
 src/tag_ape.c   | 18 +++++++++---------
 src/tag_table.h | 43 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 53 insertions(+), 9 deletions(-)
 create mode 100644 src/tag_table.h

diff --git a/Makefile.am b/Makefile.am
index 6e7827785..083c72a5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -142,6 +142,7 @@ mpd_headers = \
 	src/tag.h \
 	src/tag_internal.h \
 	src/tag_pool.h \
+	src/tag_table.h \
 	src/tag_ape.h \
 	src/tag_id3.h \
 	src/tag_print.h \
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 6d8e0c743..0e8f67456 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -19,13 +19,14 @@
 
 #include "tag_ape.h"
 #include "tag.h"
+#include "tag_table.h"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <stdio.h>
 
-static const char *const ape_tag_names[] = {
+static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 	[TAG_ITEM_TITLE] = "title",
 	[TAG_ITEM_ARTIST] = "artist",
 	[TAG_ITEM_ALBUM] = "album",
@@ -44,14 +45,13 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
 	if ((flags & (0x3 << 1)) != 0)
 		return tag;
 
-	for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) {
-		if (ape_tag_names[i] != NULL &&
-		    g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) {
-			if (tag == NULL)
-				tag = tag_new();
-			tag_add_item_n(tag, i, value, value_length);
-		}
-	}
+	enum tag_type type = tag_table_lookup(ape_tag_names, key);
+	if (type == TAG_NUM_OF_ITEM_TYPES)
+		return tag;
+
+	if (tag == NULL)
+		tag = tag_new();
+	tag_add_item_n(tag, type, value, value_length);
 
 	return tag;
 }
diff --git a/src/tag_table.h b/src/tag_table.h
new file mode 100644
index 000000000..ce47d69fc
--- /dev/null
+++ b/src/tag_table.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_TABLE_H
+#define MPD_TAG_TABLE_H
+
+#include "tag.h"
+
+#include <glib.h>
+
+/**
+ * Looks up a string in a tag translation table (case insensitive).
+ * Returns TAG_NUM_OF_ITEM_TYPES if the specified name was not found
+ * in the table.
+ */
+static inline enum tag_type
+tag_table_lookup(const char *const* table, const char *name)
+{
+	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
+		if (table[i] != NULL &&
+		    g_ascii_strcasecmp(name, table[i]) == 0)
+			return (enum tag_type)i;
+
+	return TAG_NUM_OF_ITEM_TYPES;
+}
+
+#endif

From cfcd84655c5988716229d3f8e8e30949cb1708ac Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:19:30 +0200
Subject: [PATCH 07/28] decoder/mp4ff: use tag_table.h to parse tag names

Convert if/else/else/... to a loop.
---
 src/decoder/mp4ff_plugin.c | 33 +++++++++++++++------------------
 1 file changed, 15 insertions(+), 18 deletions(-)

diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index cf9382904..a330763ca 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -19,6 +19,7 @@
 
 #include "../decoder_api.h"
 #include "config.h"
+#include "tag_table.h"
 
 #include <glib.h>
 
@@ -339,6 +340,17 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
 	mp4ff_close(mp4fh);
 }
 
+static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
+	[TAG_ITEM_TITLE] = "title",
+	[TAG_ITEM_ARTIST] = "artist",
+	[TAG_ITEM_ALBUM] = "album",
+	[TAG_ITEM_TRACK] = "track",
+	[TAG_ITEM_DISC] = "disc",
+	[TAG_ITEM_GENRE] = "genre",
+	[TAG_ITEM_DATE] = "date",
+	[TAG_ITEM_COMPOSER] = "writer",
+};
+
 static struct tag *
 mp4_tag_dup(const char *file)
 {
@@ -394,24 +406,9 @@ mp4_tag_dup(const char *file)
 
 		mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
 
-		if (0 == g_ascii_strcasecmp("artist", item)) {
-			tag_add_item(ret, TAG_ITEM_ARTIST, value);
-		} else if (0 == g_ascii_strcasecmp("title", item)) {
-			tag_add_item(ret, TAG_ITEM_TITLE, value);
-		} else if (0 == g_ascii_strcasecmp("album", item)) {
-			tag_add_item(ret, TAG_ITEM_ALBUM, value);
-		} else if (0 == g_ascii_strcasecmp("track", item)) {
-			tag_add_item(ret, TAG_ITEM_TRACK, value);
-		} else if (0 == g_ascii_strcasecmp("disc", item)) {
-			/* Is that the correct id? */
-			tag_add_item(ret, TAG_ITEM_DISC, value);
-		} else if (0 == g_ascii_strcasecmp("genre", item)) {
-			tag_add_item(ret, TAG_ITEM_GENRE, value);
-		} else if (0 == g_ascii_strcasecmp("date", item)) {
-			tag_add_item(ret, TAG_ITEM_DATE, value);
-		} else if (0 == g_ascii_strcasecmp("writer", item)) {
-			tag_add_item(ret, TAG_ITEM_COMPOSER, value);
-		}
+		enum tag_type type = tag_table_lookup(mp4ff_tag_names, item);
+		if (type != TAG_NUM_OF_ITEM_TYPES)
+			tag_add_item(ret, type, value);
 
 		free(item);
 		free(value);

From 0aeec9059053bf724942831a45e07d43b99b5955 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:22:13 +0200
Subject: [PATCH 08/28] decoder/mp4ff: support tags "albumartist", "band"

I'm not sure if mapping "band" to TAG_PERFORMER is correct, but it
might be better than nothing.
---
 NEWS                       | 2 ++
 src/decoder/mp4ff_plugin.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/NEWS b/NEWS
index fe0ec6e6b..6f499856d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,8 @@
 ver 0.15.11 (2010/??/??)
 * tags:
   - ape: support album artist
+* decoders:
+  - mp4ff: support tags "albumartist", "band"
 
 
 ver 0.15.10 (2010/05/30)
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index a330763ca..5e1251d82 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -344,11 +344,13 @@ static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 	[TAG_ITEM_TITLE] = "title",
 	[TAG_ITEM_ARTIST] = "artist",
 	[TAG_ITEM_ALBUM] = "album",
+	[TAG_ITEM_ALBUM_ARTIST] = "albumartist",
 	[TAG_ITEM_TRACK] = "track",
 	[TAG_ITEM_DISC] = "disc",
 	[TAG_ITEM_GENRE] = "genre",
 	[TAG_ITEM_DATE] = "date",
 	[TAG_ITEM_COMPOSER] = "writer",
+	[TAG_ITEM_PERFORMER] = "band",
 };
 
 static struct tag *

From 4d6d372a5b8f7903bbcbec943587c593ac08df3c Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 19:24:41 +0000
Subject: [PATCH 09/28] decoder/vorbis: use single global ov_callbacks constant

Initialize the ov_callbacks struct at compile time.
---
 src/decoder/vorbis_plugin.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index 0ff898647..3da56bd5c 100644
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -97,6 +97,13 @@ static long ogg_tell_cb(void *vdata)
 	return (long)data->input_stream->offset;
 }
 
+static const ov_callbacks vorbis_is_callbacks = {
+	.read_func = ogg_read_cb,
+	.seek_func = ogg_seek_cb,
+	.close_func = ogg_close_cb,
+	.tell_func = ogg_tell_cb,
+};
+
 static const char *
 vorbis_comment_value(const char *comment, const char *needle)
 {
@@ -241,7 +248,6 @@ vorbis_stream_decode(struct decoder *decoder,
 		     struct input_stream *input_stream)
 {
 	OggVorbis_File vf;
-	ov_callbacks callbacks;
 	OggCallbackData data;
 	struct audio_format audio_format;
 	int current_section;
@@ -266,13 +272,9 @@ vorbis_stream_decode(struct decoder *decoder,
 	data.input_stream = input_stream;
 	data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
 
-	callbacks.read_func = ogg_read_cb;
-	callbacks.seek_func = ogg_seek_cb;
-	callbacks.close_func = ogg_close_cb;
-	callbacks.tell_func = ogg_tell_cb;
-	if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
+	if ((ret = ov_open_callbacks(&data, &vf, NULL, 0,
+				     vorbis_is_callbacks)) < 0) {
 		const char *error;
-
 		if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
 			return;
 

From e223e8a5b58509287098cb1deffb9655c7249a0c Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:30:21 +0200
Subject: [PATCH 10/28] tag_ape: move code to tag_ape_name_parse()

---
 src/tag_ape.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/tag_ape.c b/src/tag_ape.c
index 0e8f67456..733dab35a 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -37,6 +37,12 @@ static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 	[TAG_ITEM_DATE] = "year"
 };
 
+static enum tag_type
+tag_ape_name_parse(const char *name)
+{
+	return tag_table_lookup(ape_tag_names, name);
+}
+
 static struct tag *
 tag_ape_import_item(struct tag *tag, unsigned long flags,
 		    const char *key, const char *value, size_t value_length)
@@ -45,7 +51,7 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
 	if ((flags & (0x3 << 1)) != 0)
 		return tag;
 
-	enum tag_type type = tag_table_lookup(ape_tag_names, key);
+	enum tag_type type = tag_ape_name_parse(key);
 	if (type == TAG_NUM_OF_ITEM_TYPES)
 		return tag;
 

From 9550c87327cdb0c97ca855bc5d77d28cbbb723d5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:31:45 +0200
Subject: [PATCH 11/28] tag: added function tag_name_parse()

Convert a string into a tag_type enum.
---
 src/locate.c |  6 +++---
 src/tag.c    | 50 +++++++++++++++++++++++++++++++++++++++++---------
 src/tag.h    | 16 ++++++++++++++++
 3 files changed, 60 insertions(+), 12 deletions(-)

diff --git a/src/locate.c b/src/locate.c
index 175bca35a..7b4721fa9 100644
--- a/src/locate.c
+++ b/src/locate.c
@@ -42,9 +42,9 @@ locate_parse_type(const char *str)
 	if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
 		return LOCATE_TAG_ANY_TYPE;
 
-	for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
-		if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
-			return i;
+	i = tag_name_parse_i(str);
+	if (i != TAG_NUM_OF_ITEM_TYPES)
+		return i;
 
 	return -1;
 }
diff --git a/src/tag.c b/src/tag.c
index c34256b78..b228480c8 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -64,6 +64,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
 
 bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
 
+enum tag_type
+tag_name_parse(const char *name)
+{
+	assert(name != NULL);
+
+	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+		assert(tag_item_names[i] != NULL);
+
+		if (strcmp(name, tag_item_names[i]) == 0)
+			return (enum tag_type)i;
+	}
+
+	return TAG_NUM_OF_ITEM_TYPES;
+}
+
+enum tag_type
+tag_name_parse_i(const char *name)
+{
+	assert(name != NULL);
+
+	for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+		assert(tag_item_names[i] != NULL);
+
+		if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
+			return (enum tag_type)i;
+	}
+
+	return TAG_NUM_OF_ITEM_TYPES;
+}
+
 static size_t items_size(const struct tag *tag)
 {
 	return tag->num_items * sizeof(struct tag_item *);
@@ -76,7 +106,7 @@ void tag_lib_init(void)
 	char *temp;
 	char *s;
 	char *c;
-	int i;
+	enum tag_type type;
 
 	/* parse the "metadata_to_use" config parameter below */
 
@@ -98,16 +128,18 @@ void tag_lib_init(void)
 			if (*s == '\0')
 				quit = 1;
 			*s = '\0';
-			for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
-				if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
-					ignore_tag_items[i] = false;
-					break;
-				}
-			}
-			if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
+
+			c = g_strstrip(c);
+			if (*c == 0)
+				continue;
+
+			type = tag_name_parse_i(c);
+			if (type == TAG_NUM_OF_ITEM_TYPES)
 				g_error("error parsing metadata item \"%s\"",
 					c);
-			}
+
+			ignore_tag_items[type] = false;
+
 			s++;
 			c = s;
 		}
diff --git a/src/tag.h b/src/tag.h
index 75a86b387..8d968c254 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -93,6 +93,22 @@ struct tag {
 	unsigned num_items;
 };
 
+/**
+ * Parse the string, and convert it into a #tag_type.  Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ */
+enum tag_type
+tag_name_parse(const char *name);
+
+/**
+ * Parse the string, and convert it into a #tag_type.  Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ *
+ * Case does not matter.
+ */
+enum tag_type
+tag_name_parse_i(const char *name);
+
 /**
  * Creates an empty #tag.
  */

From 284659034d7ec0add185e03a27434b2bd7d88216 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:30:31 +0200
Subject: [PATCH 12/28] tag_ape: remove duplicate entries in the tag name table

Reuse the function tag_name_parse_i().
---
 src/tag_ape.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/tag_ape.c b/src/tag_ape.c
index 733dab35a..babefcda3 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -27,20 +27,18 @@
 #include <stdio.h>
 
 static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
-	[TAG_ITEM_TITLE] = "title",
-	[TAG_ITEM_ARTIST] = "artist",
-	[TAG_ITEM_ALBUM] = "album",
 	[TAG_ITEM_ALBUM_ARTIST] = "album artist",
-	[TAG_ITEM_COMMENT] = "comment",
-	[TAG_ITEM_GENRE] = "genre",
-	[TAG_ITEM_TRACK] = "track",
 	[TAG_ITEM_DATE] = "year"
 };
 
 static enum tag_type
 tag_ape_name_parse(const char *name)
 {
-	return tag_table_lookup(ape_tag_names, name);
+	enum tag_type type = tag_table_lookup(ape_tag_names, name);
+	if (type == TAG_NUM_OF_ITEM_TYPES)
+		type = tag_name_parse_i(name);
+
+	return type;
 }
 
 static struct tag *

From 8e3eace289e2e2481221426ea9e6ab38d80be5fe Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:36:00 +0200
Subject: [PATCH 13/28] decoder/mp4ff: moved code to mp4ff_tag_name_parse()

---
 src/decoder/mp4ff_plugin.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index 5e1251d82..d94084e90 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -353,6 +353,12 @@ static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 	[TAG_ITEM_PERFORMER] = "band",
 };
 
+static enum tag_type
+mp4ff_tag_name_parse(const char *name)
+{
+	return tag_table_lookup(mp4ff_tag_names, name);
+}
+
 static struct tag *
 mp4_tag_dup(const char *file)
 {
@@ -408,7 +414,7 @@ mp4_tag_dup(const char *file)
 
 		mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
 
-		enum tag_type type = tag_table_lookup(mp4ff_tag_names, item);
+		enum tag_type type = mp4ff_tag_name_parse(item);
 		if (type != TAG_NUM_OF_ITEM_TYPES)
 			tag_add_item(ret, type, value);
 

From 5ebe33653c162a3a07a9a1fd71201e22fa152c6c Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:36:15 +0200
Subject: [PATCH 14/28] decoder/mp4ff: remove duplicate entries in the tag name
 table

Reuse the function tag_name_parse_i().
---
 src/decoder/mp4ff_plugin.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index d94084e90..d34080493 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -341,14 +341,6 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
 }
 
 static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
-	[TAG_ITEM_TITLE] = "title",
-	[TAG_ITEM_ARTIST] = "artist",
-	[TAG_ITEM_ALBUM] = "album",
-	[TAG_ITEM_ALBUM_ARTIST] = "albumartist",
-	[TAG_ITEM_TRACK] = "track",
-	[TAG_ITEM_DISC] = "disc",
-	[TAG_ITEM_GENRE] = "genre",
-	[TAG_ITEM_DATE] = "date",
 	[TAG_ITEM_COMPOSER] = "writer",
 	[TAG_ITEM_PERFORMER] = "band",
 };
@@ -356,7 +348,11 @@ static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
 static enum tag_type
 mp4ff_tag_name_parse(const char *name)
 {
-	return tag_table_lookup(mp4ff_tag_names, name);
+	enum tag_type type = tag_table_lookup(mp4ff_tag_names, name);
+	if (type == TAG_NUM_OF_ITEM_TYPES)
+		type = tag_name_parse_i(name);
+
+	return type;
 }
 
 static struct tag *

From 77e6810c14e3b47f27a385e2fd9e8760710d0a99 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 19:37:36 +0000
Subject: [PATCH 15/28] decoder/mikmod: fix memory leak

The return value of Player_LoadTitle() is allocated with malloc(), and
must be freed by the caller.
---
 NEWS                        | 1 +
 src/decoder/mikmod_plugin.c | 6 ++++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 6f499856d..eaaa24b9f 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ ver 0.15.11 (2010/??/??)
   - ape: support album artist
 * decoders:
   - mp4ff: support tags "albumartist", "band"
+  - mikmod: fix memory leak
 
 
 ver 0.15.10 (2010/05/30)
diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c
index 065c34319..f60dcbc61 100644
--- a/src/decoder/mikmod_plugin.c
+++ b/src/decoder/mikmod_plugin.c
@@ -219,10 +219,12 @@ static struct tag *modTagDup(const char *file)
 	ret->time = 0;
 
 	path2 = g_strdup(file);
-	title = g_strdup(Player_LoadTitle(path2));
+	title = Player_LoadTitle(path2);
 	g_free(path2);
-	if (title)
+	if (title) {
 		tag_add_item(ret, TAG_ITEM_TITLE, title);
+		free(title);
+	}
 
 	return ret;
 }

From 1bffdabe41471b994f43809d3ca18fd54ac8bf66 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:39:34 +0200
Subject: [PATCH 16/28] directory_print: return void

There is no useful return value here.
---
 src/directory_print.c | 8 ++------
 src/directory_print.h | 2 +-
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/src/directory_print.c b/src/directory_print.c
index e0575e80f..1c9f23d69 100644
--- a/src/directory_print.c
+++ b/src/directory_print.c
@@ -22,7 +22,7 @@
 #include "client.h"
 #include "song_print.h"
 
-static int
+static void
 dirvec_print(struct client *client, const struct dirvec *dv)
 {
 	size_t i;
@@ -30,15 +30,11 @@ dirvec_print(struct client *client, const struct dirvec *dv)
 	for (i = 0; i < dv->nr; ++i)
 		client_printf(client, DIRECTORY_DIR "%s\n",
 			      directory_get_path(dv->base[i]));
-
-	return 0;
 }
 
-int
+void
 directory_print(struct client *client, const struct directory *directory)
 {
 	dirvec_print(client, &directory->children);
 	songvec_print(client, &directory->songs);
-
-	return 0;
 }
diff --git a/src/directory_print.h b/src/directory_print.h
index 6dd099241..7c0110502 100644
--- a/src/directory_print.h
+++ b/src/directory_print.h
@@ -23,7 +23,7 @@
 struct client;
 struct directory;
 
-int
+void
 directory_print(struct client *client, const struct directory *directory);
 
 #endif

From 0a0c78674f8ca6ffce08feabd7c292133fd2d86e Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:40:33 +0200
Subject: [PATCH 17/28] playlist: emit IDLE_OPTIONS when resetting single mode

---
 NEWS                   | 1 +
 src/playlist_control.c | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/NEWS b/NEWS
index eaaa24b9f..4a4c4405f 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.15.11 (2010/??/??)
 * decoders:
   - mp4ff: support tags "albumartist", "band"
   - mikmod: fix memory leak
+* playlist: emit IDLE_OPTIONS when resetting single mode
 
 
 ver 0.15.10 (2010/05/30)
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4359611fd..4c156f0f5 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -24,6 +24,7 @@
 
 #include "playlist_internal.h"
 #include "player_control.h"
+#include "idle.h"
 
 #include <glib.h>
 
@@ -156,6 +157,8 @@ nextSongInPlaylist(struct playlist *playlist)
 	if (next_order < 0) {
 		/* cancel single */
 		playlist->queue.single = false;
+		idle_add(IDLE_OPTIONS);
+
 		/* no song after this one: stop playback */
 		stopPlaylist(playlist);
 

From 34415bf0b60738b22099023af5cd2cd1d202bf80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20V=C3=B6gele?= <andreas@kurzegasse.de>
Date: Wed, 30 Jun 2010 21:42:01 +0200
Subject: [PATCH 18/28] Make get_remote_uid() work on BSD

I've attached a patch that will make file URIs work on operating systems
that provide the getpeereid() function call to check the user ID of the
peer connected to a UNIX domain socket.
---
 NEWS         | 1 +
 configure.ac | 1 +
 src/listen.c | 8 +++++++-
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 4a4c4405f..6e1c41076 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ ver 0.15.11 (2010/??/??)
   - mp4ff: support tags "albumartist", "band"
   - mikmod: fix memory leak
 * playlist: emit IDLE_OPTIONS when resetting single mode
+* listen: make get_remote_uid() work on BSD
 
 
 ver 0.15.10 (2010/05/30)
diff --git a/configure.ac b/configure.ac
index 9249ada70..1043b2f65 100644
--- a/configure.ac
+++ b/configure.ac
@@ -177,6 +177,7 @@ AC_ARG_ENABLE(un,
 if test x$enable_un = xyes; then
 	AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
 	STRUCT_UCRED
+	AC_CHECK_FUNCS(getpeereid)
 fi
 
 
diff --git a/src/listen.c b/src/listen.c
index 98108d9da..d6cade855 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -407,7 +407,13 @@ static int get_remote_uid(int fd)
 
 	return cred.uid;
 #else
-	(void)fd;
+#ifdef HAVE_GETPEEREID
+	uid_t euid;
+	gid_t egid;
+
+	if (getpeereid(fd, &euid, &egid) == 0)
+		return euid;
+#endif
 	return -1;
 #endif
 }

From ec89ce5a8a3a609c5ecd725560304c9686726fa7 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:55:03 +0200
Subject: [PATCH 19/28] decoder/mp4ff: support tag "album artist"

We already supported "albumartist", but it seems some folks also use
"album artist" (with a space).
---
 NEWS                       | 2 +-
 src/decoder/mp4ff_plugin.c | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 6e1c41076..1a8cf2364 100644
--- a/NEWS
+++ b/NEWS
@@ -2,7 +2,7 @@ ver 0.15.11 (2010/??/??)
 * tags:
   - ape: support album artist
 * decoders:
-  - mp4ff: support tags "albumartist", "band"
+  - mp4ff: support tags "album artist", "albumartist", "band"
   - mikmod: fix memory leak
 * playlist: emit IDLE_OPTIONS when resetting single mode
 * listen: make get_remote_uid() work on BSD
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index d34080493..d5afe084b 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -341,6 +341,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
 }
 
 static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
+	[TAG_ITEM_ALBUM_ARTIST] = "album artist",
 	[TAG_ITEM_COMPOSER] = "writer",
 	[TAG_ITEM_PERFORMER] = "band",
 };

From 768be22f7ca3ef129a86dae23d51e2b34f5b4bc1 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:55:46 +0200
Subject: [PATCH 20/28] pcm_buffer: make the buffer pointer "void"

---
 src/pcm_buffer.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h
index 1d2a57310..b143bd98f 100644
--- a/src/pcm_buffer.h
+++ b/src/pcm_buffer.h
@@ -28,7 +28,7 @@
  * would put too much stress on the allocator.
  */
 struct pcm_buffer {
-	char *buffer;
+	void *buffer;
 
 	size_t size;
 };

From 0d03bdce6d590429143e821e2bcf34142b57b8fa Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 21:56:04 +0200
Subject: [PATCH 21/28] configure.ac: check ffmpeg version number with
 pkg-config

Replace the check for avcodec_decode_audio2(), assume it's present in
libavcodec version 51.
---
 configure.ac | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/configure.ac b/configure.ac
index 1043b2f65..f2a013720 100644
--- a/configure.ac
+++ b/configure.ac
@@ -914,17 +914,9 @@ fi
 
 AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
 
-MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat libavcodec libavutil],
+MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49],
 	[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
 
-if test x$enable_ffmpeg = xyes; then
-	old_LIBS=$LIBS
-	LIBS="$LIBS $FFMPEG_LIBS"
-	AC_CHECK_LIB(avcodec, avcodec_decode_audio2,,
-		enable_ffmpeg=no)
-	LIBS=$old_LIBS
-fi
-
 if test x$enable_ffmpeg = xyes; then
 	# prior to ffmpeg svn12865, you had to specify include files
 	# without path prefix

From 814daac5bae1e7e3a67cd7ed727bd1fe6b9a3889 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Mon, 18 Jan 2010 11:05:15 +0100
Subject: [PATCH 22/28] decoder/ffmpeg: free AVFormatContext on error

Fix a memory leak in some code paths.
---
 NEWS                        | 1 +
 src/decoder/ffmpeg_plugin.c | 4 ++++
 2 files changed, 5 insertions(+)

diff --git a/NEWS b/NEWS
index 1a8cf2364..d0b8788a7 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.15.11 (2010/??/??)
 * decoders:
   - mp4ff: support tags "album artist", "albumartist", "band"
   - mikmod: fix memory leak
+  - ffmpeg: free AVFormatContext on error
 * playlist: emit IDLE_OPTIONS when resetting single mode
 * listen: make get_remote_uid() work on BSD
 
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 2a46601f4..2e06c13f8 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -192,12 +192,14 @@ ffmpeg_helper(const char *uri, struct input_stream *input,
 
 	if (av_find_stream_info(format_context)<0) {
 		g_warning("Couldn't find stream info\n");
+		av_close_input_file(format_context);
 		return false;
 	}
 
 	audio_stream = ffmpeg_find_audio_stream(format_context);
 	if (audio_stream == -1) {
 		g_warning("No audio stream inside\n");
+		av_close_input_file(format_context);
 		return false;
 	}
 
@@ -209,11 +211,13 @@ ffmpeg_helper(const char *uri, struct input_stream *input,
 
 	if (!codec) {
 		g_warning("Unsupported audio codec\n");
+		av_close_input_file(format_context);
 		return false;
 	}
 
 	if (avcodec_open(codec_context, codec)<0) {
 		g_warning("Could not open codec\n");
+		av_close_input_file(format_context);
 		return false;
 	}
 

From c3569814bda057142da3f02a8d0cccb35184e396 Mon Sep 17 00:00:00 2001
From: Anton Khirnov <wyskas@gmail.com>
Date: Tue, 2 Feb 2010 12:35:08 +0100
Subject: [PATCH 23/28] ffmpeg: read more metadata.

---
 NEWS                        |  1 +
 src/decoder/ffmpeg_plugin.c | 11 ++++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index d0b8788a7..14465cad5 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ ver 0.15.11 (2010/??/??)
   - mp4ff: support tags "album artist", "albumartist", "band"
   - mikmod: fix memory leak
   - ffmpeg: free AVFormatContext on error
+  - ffmpeg: read more metadata
 * playlist: emit IDLE_OPTIONS when resetting single mode
 * listen: make get_remote_uid() work on BSD
 
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 2e06c13f8..7f2207669 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -405,12 +405,21 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
 	av_metadata_conv(f, NULL, f->iformat->metadata_conv);
 
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
+#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date");
+#else
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+#endif
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
 	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
-	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer");
+	ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc");
 #else
 	if (f->author[0])
 		tag_add_item(tag, TAG_ITEM_ARTIST, f->author);

From a1882f48beee0c1b6750ec03f8b133e7325057a9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 23:38:49 +0200
Subject: [PATCH 24/28] decoder/vorbis: handle uri==NULL

This fixes a theoretical crash, which has never occurred in practice.
---
 NEWS                        | 1 +
 src/decoder/vorbis_plugin.c | 3 +++
 2 files changed, 4 insertions(+)

diff --git a/NEWS b/NEWS
index 14465cad5..fac8ec6fd 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ ver 0.15.11 (2010/??/??)
 * decoders:
   - mp4ff: support tags "album artist", "albumartist", "band"
   - mikmod: fix memory leak
+  - vorbis: handle uri==NULL
   - ffmpeg: free AVFormatContext on error
   - ffmpeg: read more metadata
 * playlist: emit IDLE_OPTIONS when resetting single mode
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index 3da56bd5c..7c782a779 100644
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -233,6 +233,9 @@ oggvorbis_seekable(struct decoder *decoder)
 	bool seekable;
 
 	uri = decoder_get_uri(decoder);
+	if (uri == NULL)
+		return false;
+
 	/* disable seeking on remote streams, because libvorbis seeks
 	   around like crazy, and due to being very expensive, this
 	   delays song playback my 10 or 20 seconds */

From 0265c34bed4bdfd45a4ef1b5ff73071c5d5a5e10 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 23:40:04 +0200
Subject: [PATCH 25/28] decoder/ffmpeg: free URI, fix memory leak

Free the string allocated by decoder_get_uri().
---
 NEWS                        | 1 +
 src/decoder/ffmpeg_plugin.c | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index fac8ec6fd..3ae9fc7f3 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ ver 0.15.11 (2010/??/??)
   - mp4ff: support tags "album artist", "albumartist", "band"
   - mikmod: fix memory leak
   - vorbis: handle uri==NULL
+  - ffmpeg: fix memory leak
   - ffmpeg: free AVFormatContext on error
   - ffmpeg: read more metadata
 * playlist: emit IDLE_OPTIONS when resetting single mode
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 7f2207669..fc402ca04 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -376,8 +376,10 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 	ctx.input = input;
 	ctx.decoder = decoder;
 
-	ffmpeg_helper(decoder_get_uri(decoder), input,
+	char *uri = decoder_get_uri(decoder);
+	ffmpeg_helper(uri, input,
 		      ffmpeg_decode_internal, &ctx);
+	g_free(uri);
 }
 
 #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)

From 375a09d6f6d09fc132976e1f82647cc56fb33640 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 20:32:29 +0200
Subject: [PATCH 26/28] decoder/ffmpeg: manual format probing

Use the libavformat function av_probe_input_format() to probe the
AVInputFormat, instead of letting av_open_input_file() do it
implicitly.  We will switch to av_open_input_stream() very soon, which
does not have the probing code.

Loosely based on a patch from Jasper St. Pierre.
---
 src/decoder/ffmpeg_plugin.c | 51 ++++++++++++++++++++++++++++++++++---
 1 file changed, 47 insertions(+), 4 deletions(-)

diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index fc402ca04..f7218e238 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -160,11 +160,53 @@ append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
 	g_free(base);
 }
 
+static AVInputFormat *
+ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
+	     const char *uri)
+{
+	enum {
+		BUFFER_SIZE = 16384,
+		PADDING = 16,
+	};
+
+	unsigned char *buffer = g_malloc(BUFFER_SIZE);
+	size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
+	if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET)) {
+		g_free(buffer);
+		return NULL;
+	}
+
+	/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
+	   beyond the declared buffer limit, which makes valgrind
+	   angry; this workaround removes some padding from the buffer
+	   size */
+	nbytes -= PADDING;
+
+	AVProbeData avpd = {
+		.buf = buffer,
+		.buf_size = nbytes,
+		.filename = uri,
+	};
+
+	AVInputFormat *format = av_probe_input_format(&avpd, true);
+	g_free(buffer);
+
+	return format;
+}
+
 static bool
-ffmpeg_helper(const char *uri, struct input_stream *input,
+ffmpeg_helper(const char *uri,
+	      struct decoder *decoder, struct input_stream *input,
 	      bool (*callback)(struct ffmpeg_context *ctx),
 	      struct ffmpeg_context *ctx)
 {
+	AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri);
+	if (input_format == NULL)
+		return false;
+
+	g_debug("detected input format '%s' (%s)",
+		input_format->name, input_format->long_name);
+
 	AVFormatContext *format_context;
 	AVCodecContext *codec_context;
 	AVCodec *codec;
@@ -185,7 +227,8 @@ ffmpeg_helper(const char *uri, struct input_stream *input,
 	}
 
 	//ffmpeg works with ours "fileops" helper
-	if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
+	if (av_open_input_file(&format_context, stream.url, input_format,
+			       0, NULL) != 0) {
 		g_warning("Open failed\n");
 		return false;
 	}
@@ -377,7 +420,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 	ctx.decoder = decoder;
 
 	char *uri = decoder_get_uri(decoder);
-	ffmpeg_helper(uri, input,
+	ffmpeg_helper(uri, decoder, input,
 		      ffmpeg_decode_internal, &ctx);
 	g_free(uri);
 }
@@ -465,7 +508,7 @@ static struct tag *ffmpeg_tag(const char *file)
 	ctx.decoder = NULL;
 	ctx.tag = tag_new();
 
-	ret = ffmpeg_helper(file, &input, ffmpeg_tag_internal, &ctx);
+	ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx);
 	if (!ret) {
 		tag_free(ctx.tag);
 		ctx.tag = NULL;

From 49bc317fb8b5dad101eb0d995167ce0ccaf9f7fc Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 30 Jun 2010 23:27:45 +0200
Subject: [PATCH 27/28] decoder/ffmpeg: fix libavformat 0.6 by using
 av_open_input_stream()

libavformat 0.6 does not pass the original URI pointer to the "open"
method, which leads to a crash because MPD was using a dirty hack to
pass a pointer to that method.

This patch switches to av_open_input_stream() with a custom
ByteIOContext class, instead of doing the URI string hack with
av_open_input_file().

Loosely based on a patch from Jasper St. Pierre.
---
 NEWS                        |   1 +
 src/decoder/ffmpeg_plugin.c | 134 ++++++++++++++----------------------
 2 files changed, 53 insertions(+), 82 deletions(-)

diff --git a/NEWS b/NEWS
index 3ae9fc7f3..a68abb0ed 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ ver 0.15.11 (2010/??/??)
   - ffmpeg: fix memory leak
   - ffmpeg: free AVFormatContext on error
   - ffmpeg: read more metadata
+  - ffmpeg: fix libavformat 0.6 by using av_open_input_stream()
 * playlist: emit IDLE_OPTIONS when resetting single mode
 * listen: make get_remote_uid() work on BSD
 
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index f7218e238..9bae39793 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -53,48 +53,27 @@ struct ffmpeg_context {
 	struct tag *tag;
 };
 
-struct ffmpeg_stream {
-	/** hack - see url_to_struct() */
-	char url[64];
-
+struct mpd_ffmpeg_stream {
 	struct decoder *decoder;
 	struct input_stream *input;
+
+	ByteIOContext *io;
+	unsigned char buffer[8192];
 };
 
-/**
- * Convert a faked mpd:// URL to a ffmpeg_stream structure.  This is a
- * hack because ffmpeg does not provide a nice API for passing a
- * user-defined pointer to mpdurl_open().
- */
-static struct ffmpeg_stream *url_to_struct(const char *url)
+static int
+mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
 {
-	union {
-		const char *in;
-		struct ffmpeg_stream *out;
-	} u = { .in = url };
-	return u.out;
-}
-
-static int mpd_ffmpeg_open(URLContext *h, const char *filename,
-			   G_GNUC_UNUSED int flags)
-{
-	struct ffmpeg_stream *stream = url_to_struct(filename);
-	h->priv_data = stream;
-	h->is_streamed = stream->input->seekable ? 0 : 1;
-	return 0;
-}
-
-static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size)
-{
-	struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
+	struct mpd_ffmpeg_stream *stream = opaque;
 
 	return decoder_read(stream->decoder, stream->input,
 			    (void *)buf, size);
 }
 
-static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
+static int64_t
+mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
 {
-	struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
+	struct mpd_ffmpeg_stream *stream = opaque;
 	bool ret;
 
 	if (whence == AVSEEK_SIZE)
@@ -107,25 +86,36 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
 	return stream->input->offset;
 }
 
-static int mpd_ffmpeg_close(URLContext *h)
+static struct mpd_ffmpeg_stream *
+mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
 {
-	h->priv_data = NULL;
-	return 0;
+	struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
+	stream->decoder = decoder;
+	stream->input = input;
+	stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
+				       false, stream,
+				       mpd_ffmpeg_stream_read, NULL,
+				       input->seekable
+				       ? mpd_ffmpeg_stream_seek : NULL);
+	if (stream->io == NULL) {
+		g_free(stream);
+		return NULL;
+	}
+
+	return stream;
 }
 
-static URLProtocol mpd_ffmpeg_fileops = {
-	.name = "mpd",
-	.url_open = mpd_ffmpeg_open,
-	.url_read = mpd_ffmpeg_read,
-	.url_seek = mpd_ffmpeg_seek,
-	.url_close = mpd_ffmpeg_close,
-};
+static void
+mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
+{
+	av_free(stream->io);
+	g_free(stream);
+}
 
 static bool
 ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
 {
 	av_register_all();
-	register_protocol(&mpd_ffmpeg_fileops);
 	return true;
 }
 
@@ -140,26 +130,6 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
 	return -1;
 }
 
-/**
- * Append the suffix of the original URI to the virtual stream URI.
- * Without this, libavformat cannot detect some of the codecs
- * (e.g. "shorten").
- */
-static void
-append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
-{
-	assert(stream != NULL);
-	assert(uri != NULL);
-
-	char *base = g_path_get_basename(uri);
-
-	const char *suffix = strrchr(base, '.');
-	if (suffix != NULL && suffix[1] != 0)
-		g_strlcat(stream->url, suffix, sizeof(stream->url));
-
-	g_free(base);
-}
-
 static AVInputFormat *
 ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
 	     const char *uri)
@@ -207,42 +177,39 @@ ffmpeg_helper(const char *uri,
 	g_debug("detected input format '%s' (%s)",
 		input_format->name, input_format->long_name);
 
+	struct mpd_ffmpeg_stream *stream =
+		mpd_ffmpeg_stream_open(decoder, input);
+	if (stream == NULL) {
+		g_warning("Failed to open stream");
+		return false;
+	}
+
 	AVFormatContext *format_context;
 	AVCodecContext *codec_context;
 	AVCodec *codec;
 	int audio_stream;
-	struct ffmpeg_stream stream = {
-		.url = "mpd://X", /* only the mpd:// prefix matters */
-	};
 	bool ret;
 
-	if (uri != NULL)
-		append_uri_suffix(&stream, uri);
-
-	stream.input = input;
-	if (ctx && ctx->decoder) {
-		stream.decoder = ctx->decoder; //are we in decoding loop ?
-	} else {
-		stream.decoder = NULL;
-	}
-
 	//ffmpeg works with ours "fileops" helper
-	if (av_open_input_file(&format_context, stream.url, input_format,
-			       0, NULL) != 0) {
+	if (av_open_input_stream(&format_context, stream->io, uri,
+				 input_format, NULL) != 0) {
 		g_warning("Open failed\n");
+		mpd_ffmpeg_stream_close(stream);
 		return false;
 	}
 
 	if (av_find_stream_info(format_context)<0) {
 		g_warning("Couldn't find stream info\n");
-		av_close_input_file(format_context);
+		av_close_input_stream(format_context);
+		mpd_ffmpeg_stream_close(stream);
 		return false;
 	}
 
 	audio_stream = ffmpeg_find_audio_stream(format_context);
 	if (audio_stream == -1) {
 		g_warning("No audio stream inside\n");
-		av_close_input_file(format_context);
+		av_close_input_stream(format_context);
+		mpd_ffmpeg_stream_close(stream);
 		return false;
 	}
 
@@ -254,13 +221,15 @@ ffmpeg_helper(const char *uri,
 
 	if (!codec) {
 		g_warning("Unsupported audio codec\n");
-		av_close_input_file(format_context);
+		av_close_input_stream(format_context);
+		mpd_ffmpeg_stream_close(stream);
 		return false;
 	}
 
 	if (avcodec_open(codec_context, codec)<0) {
 		g_warning("Could not open codec\n");
-		av_close_input_file(format_context);
+		av_close_input_stream(format_context);
+		mpd_ffmpeg_stream_close(stream);
 		return false;
 	}
 
@@ -274,7 +243,8 @@ ffmpeg_helper(const char *uri,
 		ret = true;
 
 	avcodec_close(codec_context);
-	av_close_input_file(format_context);
+	av_close_input_stream(format_context);
+	mpd_ffmpeg_stream_close(stream);
 
 	return ret;
 }

From 56bf4ede1896708c50df864521bd71aebf25aa02 Mon Sep 17 00:00:00 2001
From: Avuton Olrich <avuton@gmail.com>
Date: Wed, 14 Jul 2010 17:33:28 -0700
Subject: [PATCH 28/28] mpd version 0.15.11

---
 NEWS         | 2 +-
 configure.ac | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index a68abb0ed..65d693166 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-ver 0.15.11 (2010/??/??)
+ver 0.15.11 (2010/06/14)
 * tags:
   - ape: support album artist
 * decoders:
diff --git a/configure.ac b/configure.ac
index f2a013720..60901a3f6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,5 +1,5 @@
 AC_PREREQ(2.60)
-AC_INIT(mpd, 0.15.11~git, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.15.11, musicpd-dev-team@lists.sourceforge.net)
 AC_CONFIG_SRCDIR([src/main.c])
 AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
 AM_CONFIG_HEADER(config.h)