--- Makefile	2012-11-07 09:46:17.000000000 +0100
+++ Makefile	2013-01-04 07:15:35.293069953 +0100
@@ -74,6 +74,7 @@ mikmod-objs		:= mikmod.lo
 modplug-objs		:= modplug.lo
 mpc-objs		:= mpc.lo
 vorbis-objs		:= vorbis.lo
+opus-objs		:= opus.lo
 wavpack-objs		:= wavpack.lo
 wav-objs		:= wav.lo
 mp4-objs		:= mp4.lo
@@ -88,6 +89,7 @@ ip-$(CONFIG_MIKMOD)	+= mikmod.so
 ip-$(CONFIG_MODPLUG)	+= modplug.so
 ip-$(CONFIG_MPC)	+= mpc.so
 ip-$(CONFIG_VORBIS)	+= vorbis.so
+ip-$(CONFIG_OPUS)	+= opus.so
 ip-$(CONFIG_WAVPACK)	+= wavpack.so
 ip-$(CONFIG_WAV)	+= wav.so
 ip-$(CONFIG_MP4)	+= mp4.so
@@ -102,6 +104,7 @@ $(mikmod-objs):		CFLAGS += $(MIKMOD_CFLA
 $(modplug-objs):	CFLAGS += $(MODPLUG_CFLAGS)
 $(mpc-objs):		CFLAGS += $(MPC_CFLAGS)
 $(vorbis-objs):		CFLAGS += $(VORBIS_CFLAGS)
+$(opus-objs):		CFLAGS += $(OPUS_CFLAGS)
 $(wavpack-objs):	CFLAGS += $(WAVPACK_CFLAGS)
 $(mp4-objs):		CFLAGS += $(MP4_CFLAGS)
 $(aac-objs):		CFLAGS += $(AAC_CFLAGS)
@@ -129,6 +132,9 @@ mpc.so: $(mpc-objs) $(libcmus-y)
 vorbis.so: $(vorbis-objs) $(libcmus-y)
	$(call cmd,ld_dl,$(VORBIS_LIBS))

+opus.so: $(opus-objs) $(libcmus-y)
+	$(call cmd,ld_dl,$(OPUS_LIBS))
+
 wavpack.so: $(wavpack-objs) $(libcmus-y)
	$(call cmd,ld_dl,$(WAVPACK_LIBS))

--- configure	2012-11-07 09:46:17.000000000 +0100
+++ configure	2013-01-04 07:15:35.293069953 +0100
@@ -228,6 +228,12 @@ check_vorbis()
	fi
 }

+check_opus()
+{
+	pkg_config OPUS "opusfile"
+	return $?
+}
+
 check_wavpack()
 {
	pkg_config WAVPACK "wavpack" "" "-lwavpack"
@@ -425,6 +431,7 @@ Optional Features: y/n
   CONFIG_MPC      	libmpcdec (Musepack .mpc, .mpp, .mp+)           [auto]
   CONFIG_VORBIS   	Ogg/Vorbis (.ogg, application/ogg, audio/x-ogg) [auto]
   CONFIG_TREMOR   	Use Tremor as Ogg/Vorbis input plugin           [n]
+  CONFIG_OPUS   	Opus (.opus)                                    [auto]
   CONFIG_WAV      	WAV                                             [y]
   CONFIG_WAVPACK  	WavPack (.wv, audio/x-wavpack)                  [auto]
   CONFIG_MP4      	MPEG-4 AAC (.mp4, .m4a, .m4b)                   [auto]
@@ -485,6 +492,7 @@ check check_mikmod  CONFIG_MIKMOD
 check check_modplug CONFIG_MODPLUG
 check check_mpc     CONFIG_MPC
 check check_vorbis  CONFIG_VORBIS
+check check_opus    CONFIG_OPUS
 check check_wavpack CONFIG_WAVPACK
 check check_mp4     CONFIG_MP4
 check check_aac     CONFIG_AAC
@@ -534,7 +542,7 @@ config_header config/cue.h CONFIG_CUE
 CFLAGS="${CFLAGS} -DHAVE_CONFIG"

 makefile_vars bindir datadir libdir mandir exampledir
-makefile_vars CONFIG_CDIO CONFIG_FLAC CONFIG_MAD CONFIG_MIKMOD CONFIG_MODPLUG CONFIG_MPC CONFIG_VORBIS CONFIG_WAVPACK CONFIG_WAV CONFIG_MP4 CONFIG_AAC CONFIG_FFMPEG CONFIG_CUE
+makefile_vars CONFIG_CDIO CONFIG_FLAC CONFIG_MAD CONFIG_MIKMOD CONFIG_MODPLUG CONFIG_MPC CONFIG_VORBIS CONFIG_OPUS CONFIG_WAVPACK CONFIG_WAV CONFIG_MP4 CONFIG_AAC CONFIG_FFMPEG CONFIG_CUE
 makefile_vars CONFIG_ROAR CONFIG_PULSE CONFIG_ALSA CONFIG_AO CONFIG_ARTS CONFIG_OSS CONFIG_SUN CONFIG_WAVEOUT

 generate_config_mk
--- opus.c	1970-01-01 01:00:00.000000000 +0100
+++ opus.c	2013-01-04 07:15:35.493069945 +0100
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2008-2012 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include "read_wrapper.h"
+#include "debug.h"
+#include "comment.h"
+
+#include <opusfile.h>
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define SAMPLING_RATE 48000
+#define CHANNELS 2
+
+struct opus_private {
+	OggOpusFile *of;
+	int current_link;
+};
+
+static int read_func(void *datasource, unsigned char *ptr, int size)
+{
+	struct input_plugin_data *ip_data = datasource;
+	return read_wrapper(ip_data, ptr, size);
+}
+
+static int seek_func(void *datasource, opus_int64 offset, int whence)
+{
+	struct input_plugin_data *ip_data = datasource;
+	return lseek(ip_data->fd, offset, whence);
+}
+
+static int close_func(void *datasource)
+{
+	struct input_plugin_data *ip_data;
+	int rc;
+
+	ip_data = datasource;
+	rc = close(ip_data->fd);
+	ip_data->fd = -1;
+	return rc;
+}
+
+static opus_int64 tell_func(void *datasource)
+{
+	struct input_plugin_data *ip_data = datasource;
+	return lseek(ip_data->fd, 0, SEEK_CUR);
+}
+
+static OpusFileCallbacks callbacks = {
+	.read = read_func,
+	.seek = seek_func,
+	.tell = tell_func,
+	.close = close_func
+};
+
+static int opus_open(struct input_plugin_data *ip_data)
+{
+	struct opus_private *priv;
+	int rc;
+	void *source;
+
+	priv = xnew(struct opus_private, 1);
+	priv->current_link = -1;
+	priv->of = NULL;
+
+	source = op_fdopen(&callbacks, ip_data->fd, "r");
+	if (source == NULL) {
+		free(priv);
+		return -IP_ERROR_INTERNAL;
+	}
+
+	priv->of = op_open_callbacks(source, &callbacks, NULL, 0, &rc);
+	if (rc != 0) {
+		d_print("op_open_callbacks failed: %d:%s\n", rc, strerror(rc));
+		free(priv);
+		/* ogg is a container format, so it is likely to contain
+		 * something else if it isn't opus */
+		return -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+	}
+	ip_data->private = priv;
+
+	ip_data->sf = sf_rate(SAMPLING_RATE)
+		| sf_channels(CHANNELS)
+		| sf_bits(16)
+		| sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+	ip_data->sf |= sf_bigendian(1);
+#endif
+	return 0;
+}
+
+static int opus_close(struct input_plugin_data *ip_data)
+{
+	struct opus_private *priv = ip_data->private;
+	/* this closes ip_data->fd! */
+	op_free(priv->of);
+	ip_data->fd = -1;
+	free(priv);
+	ip_data->private = NULL;
+	return 0;
+}
+
+/*
+ * -n
+ *     indicates error
+ * 0
+ *     indicates EOF
+ * n
+ *     indicates actual number of bytes read
+ */
+static int opus_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+	struct opus_private *priv;
+	int samples, current_link, rc;
+
+	priv = ip_data->private;
+
+	/* samples = number of samples read per channel */
+	samples = op_read_stereo(priv->of, (opus_int16*)buffer,
+							 count / sizeof(opus_int16));
+	if (samples < 0) {
+		switch (samples) {
+		case OP_HOLE:
+			errno = EAGAIN;
+			rc = -1;
+			break;
+		case OP_EREAD:
+			errno = EINVAL;
+			rc = -1;
+			break;
+		case OP_EFAULT:
+			errno = EINVAL;
+			rc = -1;
+			break;
+		case OP_EIMPL:
+			rc = -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+			break;
+		case OP_EINVAL:
+			errno = EINVAL;
+			rc = -1;
+			break;
+		case OP_ENOTFORMAT:
+			rc = -IP_ERROR_FILE_FORMAT;
+			break;
+		case OP_EBADHEADER:
+			rc = -IP_ERROR_FILE_FORMAT;
+			break;
+		case OP_EVERSION:
+			rc = -IP_ERROR_FILE_FORMAT;
+			break;
+		case OP_EBADPACKET:
+			errno = EINVAL;
+			rc = -1;
+			break;
+		case OP_EBADLINK:
+			errno = EINVAL;
+			rc = -1;
+			break;
+		case OP_EBADTIMESTAMP:
+			rc = -IP_ERROR_FILE_FORMAT;
+			break;
+		default:
+			d_print("error: %d\n", samples);
+			rc = -IP_ERROR_FILE_FORMAT;
+		}
+	} else if (samples == 0) {
+		/* EOF or buffer too small */
+		rc = 0;
+	} else {
+		current_link = op_current_link(priv->of);
+		if (current_link < 0) {
+			d_print("error: %d\n", current_link);
+			rc = -1;
+		} else {
+			if (ip_data->remote && current_link != priv->current_link) {
+				ip_data->metadata_changed = 1;
+				priv->current_link = current_link;
+			}
+
+			/* bytes = samples * channels * sample_size */
+			rc = samples * CHANNELS * sizeof(opus_int16);
+		}
+	}
+
+	return rc;
+}
+
+static int opus_seek(struct input_plugin_data *ip_data, double offset)
+{
+	struct opus_private *priv;
+	int rc;
+
+	priv = ip_data->private;
+
+	rc = op_pcm_seek(priv->of, offset * SAMPLING_RATE);
+	switch (rc) {
+	case OP_ENOSEEK:
+		return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+	case OP_EINVAL:
+		return -IP_ERROR_INTERNAL;
+	case OP_EREAD:
+		return -IP_ERROR_INTERNAL;
+	case OP_EFAULT:
+		return -IP_ERROR_INTERNAL;
+	case OP_EBADLINK:
+		return -IP_ERROR_INTERNAL;
+	}
+	return 0;
+}
+
+static int opus_read_comments(struct input_plugin_data *ip_data,
+							  struct keyval **comments)
+{
+	GROWING_KEYVALS(c);
+	struct opus_private *priv;
+	const OpusTags *ot;
+	int i;
+
+	priv = ip_data->private;
+
+	ot = op_tags(priv->of, -1);
+	if (ot == NULL) {
+		d_print("ot == NULL\n");
+		*comments = keyvals_new(0);
+		return 0;
+	}
+
+	for (i = 0; i < ot->comments; i++) {
+		const char *str = ot->user_comments[i];
+		const char *eq = strchr(str, '=');
+		char *key;
+
+		if (!eq) {
+			d_print("invalid comment: '%s' ('=' expected)\n", str);
+			continue;
+		}
+
+		key = xstrndup(str, eq - str);
+		comments_add_const(&c, key, eq + 1);
+		free(key);
+	}
+	keyvals_terminate(&c);
+	*comments = c.keyvals;
+	return 0;
+}
+
+static int opus_duration(struct input_plugin_data *ip_data)
+{
+	struct opus_private *priv;
+	ogg_int64_t samples;
+
+	priv = ip_data->private;
+
+	samples = op_pcm_total(priv->of, -1);
+	if (samples < 0)
+		return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+	return samples / SAMPLING_RATE;
+}
+
+static long opus_bitrate(struct input_plugin_data *ip_data)
+{
+	struct opus_private *priv;
+	opus_int32 bitrate;
+
+	priv = ip_data->private;
+
+	bitrate = op_bitrate(priv->of, -1);
+	if (bitrate < 0)
+		return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+	else
+		return bitrate;
+}
+
+static long opus_current_bitrate(struct input_plugin_data *ip_data)
+{
+	struct opus_private *priv;
+	opus_int32 bitrate;
+
+	priv = ip_data->private;
+
+	bitrate = op_bitrate_instant(priv->of);
+	if (bitrate < 0)
+		return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+	else
+		return bitrate;
+}
+
+static char *opus_codec(struct input_plugin_data *ip_data)
+{
+	return xstrdup("opus");
+}
+
+static char *opus_codec_profile(struct input_plugin_data *ip_data)
+{
+	return NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+	.open = opus_open,
+	.close = opus_close,
+	.read = opus_read,
+	.seek = opus_seek,
+	.read_comments = opus_read_comments,
+	.duration = opus_duration,
+	.bitrate = opus_bitrate,
+	.bitrate_current = opus_current_bitrate,
+	.codec = opus_codec,
+	.codec_profile = opus_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "opus", NULL };
+const char * const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
