Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b552e9a120 | ||
|
|
5923cfcde3 | ||
|
|
e3f4c7b91c | ||
|
|
54294366d5 | ||
|
|
4a7abc9d44 | ||
|
|
589bb54111 | ||
|
|
64dacd175a | ||
|
|
625e4755d1 | ||
|
|
676739c426 | ||
|
|
0fec8e0864 | ||
|
|
1f976d6e54 | ||
|
|
a4908dca42 | ||
|
|
8b055c3127 | ||
|
|
172182b18f | ||
|
|
898a13f196 | ||
|
|
b97e92468f | ||
|
|
56bf4ede18 | ||
|
|
49bc317fb8 | ||
|
|
375a09d6f6 | ||
|
|
0265c34bed | ||
|
|
a1882f48be | ||
|
|
c3569814bd | ||
|
|
814daac5ba | ||
|
|
0d03bdce6d | ||
|
|
768be22f7c | ||
|
|
ec89ce5a8a | ||
|
|
34415bf0b6 | ||
|
|
0a0c78674f | ||
|
|
1bffdabe41 | ||
|
|
77e6810c14 | ||
|
|
5ebe33653c | ||
|
|
8e3eace289 | ||
|
|
284659034d | ||
|
|
9550c87327 | ||
|
|
e223e8a5b5 | ||
|
|
4d6d372a5b | ||
|
|
0aeec90590 | ||
|
|
cfcd84655c | ||
|
|
5092eaf1cc | ||
|
|
9328558fc7 | ||
|
|
026bd15872 | ||
|
|
7cca55549b | ||
|
|
c7e89ea3a3 | ||
|
|
65ad298460 | ||
|
|
57e95ea6f4 | ||
|
|
442d2e74e3 | ||
|
|
28736414a8 | ||
|
|
e98bd55cbf | ||
|
|
a1a03deed2 | ||
|
|
0dcd865c2e |
@@ -142,6 +142,7 @@ mpd_headers = \
|
|||||||
src/tag.h \
|
src/tag.h \
|
||||||
src/tag_internal.h \
|
src/tag_internal.h \
|
||||||
src/tag_pool.h \
|
src/tag_pool.h \
|
||||||
|
src/tag_table.h \
|
||||||
src/tag_ape.h \
|
src/tag_ape.h \
|
||||||
src/tag_id3.h \
|
src/tag_id3.h \
|
||||||
src/tag_print.h \
|
src/tag_print.h \
|
||||||
|
|||||||
42
NEWS
42
NEWS
@@ -1,3 +1,45 @@
|
|||||||
|
ver 0.15.13 (2010/10/10)
|
||||||
|
* output_thread: fix race condition after CANCEL command
|
||||||
|
* output:
|
||||||
|
- httpd: fix random data in stream title
|
||||||
|
- httpd: MIME type audio/ogg for Ogg Vorbis
|
||||||
|
* input:
|
||||||
|
- rewind: update MIME not only once
|
||||||
|
- rewind: enable for MMS
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.12 (2010/07/20)
|
||||||
|
* input:
|
||||||
|
- curl: remove assertion after curl_multi_fdset()
|
||||||
|
* tags:
|
||||||
|
- rva2: set "gain", not "peak"
|
||||||
|
* decoders:
|
||||||
|
- wildmidi: support version 0.2.3
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.11 (2010/06/14)
|
||||||
|
* tags:
|
||||||
|
- ape: support album artist
|
||||||
|
* decoders:
|
||||||
|
- 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
|
||||||
|
- 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
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.10 (2010/05/30)
|
||||||
|
* input:
|
||||||
|
- mms: fix memory leak in error handler
|
||||||
|
- mms: initialize the "eof" attribute
|
||||||
|
* decoders:
|
||||||
|
- mad: properly calculate ID3 size without libid3tag
|
||||||
|
|
||||||
|
|
||||||
ver 0.15.9 (2010/03/21)
|
ver 0.15.9 (2010/03/21)
|
||||||
* decoders:
|
* decoders:
|
||||||
- mad: fix crash when seeking at end of song
|
- mad: fix crash when seeking at end of song
|
||||||
|
|||||||
17
configure.ac
17
configure.ac
@@ -1,5 +1,5 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
AC_INIT(mpd, 0.15.9, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.15.13, musicpd-dev-team@lists.sourceforge.net)
|
||||||
AC_CONFIG_SRCDIR([src/main.c])
|
AC_CONFIG_SRCDIR([src/main.c])
|
||||||
AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
|
AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
|
||||||
AM_CONFIG_HEADER(config.h)
|
AM_CONFIG_HEADER(config.h)
|
||||||
@@ -177,6 +177,7 @@ AC_ARG_ENABLE(un,
|
|||||||
if test x$enable_un = xyes; then
|
if test x$enable_un = xyes; then
|
||||||
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
|
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
|
||||||
STRUCT_UCRED
|
STRUCT_UCRED
|
||||||
|
AC_CHECK_FUNCS(getpeereid)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -913,17 +914,9 @@ fi
|
|||||||
|
|
||||||
AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
|
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])
|
[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
|
if test x$enable_ffmpeg = xyes; then
|
||||||
# prior to ffmpeg svn12865, you had to specify include files
|
# prior to ffmpeg svn12865, you had to specify include files
|
||||||
# without path prefix
|
# without path prefix
|
||||||
@@ -957,6 +950,10 @@ if test x$enable_wildmidi = xyes; then
|
|||||||
AC_CHECK_LIB(WildMidi, WildMidi_Init,,
|
AC_CHECK_LIB(WildMidi, WildMidi_Init,,
|
||||||
AC_MSG_ERROR([libwildmidi not found]))
|
AC_MSG_ERROR([libwildmidi not found]))
|
||||||
|
|
||||||
|
AC_CHECK_LIB(WildMidi, WildMidi_SampledSeek,
|
||||||
|
[AC_DEFINE(HAVE_WILDMIDI_SAMPLED_SEEK, 1,
|
||||||
|
[Defined if WildMidi_SampledSeek() is available (libwildmidi <= 0.2.2)])])
|
||||||
|
|
||||||
CFLAGS=$oldcflags
|
CFLAGS=$oldcflags
|
||||||
LIBS=$oldlibs
|
LIBS=$oldlibs
|
||||||
CPPFLAGS=$oldcppflags
|
CPPFLAGS=$oldcppflags
|
||||||
|
|||||||
@@ -53,48 +53,27 @@ struct ffmpeg_context {
|
|||||||
struct tag *tag;
|
struct tag *tag;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ffmpeg_stream {
|
struct mpd_ffmpeg_stream {
|
||||||
/** hack - see url_to_struct() */
|
|
||||||
char url[64];
|
|
||||||
|
|
||||||
struct decoder *decoder;
|
struct decoder *decoder;
|
||||||
struct input_stream *input;
|
struct input_stream *input;
|
||||||
|
|
||||||
|
ByteIOContext *io;
|
||||||
|
unsigned char buffer[8192];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
static int
|
||||||
* Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a
|
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
union {
|
struct mpd_ffmpeg_stream *stream = opaque;
|
||||||
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;
|
|
||||||
|
|
||||||
return decoder_read(stream->decoder, stream->input,
|
return decoder_read(stream->decoder, stream->input,
|
||||||
(void *)buf, size);
|
(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;
|
bool ret;
|
||||||
|
|
||||||
if (whence == AVSEEK_SIZE)
|
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;
|
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;
|
struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
|
||||||
return 0;
|
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 = {
|
static void
|
||||||
.name = "mpd",
|
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
|
||||||
.url_open = mpd_ffmpeg_open,
|
{
|
||||||
.url_read = mpd_ffmpeg_read,
|
av_free(stream->io);
|
||||||
.url_seek = mpd_ffmpeg_seek,
|
g_free(stream);
|
||||||
.url_close = mpd_ffmpeg_close,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
|
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
|
||||||
{
|
{
|
||||||
av_register_all();
|
av_register_all();
|
||||||
register_protocol(&mpd_ffmpeg_fileops);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,64 +130,86 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static AVInputFormat *
|
||||||
* Append the suffix of the original URI to the virtual stream URI.
|
ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
|
||||||
* Without this, libavformat cannot detect some of the codecs
|
const char *uri)
|
||||||
* (e.g. "shorten").
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
|
|
||||||
{
|
{
|
||||||
assert(stream != NULL);
|
enum {
|
||||||
assert(uri != NULL);
|
BUFFER_SIZE = 16384,
|
||||||
|
PADDING = 16,
|
||||||
|
};
|
||||||
|
|
||||||
char *base = g_path_get_basename(uri);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
const char *suffix = strrchr(base, '.');
|
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||||
if (suffix != NULL && suffix[1] != 0)
|
beyond the declared buffer limit, which makes valgrind
|
||||||
g_strlcat(stream->url, suffix, sizeof(stream->url));
|
angry; this workaround removes some padding from the buffer
|
||||||
|
size */
|
||||||
|
nbytes -= PADDING;
|
||||||
|
|
||||||
g_free(base);
|
AVProbeData avpd = {
|
||||||
|
.buf = buffer,
|
||||||
|
.buf_size = nbytes,
|
||||||
|
.filename = uri,
|
||||||
|
};
|
||||||
|
|
||||||
|
AVInputFormat *format = av_probe_input_format(&avpd, true);
|
||||||
|
g_free(buffer);
|
||||||
|
|
||||||
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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),
|
bool (*callback)(struct ffmpeg_context *ctx),
|
||||||
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);
|
||||||
|
|
||||||
|
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;
|
AVFormatContext *format_context;
|
||||||
AVCodecContext *codec_context;
|
AVCodecContext *codec_context;
|
||||||
AVCodec *codec;
|
AVCodec *codec;
|
||||||
int audio_stream;
|
int audio_stream;
|
||||||
struct ffmpeg_stream stream = {
|
|
||||||
.url = "mpd://X", /* only the mpd:// prefix matters */
|
|
||||||
};
|
|
||||||
bool ret;
|
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
|
//ffmpeg works with ours "fileops" helper
|
||||||
if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
|
if (av_open_input_stream(&format_context, stream->io, uri,
|
||||||
|
input_format, NULL) != 0) {
|
||||||
g_warning("Open failed\n");
|
g_warning("Open failed\n");
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (av_find_stream_info(format_context)<0) {
|
if (av_find_stream_info(format_context)<0) {
|
||||||
g_warning("Couldn't find stream info\n");
|
g_warning("Couldn't find stream info\n");
|
||||||
|
av_close_input_stream(format_context);
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_stream = ffmpeg_find_audio_stream(format_context);
|
audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||||
if (audio_stream == -1) {
|
if (audio_stream == -1) {
|
||||||
g_warning("No audio stream inside\n");
|
g_warning("No audio stream inside\n");
|
||||||
|
av_close_input_stream(format_context);
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,11 +221,15 @@ ffmpeg_helper(const char *uri, struct input_stream *input,
|
|||||||
|
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
g_warning("Unsupported audio codec\n");
|
g_warning("Unsupported audio codec\n");
|
||||||
|
av_close_input_stream(format_context);
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avcodec_open(codec_context, codec)<0) {
|
if (avcodec_open(codec_context, codec)<0) {
|
||||||
g_warning("Could not open codec\n");
|
g_warning("Could not open codec\n");
|
||||||
|
av_close_input_stream(format_context);
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +243,8 @@ ffmpeg_helper(const char *uri, struct input_stream *input,
|
|||||||
ret = true;
|
ret = true;
|
||||||
|
|
||||||
avcodec_close(codec_context);
|
avcodec_close(codec_context);
|
||||||
av_close_input_file(format_context);
|
av_close_input_stream(format_context);
|
||||||
|
mpd_ffmpeg_stream_close(stream);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -372,8 +389,10 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
|||||||
ctx.input = input;
|
ctx.input = input;
|
||||||
ctx.decoder = decoder;
|
ctx.decoder = decoder;
|
||||||
|
|
||||||
ffmpeg_helper(decoder_get_uri(decoder), input,
|
char *uri = decoder_get_uri(decoder);
|
||||||
|
ffmpeg_helper(uri, decoder, input,
|
||||||
ffmpeg_decode_internal, &ctx);
|
ffmpeg_decode_internal, &ctx);
|
||||||
|
g_free(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
||||||
@@ -401,12 +420,21 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
|
|||||||
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
|
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
|
||||||
|
|
||||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
|
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_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_ALBUM, "album");
|
||||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
|
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_GENRE, "genre");
|
||||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
|
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
|
#else
|
||||||
if (f->author[0])
|
if (f->author[0])
|
||||||
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
|
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
|
||||||
@@ -450,7 +478,7 @@ static struct tag *ffmpeg_tag(const char *file)
|
|||||||
ctx.decoder = NULL;
|
ctx.decoder = NULL;
|
||||||
ctx.tag = tag_new();
|
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) {
|
if (!ret) {
|
||||||
tag_free(ctx.tag);
|
tag_free(ctx.tag);
|
||||||
ctx.tag = NULL;
|
ctx.tag = NULL;
|
||||||
|
|||||||
@@ -209,14 +209,14 @@ mp3_fill_buffer(struct mp3_data *data)
|
|||||||
|
|
||||||
#ifdef HAVE_ID3TAG
|
#ifdef HAVE_ID3TAG
|
||||||
/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */
|
/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */
|
||||||
static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gain_info)
|
static bool
|
||||||
|
parse_rva2(struct id3_tag *tag, struct replay_gain_info *replay_gain_info)
|
||||||
{
|
{
|
||||||
struct id3_frame const * frame;
|
struct id3_frame const * frame;
|
||||||
|
|
||||||
id3_latin1_t const *id;
|
id3_latin1_t const *id;
|
||||||
id3_byte_t const *data;
|
id3_byte_t const *data;
|
||||||
id3_length_t length;
|
id3_length_t length;
|
||||||
int found;
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
CHANNEL_OTHER = 0x00,
|
CHANNEL_OTHER = 0x00,
|
||||||
@@ -230,18 +230,18 @@ static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gai
|
|||||||
CHANNEL_SUBWOOFER = 0x08
|
CHANNEL_SUBWOOFER = 0x08
|
||||||
};
|
};
|
||||||
|
|
||||||
found = 0;
|
|
||||||
|
|
||||||
/* relative volume adjustment information */
|
/* relative volume adjustment information */
|
||||||
|
|
||||||
frame = id3_tag_findframe(tag, "RVA2", 0);
|
frame = id3_tag_findframe(tag, "RVA2", 0);
|
||||||
if (!frame) return 0;
|
if (frame == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
id = id3_field_getlatin1(id3_frame_field(frame, 0));
|
id = id3_field_getlatin1(id3_frame_field(frame, 0));
|
||||||
data = id3_field_getbinarydata(id3_frame_field(frame, 1),
|
data = id3_field_getbinarydata(id3_frame_field(frame, 1),
|
||||||
&length);
|
&length);
|
||||||
|
|
||||||
if (!id || !data) return 0;
|
if (id == NULL || data == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "The 'identification' string is used to identify the
|
* "The 'identification' string is used to identify the
|
||||||
@@ -277,22 +277,21 @@ static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gai
|
|||||||
|
|
||||||
voladj_float = (double) voladj_fixed / 512;
|
voladj_float = (double) voladj_fixed / 512;
|
||||||
|
|
||||||
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = voladj_float;
|
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = voladj_float;
|
||||||
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = voladj_float;
|
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = voladj_float;
|
||||||
|
|
||||||
g_debug("parseRVA2: Relative Volume "
|
g_debug("parseRVA2: Relative Volume "
|
||||||
"%+.1f dB adjustment (%s)\n",
|
"%+.1f dB adjustment (%s)\n",
|
||||||
voladj_float, id);
|
voladj_float, id);
|
||||||
|
|
||||||
found = 1;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data += 4 + peak_bytes;
|
data += 4 + peak_bytes;
|
||||||
length -= 4 + peak_bytes;
|
length -= 4 + peak_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -425,7 +424,27 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
|
|||||||
/* This code is enabled when libid3tag is disabled. Instead
|
/* This code is enabled when libid3tag is disabled. Instead
|
||||||
of parsing the ID3 frame, it just skips it. */
|
of parsing the ID3 frame, it just skips it. */
|
||||||
|
|
||||||
mad_stream_skip(&data->stream, tagsize);
|
size_t count = data->stream.bufend - data->stream.this_frame;
|
||||||
|
|
||||||
|
if (tagsize <= count) {
|
||||||
|
mad_stream_skip(&data->stream, tagsize);
|
||||||
|
} else {
|
||||||
|
mad_stream_skip(&data->stream, count);
|
||||||
|
|
||||||
|
while (count < tagsize) {
|
||||||
|
size_t len = tagsize - count;
|
||||||
|
char ignored[1024];
|
||||||
|
if (len > sizeof(ignored))
|
||||||
|
len = sizeof(ignored);
|
||||||
|
|
||||||
|
len = decoder_read(data->decoder, data->input_stream,
|
||||||
|
ignored, len);
|
||||||
|
if (len == 0)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
count += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,16 +452,16 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
|
|||||||
/**
|
/**
|
||||||
* This function emulates libid3tag when it is disabled. Instead of
|
* This function emulates libid3tag when it is disabled. Instead of
|
||||||
* doing a real analyzation of the frame, it just checks whether the
|
* doing a real analyzation of the frame, it just checks whether the
|
||||||
* frame begins with the string "ID3". If so, it returns the full
|
* frame begins with the string "ID3". If so, it returns the length
|
||||||
* length.
|
* of the ID3 frame.
|
||||||
*/
|
*/
|
||||||
static signed long
|
static signed long
|
||||||
id3_tag_query(const void *p0, size_t length)
|
id3_tag_query(const void *p0, size_t length)
|
||||||
{
|
{
|
||||||
const char *p = p0;
|
const char *p = p0;
|
||||||
|
|
||||||
return length > 3 && memcmp(p, "ID3", 3) == 0
|
return length >= 10 && memcmp(p, "ID3", 3) == 0
|
||||||
? length
|
? (p[8] << 7) + p[9] + 10
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
#endif /* !HAVE_ID3TAG */
|
#endif /* !HAVE_ID3TAG */
|
||||||
|
|||||||
@@ -219,10 +219,12 @@ static struct tag *modTagDup(const char *file)
|
|||||||
ret->time = 0;
|
ret->time = 0;
|
||||||
|
|
||||||
path2 = g_strdup(file);
|
path2 = g_strdup(file);
|
||||||
title = g_strdup(Player_LoadTitle(path2));
|
title = Player_LoadTitle(path2);
|
||||||
g_free(path2);
|
g_free(path2);
|
||||||
if (title)
|
if (title) {
|
||||||
tag_add_item(ret, TAG_ITEM_TITLE, title);
|
tag_add_item(ret, TAG_ITEM_TITLE, title);
|
||||||
|
free(title);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "../decoder_api.h"
|
#include "../decoder_api.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "tag_table.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
@@ -339,6 +340,22 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
|
|||||||
mp4ff_close(mp4fh);
|
mp4ff_close(mp4fh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum tag_type
|
||||||
|
mp4ff_tag_name_parse(const char *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 *
|
static struct tag *
|
||||||
mp4_tag_dup(const char *file)
|
mp4_tag_dup(const char *file)
|
||||||
{
|
{
|
||||||
@@ -394,24 +411,9 @@ mp4_tag_dup(const char *file)
|
|||||||
|
|
||||||
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
|
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
|
||||||
|
|
||||||
if (0 == g_ascii_strcasecmp("artist", item)) {
|
enum tag_type type = mp4ff_tag_name_parse(item);
|
||||||
tag_add_item(ret, TAG_ITEM_ARTIST, value);
|
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||||
} else if (0 == g_ascii_strcasecmp("title", item)) {
|
tag_add_item(ret, type, value);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(item);
|
free(item);
|
||||||
free(value);
|
free(value);
|
||||||
|
|||||||
@@ -97,6 +97,13 @@ static long ogg_tell_cb(void *vdata)
|
|||||||
return (long)data->input_stream->offset;
|
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 *
|
static const char *
|
||||||
vorbis_comment_value(const char *comment, const char *needle)
|
vorbis_comment_value(const char *comment, const char *needle)
|
||||||
{
|
{
|
||||||
@@ -226,6 +233,9 @@ oggvorbis_seekable(struct decoder *decoder)
|
|||||||
bool seekable;
|
bool seekable;
|
||||||
|
|
||||||
uri = decoder_get_uri(decoder);
|
uri = decoder_get_uri(decoder);
|
||||||
|
if (uri == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
/* disable seeking on remote streams, because libvorbis seeks
|
/* disable seeking on remote streams, because libvorbis seeks
|
||||||
around like crazy, and due to being very expensive, this
|
around like crazy, and due to being very expensive, this
|
||||||
delays song playback my 10 or 20 seconds */
|
delays song playback my 10 or 20 seconds */
|
||||||
@@ -241,7 +251,6 @@ vorbis_stream_decode(struct decoder *decoder,
|
|||||||
struct input_stream *input_stream)
|
struct input_stream *input_stream)
|
||||||
{
|
{
|
||||||
OggVorbis_File vf;
|
OggVorbis_File vf;
|
||||||
ov_callbacks callbacks;
|
|
||||||
OggCallbackData data;
|
OggCallbackData data;
|
||||||
struct audio_format audio_format;
|
struct audio_format audio_format;
|
||||||
int current_section;
|
int current_section;
|
||||||
@@ -266,13 +275,9 @@ vorbis_stream_decode(struct decoder *decoder,
|
|||||||
data.input_stream = input_stream;
|
data.input_stream = input_stream;
|
||||||
data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
|
data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
|
||||||
|
|
||||||
callbacks.read_func = ogg_read_cb;
|
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0,
|
||||||
callbacks.seek_func = ogg_seek_cb;
|
vorbis_is_callbacks)) < 0) {
|
||||||
callbacks.close_func = ogg_close_cb;
|
|
||||||
callbacks.tell_func = ogg_tell_cb;
|
|
||||||
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
|
|
||||||
const char *error;
|
const char *error;
|
||||||
|
|
||||||
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
|
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,11 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
|
|||||||
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
|
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
|
||||||
decoder_seek_where(decoder);
|
decoder_seek_where(decoder);
|
||||||
|
|
||||||
|
#ifdef HAVE_WILDMIDI_SAMPLED_SEEK
|
||||||
WildMidi_SampledSeek(wm, &seek_where);
|
WildMidi_SampledSeek(wm, &seek_where);
|
||||||
|
#else
|
||||||
|
WildMidi_FastSeek(wm, &seek_where);
|
||||||
|
#endif
|
||||||
decoder_command_finished(decoder);
|
decoder_command_finished(decoder);
|
||||||
cmd = DECODE_COMMAND_NONE;
|
cmd = DECODE_COMMAND_NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "song_print.h"
|
#include "song_print.h"
|
||||||
|
|
||||||
static int
|
static void
|
||||||
dirvec_print(struct client *client, const struct dirvec *dv)
|
dirvec_print(struct client *client, const struct dirvec *dv)
|
||||||
{
|
{
|
||||||
size_t i;
|
size_t i;
|
||||||
@@ -30,15 +30,11 @@ dirvec_print(struct client *client, const struct dirvec *dv)
|
|||||||
for (i = 0; i < dv->nr; ++i)
|
for (i = 0; i < dv->nr; ++i)
|
||||||
client_printf(client, DIRECTORY_DIR "%s\n",
|
client_printf(client, DIRECTORY_DIR "%s\n",
|
||||||
directory_get_path(dv->base[i]));
|
directory_get_path(dv->base[i]));
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
void
|
||||||
directory_print(struct client *client, const struct directory *directory)
|
directory_print(struct client *client, const struct directory *directory)
|
||||||
{
|
{
|
||||||
dirvec_print(client, &directory->children);
|
dirvec_print(client, &directory->children);
|
||||||
songvec_print(client, &directory->songs);
|
songvec_print(client, &directory->songs);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
struct client;
|
struct client;
|
||||||
struct directory;
|
struct directory;
|
||||||
|
|
||||||
int
|
void
|
||||||
directory_print(struct client *client, const struct directory *directory);
|
directory_print(struct client *client, const struct directory *directory);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ icy_server_metadata_page(const struct tag *tag, ...)
|
|||||||
gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
|
gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata -
|
||||||
// "StreamTitle='';StreamUrl='';"
|
// "StreamTitle='';StreamUrl='';"
|
||||||
// = 4081 - 28
|
// = 4081 - 28
|
||||||
|
stream_title[0] = '\0';
|
||||||
|
|
||||||
last_item = -1;
|
last_item = -1;
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,6 @@ input_curl_select(struct input_curl *c)
|
|||||||
fd_set rfds, wfds, efds;
|
fd_set rfds, wfds, efds;
|
||||||
int max_fd, ret;
|
int max_fd, ret;
|
||||||
CURLMcode mcode;
|
CURLMcode mcode;
|
||||||
/* XXX hard coded timeout value.. */
|
|
||||||
struct timeval timeout = {
|
struct timeval timeout = {
|
||||||
.tv_sec = 1,
|
.tv_sec = 1,
|
||||||
.tv_usec = 0,
|
.tv_usec = 0,
|
||||||
@@ -260,7 +259,23 @@ input_curl_select(struct input_curl *c)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(max_fd >= 0);
|
#if LIBCURL_VERSION_NUM >= 0x070f04
|
||||||
|
long timeout2;
|
||||||
|
mcode = curl_multi_timeout(c->multi, &timeout2);
|
||||||
|
if (mcode != CURLM_OK) {
|
||||||
|
g_warning("curl_multi_timeout() failed: %s\n",
|
||||||
|
curl_multi_strerror(mcode));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout2 >= 0) {
|
||||||
|
if (timeout2 > 10000)
|
||||||
|
timeout2 = 10000;
|
||||||
|
|
||||||
|
timeout.tv_sec = timeout2 / 1000;
|
||||||
|
timeout.tv_usec = (timeout2 % 1000) * 1000;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout);
|
ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
|||||||
@@ -49,10 +49,13 @@ input_mms_open(struct input_stream *is, const char *url)
|
|||||||
m = g_new(struct input_mms, 1);
|
m = g_new(struct input_mms, 1);
|
||||||
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
|
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
|
||||||
if (m->mms == NULL) {
|
if (m->mms == NULL) {
|
||||||
|
g_free(m);
|
||||||
g_warning("mmsx_connect() failed");
|
g_warning("mmsx_connect() failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m->eof = false;
|
||||||
|
|
||||||
/* XX is this correct? at least this selects the ffmpeg
|
/* XX is this correct? at least this selects the ffmpeg
|
||||||
decoder, which seems to work fine*/
|
decoder, which seems to work fine*/
|
||||||
is->mime = g_strdup("audio/x-ms-wma");
|
is->mime = g_strdup("audio/x-ms-wma");
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "input/rewind_input_plugin.h"
|
#include "input/rewind_input_plugin.h"
|
||||||
#include "input/curl_input_plugin.h"
|
#include "input/curl_input_plugin.h"
|
||||||
|
#ifdef ENABLE_MMS
|
||||||
|
#include "input/mms_input_plugin.h"
|
||||||
|
#endif
|
||||||
#include "input_plugin.h"
|
#include "input_plugin.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
|
|
||||||
@@ -86,10 +89,11 @@ copy_attributes(struct input_stream *dest)
|
|||||||
dest->size = src->size;
|
dest->size = src->size;
|
||||||
dest->offset = src->offset;
|
dest->offset = src->offset;
|
||||||
|
|
||||||
if (dest->mime == NULL && src->mime != NULL)
|
if (src->mime != NULL) {
|
||||||
/* this is set only once, and the duplicated pointer
|
if (dest->mime != NULL)
|
||||||
is freed by input_stream_close() */
|
g_free(dest->mime);
|
||||||
dest->mime = g_strdup(src->mime);
|
dest->mime = g_strdup(src->mime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -219,7 +223,11 @@ input_rewind_open(struct input_stream *is)
|
|||||||
assert(is != NULL);
|
assert(is != NULL);
|
||||||
assert(is->offset == 0);
|
assert(is->offset == 0);
|
||||||
|
|
||||||
if (is->plugin != &input_plugin_curl)
|
if (is->plugin != &input_plugin_curl
|
||||||
|
#ifdef ENABLE_MMS
|
||||||
|
&& is->plugin != &input_plugin_mms
|
||||||
|
#endif
|
||||||
|
)
|
||||||
/* due to limitations in the input_plugin API, we only
|
/* due to limitations in the input_plugin API, we only
|
||||||
(explicitly) support the CURL input plugin */
|
(explicitly) support the CURL input plugin */
|
||||||
return;
|
return;
|
||||||
@@ -229,7 +237,8 @@ input_rewind_open(struct input_stream *is)
|
|||||||
|
|
||||||
/* move the CURL input stream to c->input */
|
/* move the CURL input stream to c->input */
|
||||||
c->input = *is;
|
c->input = *is;
|
||||||
input_curl_reinit(&c->input);
|
if (is->plugin == &input_plugin_curl)
|
||||||
|
input_curl_reinit(&c->input);
|
||||||
|
|
||||||
/* convert the existing input_stream pointer to a "rewind"
|
/* convert the existing input_stream pointer to a "rewind"
|
||||||
input stream */
|
input stream */
|
||||||
|
|||||||
@@ -407,7 +407,13 @@ static int get_remote_uid(int fd)
|
|||||||
|
|
||||||
return cred.uid;
|
return cred.uid;
|
||||||
#else
|
#else
|
||||||
(void)fd;
|
#ifdef HAVE_GETPEEREID
|
||||||
|
uid_t euid;
|
||||||
|
gid_t egid;
|
||||||
|
|
||||||
|
if (getpeereid(fd, &euid, &egid) == 0)
|
||||||
|
return euid;
|
||||||
|
#endif
|
||||||
return -1;
|
return -1;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ locate_parse_type(const char *str)
|
|||||||
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
|
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
|
||||||
return LOCATE_TAG_ANY_TYPE;
|
return LOCATE_TAG_ANY_TYPE;
|
||||||
|
|
||||||
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
|
i = tag_name_parse_i(str);
|
||||||
if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
|
if (i != TAG_NUM_OF_ITEM_TYPES)
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,3 +48,10 @@ void notify_signal(struct notify *notify)
|
|||||||
g_cond_signal(notify->cond);
|
g_cond_signal(notify->cond);
|
||||||
g_mutex_unlock(notify->mutex);
|
g_mutex_unlock(notify->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void notify_clear(struct notify *notify)
|
||||||
|
{
|
||||||
|
g_mutex_lock(notify->mutex);
|
||||||
|
notify->pending = false;
|
||||||
|
g_mutex_unlock(notify->mutex);
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,4 +45,9 @@ void notify_wait(struct notify *notify);
|
|||||||
*/
|
*/
|
||||||
void notify_signal(struct notify *notify);
|
void notify_signal(struct notify *notify);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears a pending notification.
|
||||||
|
*/
|
||||||
|
void notify_clear(struct notify *notify);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(encoder_name, "vorbis") == 0)
|
if (strcmp(encoder_name, "vorbis") == 0)
|
||||||
httpd->content_type = "application/x-ogg";
|
httpd->content_type = "audio/ogg";
|
||||||
else if (strcmp(encoder_name, "lame") == 0)
|
else if (strcmp(encoder_name, "lame") == 0)
|
||||||
httpd->content_type = "audio/mpeg";
|
httpd->content_type = "audio/mpeg";
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -268,6 +268,16 @@ static gpointer audio_output_task(gpointer arg)
|
|||||||
ao->chunk = NULL;
|
ao->chunk = NULL;
|
||||||
if (ao->open)
|
if (ao->open)
|
||||||
ao_plugin_cancel(ao->plugin, ao->data);
|
ao_plugin_cancel(ao->plugin, ao->data);
|
||||||
|
|
||||||
|
/* we must clear the notification now, because
|
||||||
|
the notify_wait() call below must wait
|
||||||
|
until audio_output_all_cancel() has cleared
|
||||||
|
the pipe; if another notification happens
|
||||||
|
to be still pending, we get a race
|
||||||
|
condition with a crash or an assertion
|
||||||
|
failure */
|
||||||
|
notify_clear(&ao->notify);
|
||||||
|
|
||||||
ao_command_finished(ao);
|
ao_command_finished(ao);
|
||||||
|
|
||||||
/* the player thread will now clear our music
|
/* the player thread will now clear our music
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
* would put too much stress on the allocator.
|
* would put too much stress on the allocator.
|
||||||
*/
|
*/
|
||||||
struct pcm_buffer {
|
struct pcm_buffer {
|
||||||
char *buffer;
|
void *buffer;
|
||||||
|
|
||||||
size_t size;
|
size_t size;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include "playlist_internal.h"
|
#include "playlist_internal.h"
|
||||||
#include "player_control.h"
|
#include "player_control.h"
|
||||||
|
#include "idle.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
@@ -156,6 +157,8 @@ nextSongInPlaylist(struct playlist *playlist)
|
|||||||
if (next_order < 0) {
|
if (next_order < 0) {
|
||||||
/* cancel single */
|
/* cancel single */
|
||||||
playlist->queue.single = false;
|
playlist->queue.single = false;
|
||||||
|
idle_add(IDLE_OPTIONS);
|
||||||
|
|
||||||
/* no song after this one: stop playback */
|
/* no song after this one: stop playback */
|
||||||
stopPlaylist(playlist);
|
stopPlaylist(playlist);
|
||||||
|
|
||||||
|
|||||||
50
src/tag.c
50
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];
|
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)
|
static size_t items_size(const struct tag *tag)
|
||||||
{
|
{
|
||||||
return tag->num_items * sizeof(struct tag_item *);
|
return tag->num_items * sizeof(struct tag_item *);
|
||||||
@@ -76,7 +106,7 @@ void tag_lib_init(void)
|
|||||||
char *temp;
|
char *temp;
|
||||||
char *s;
|
char *s;
|
||||||
char *c;
|
char *c;
|
||||||
int i;
|
enum tag_type type;
|
||||||
|
|
||||||
/* parse the "metadata_to_use" config parameter below */
|
/* parse the "metadata_to_use" config parameter below */
|
||||||
|
|
||||||
@@ -98,16 +128,18 @@ void tag_lib_init(void)
|
|||||||
if (*s == '\0')
|
if (*s == '\0')
|
||||||
quit = 1;
|
quit = 1;
|
||||||
*s = '\0';
|
*s = '\0';
|
||||||
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
|
|
||||||
if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
|
c = g_strstrip(c);
|
||||||
ignore_tag_items[i] = false;
|
if (*c == 0)
|
||||||
break;
|
continue;
|
||||||
}
|
|
||||||
}
|
type = tag_name_parse_i(c);
|
||||||
if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
|
if (type == TAG_NUM_OF_ITEM_TYPES)
|
||||||
g_error("error parsing metadata item \"%s\"",
|
g_error("error parsing metadata item \"%s\"",
|
||||||
c);
|
c);
|
||||||
}
|
|
||||||
|
ignore_tag_items[type] = false;
|
||||||
|
|
||||||
s++;
|
s++;
|
||||||
c = s;
|
c = s;
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/tag.h
16
src/tag.h
@@ -93,6 +93,22 @@ struct tag {
|
|||||||
unsigned num_items;
|
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.
|
* Creates an empty #tag.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,12 +19,47 @@
|
|||||||
|
|
||||||
#include "tag_ape.h"
|
#include "tag_ape.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
|
#include "tag_table.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
|
||||||
|
[TAG_ITEM_ALBUM_ARTIST] = "album artist",
|
||||||
|
[TAG_ITEM_DATE] = "year"
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum tag_type
|
||||||
|
tag_ape_name_parse(const char *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 *
|
||||||
|
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;
|
||||||
|
|
||||||
|
enum tag_type type = tag_ape_name_parse(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;
|
||||||
|
}
|
||||||
|
|
||||||
struct tag *
|
struct tag *
|
||||||
tag_ape_load(const char *file)
|
tag_ape_load(const char *file)
|
||||||
{
|
{
|
||||||
@@ -36,7 +71,6 @@ tag_ape_load(const char *file)
|
|||||||
size_t tagLen;
|
size_t tagLen;
|
||||||
size_t size;
|
size_t size;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int i;
|
|
||||||
char *key;
|
char *key;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -48,26 +82,6 @@ tag_ape_load(const char *file)
|
|||||||
unsigned char reserved[8];
|
unsigned char reserved[8];
|
||||||
} footer;
|
} 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");
|
fp = fopen(file, "r");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -127,17 +141,8 @@ tag_ape_load(const char *file)
|
|||||||
if (tagLen < size)
|
if (tagLen < size)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
/* we only care about utf-8 text tags */
|
ret = tag_ape_import_item(ret, flags, key, p, size);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p += size;
|
p += size;
|
||||||
tagLen -= size;
|
tagLen -= size;
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/tag_table.h
Normal file
43
src/tag_table.h
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user