Commit 8c743fd3 authored by Alexey Morsov's avatar Alexey Morsov

Merge commit 'release-0.15.13' into alt

parents 2835998a b552e9a1
...@@ -142,6 +142,7 @@ mpd_headers = \ ...@@ -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 \
......
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)
* decoders:
- mad: fix crash when seeking at end of song
- mpcdec: fix negative shift on fixed-point samples
- mpcdec: fix replay gain formula with v8
* playlist: fix single+repeat in random mode
* player: postpone song tags during cross-fade
ver 0.15.8 (2010/01/17) ver 0.15.8 (2010/01/17)
* input: * input:
- curl: allow rewinding with Icy-Metadata - curl: allow rewinding with Icy-Metadata
......
AC_PREREQ(2.60) AC_PREREQ(2.60)
AC_INIT(mpd, 0.15.8, 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, ...@@ -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,18 +914,10 @@ fi ...@@ -913,18 +914,10 @@ 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 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 # prior to ffmpeg svn12865, you had to specify include files
# without path prefix # without path prefix
old_CPPCFLAGS=$CPPFLAGS old_CPPCFLAGS=$CPPFLAGS
...@@ -957,6 +950,10 @@ if test x$enable_wildmidi = xyes; then ...@@ -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
......
...@@ -173,7 +173,6 @@ cue_tag_file( FILE* fp, ...@@ -173,7 +173,6 @@ cue_tag_file( FILE* fp,
{ {
struct tag* cd_tag = NULL; struct tag* cd_tag = NULL;
struct tag* track_tag = NULL; struct tag* track_tag = NULL;
struct tag* merge_tag = NULL;
struct Cd* cd = NULL; struct Cd* cd = NULL;
if (tnum > 256) if (tnum > 256)
...@@ -199,26 +198,7 @@ cue_tag_file( FILE* fp, ...@@ -199,26 +198,7 @@ cue_tag_file( FILE* fp,
cd_delete(cd); cd_delete(cd);
} }
if ((cd_tag != NULL) && (track_tag != NULL)) return tag_merge_replace(cd_tag, track_tag);
{
merge_tag = tag_merge(cd_tag, track_tag);
tag_free(cd_tag);
tag_free(track_tag);
return merge_tag;
}
else if (cd_tag != NULL)
{
return cd_tag;
}
else if (track_tag != NULL)
{
return track_tag;
}
else
return NULL;
} }
struct tag* struct tag*
......
...@@ -53,48 +53,27 @@ struct ffmpeg_context { ...@@ -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;
* Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a unsigned char buffer[8192];
* 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 {
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) static int
mpd_ffmpeg_stream_read(void *opaque, uint8_t *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, 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) ...@@ -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) ...@@ -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,
};
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;
char *base = g_path_get_basename(uri); AVProbeData avpd = {
.buf = buffer,
.buf_size = nbytes,
.filename = uri,
};
const char *suffix = strrchr(base, '.'); AVInputFormat *format = av_probe_input_format(&avpd, true);
if (suffix != NULL && suffix[1] != 0) g_free(buffer);
g_strlcat(stream->url, suffix, sizeof(stream->url));
g_free(base); 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, ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 ...@@ -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 ...@@ -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, ...@@ -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. */
size_t count = data->stream.bufend - data->stream.this_frame;
if (tagsize <= count) {
mad_stream_skip(&data->stream, tagsize); 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, ...@@ -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 */
...@@ -1207,10 +1226,6 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) ...@@ -1207,10 +1226,6 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
if (replay_gain_info) if (replay_gain_info)
replay_gain_info_free(replay_gain_info); replay_gain_info_free(replay_gain_info);
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK &&
data.mute_frame == MUTEFRAME_SEEK)
decoder_command_finished(decoder);
mp3_data_finish(&data); mp3_data_finish(&data);
} }
......
...@@ -219,10 +219,12 @@ static struct tag *modTagDup(const char *file) ...@@ -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 @@ ...@@ -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) ...@@ -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) ...@@ -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);
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include <mpcdec/mpcdec.h> #include <mpcdec/mpcdec.h>
#else #else
#include <mpc/mpcdec.h> #include <mpc/mpcdec.h>
#include <math.h>
#endif #endif
#include <glib.h> #include <glib.h>
...@@ -103,7 +104,7 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample) ...@@ -103,7 +104,7 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT; const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
if (shift < 0) if (shift < 0)
val = sample << -shift; val = sample >> -shift;
else else
val = sample << shift; val = sample << shift;
#else #else
...@@ -209,10 +210,17 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) ...@@ -209,10 +210,17 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
} }
replay_gain_info = replay_gain_info_new(); replay_gain_info = replay_gain_info_new();
#ifdef MPC_IS_OLD_API
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0;
#else
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
#endif
decoder_initialized(mpd_decoder, &audio_format, decoder_initialized(mpd_decoder, &audio_format,
is->seekable, is->seekable,
......
...@@ -97,6 +97,13 @@ static long ogg_tell_cb(void *vdata) ...@@ -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) ...@@ -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, ...@@ -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, ...@@ -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) ...@@ -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 @@ ...@@ -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) ...@@ -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 @@ ...@@ -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, ...) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 @@ ...@@ -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) ...@@ -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) ...@@ -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,6 +237,7 @@ input_rewind_open(struct input_stream *is) ...@@ -229,6 +237,7 @@ 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;
if (is->plugin == &input_plugin_curl)
input_curl_reinit(&c->input); input_curl_reinit(&c->input);
/* convert the existing input_stream pointer to a "rewind" /* convert the existing input_stream pointer to a "rewind"
......
...@@ -407,7 +407,13 @@ static int get_remote_uid(int fd) ...@@ -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,8 +42,8 @@ locate_parse_type(const char *str) ...@@ -42,8 +42,8 @@ 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) ...@@ -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); ...@@ -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, ...@@ -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) ...@@ -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 @@ ...@@ -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;
}; };
......
...@@ -90,6 +90,13 @@ struct player { ...@@ -90,6 +90,13 @@ struct player {
unsigned cross_fade_chunks; unsigned cross_fade_chunks;
/** /**
* The tag of the "next" song during cross-fade. It is
* postponed, and sent to the output thread when the new song
* really begins.
*/
struct tag *cross_fade_tag;
/**
* The current audio format for the audio outputs. * The current audio format for the audio outputs.
*/ */
struct audio_format play_audio_format; struct audio_format play_audio_format;
...@@ -518,6 +525,14 @@ play_next_chunk(struct player *player) ...@@ -518,6 +525,14 @@ play_next_chunk(struct player *player)
chunk = music_pipe_shift(player->pipe); chunk = music_pipe_shift(player->pipe);
assert(chunk != NULL); assert(chunk != NULL);
/* don't send the tags of the new song (which
is being faded in) yet; postpone it until
the current song is faded out */
player->cross_fade_tag =
tag_merge_replace(player->cross_fade_tag,
other_chunk->tag);
other_chunk->tag = NULL;
cross_fade_apply(chunk, other_chunk, cross_fade_apply(chunk, other_chunk,
&dc.out_audio_format, &dc.out_audio_format,
cross_fade_position, cross_fade_position,
...@@ -544,6 +559,14 @@ play_next_chunk(struct player *player) ...@@ -544,6 +559,14 @@ play_next_chunk(struct player *player)
assert(chunk != NULL); assert(chunk != NULL);
/* insert the postponed tag if cross-fading is finished */
if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) {
chunk->tag = tag_merge_replace(chunk->tag,
player->cross_fade_tag);
player->cross_fade_tag = NULL;
}
/* play the current chunk */ /* play the current chunk */
success = play_chunk(player->song, chunk, &player->play_audio_format, success = play_chunk(player->song, chunk, &player->play_audio_format,
...@@ -608,6 +631,7 @@ static void do_play(void) ...@@ -608,6 +631,7 @@ static void do_play(void)
.xfade = XFADE_UNKNOWN, .xfade = XFADE_UNKNOWN,
.cross_fading = false, .cross_fading = false,
.cross_fade_chunks = 0, .cross_fade_chunks = 0,
.cross_fade_tag = NULL,
.size_to_time = 0.0, .size_to_time = 0.0,
}; };
...@@ -754,6 +778,9 @@ static void do_play(void) ...@@ -754,6 +778,9 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer); music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe); music_pipe_free(player.pipe);
if (player.cross_fade_tag != NULL)
tag_free(player.cross_fade_tag);
pc.state = PLAYER_STATE_STOP; pc.state = PLAYER_STATE_STOP;
event_pipe_emit(PIPE_EVENT_PLAYLIST); event_pipe_emit(PIPE_EVENT_PLAYLIST);
} }
......
...@@ -140,7 +140,8 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) ...@@ -140,7 +140,8 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
? queue_next_order(&playlist->queue, playlist->current) ? queue_next_order(&playlist->queue, playlist->current)
: 0; : 0;
if (next_order == 0 && playlist->queue.random) { if (next_order == 0 && playlist->queue.random &&
!playlist->queue.single) {
/* shuffle the song order again, so we get a different /* shuffle the song order again, so we get a different
order each time the playlist is played order each time the playlist is played
completely */ completely */
......
...@@ -24,6 +24,7 @@ ...@@ -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) ...@@ -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);
......
...@@ -64,6 +64,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { ...@@ -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) ...@@ -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) ...@@ -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;
} }
...@@ -247,6 +279,22 @@ tag_merge(const struct tag *base, const struct tag *add) ...@@ -247,6 +279,22 @@ tag_merge(const struct tag *base, const struct tag *add)
return ret; return ret;
} }
struct tag *
tag_merge_replace(struct tag *base, struct tag *add)
{
if (add == NULL)
return base;
if (base == NULL)
return add;
struct tag *tag = tag_merge(base, add);
tag_free(base);
tag_free(add);
return tag;
}
const char * const char *
tag_get_value(const struct tag *tag, enum tag_type type) tag_get_value(const struct tag *tag, enum tag_type type)
{ {
......
...@@ -94,6 +94,22 @@ struct tag { ...@@ -94,6 +94,22 @@ struct tag {
}; };
/** /**
* 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.
*/ */
struct tag *tag_new(void); struct tag *tag_new(void);
...@@ -166,6 +182,15 @@ struct tag * ...@@ -166,6 +182,15 @@ struct tag *
tag_merge(const struct tag *base, const struct tag *add); tag_merge(const struct tag *base, const struct tag *add);
/** /**
* Merges the data from two tags. Any of the two may be NULL. Both
* are freed by this function.
*
* @return a newly allocated tag, which must be freed with tag_free()
*/
struct tag *
tag_merge_replace(struct tag *base, struct tag *add);
/**
* Returns true if the tag contains no items. This ignores the "time" * Returns true if the tag contains no items. This ignores the "time"
* attribute. * attribute.
*/ */
......
...@@ -19,12 +19,47 @@ ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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;
} }
......
/*
* 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment