Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
d612e5e0ab | ||
![]() |
73ba4ea3da | ||
![]() |
cbfaa4a266 | ||
![]() |
2e72a9b262 | ||
![]() |
96033e4b4e | ||
![]() |
9134169e37 | ||
![]() |
35c5a371ea | ||
![]() |
728c66e7e3 | ||
![]() |
760569fc66 | ||
![]() |
2579a2f924 | ||
![]() |
a7664b98ba | ||
![]() |
1b441837f1 | ||
![]() |
e43bf52cbb | ||
![]() |
8ba08edd0e | ||
![]() |
90d16af66a | ||
![]() |
777bd7c1e1 | ||
![]() |
959f94b06c | ||
![]() |
4419e5b90d | ||
![]() |
c88f95a2ea | ||
![]() |
c7d099c757 | ||
![]() |
d38c09051c | ||
![]() |
b1cc760aa5 | ||
![]() |
d66c055fec | ||
![]() |
915d1d0738 | ||
![]() |
8a6d448aaf | ||
![]() |
a1939f3966 | ||
![]() |
9179f108a5 | ||
![]() |
3411f6cffd | ||
![]() |
6c0f50efb5 | ||
![]() |
2234d491b7 | ||
![]() |
81aa58efa8 | ||
![]() |
83aac2a057 | ||
![]() |
f01d7d230b | ||
![]() |
8f7bc70bf5 | ||
![]() |
5a354a1ed4 | ||
![]() |
f4b707b4ca | ||
![]() |
cd69fee0a4 | ||
![]() |
23e46b38ca | ||
![]() |
7162fe85ce | ||
![]() |
16123f1b8e | ||
![]() |
21fdf47b56 | ||
![]() |
d1aee3ae74 |
Makefile.amNEWSconfigure.ac
src
archive
archive_api.hcue
dbUtils.cdecoder
_flac_common.cffmpeg_plugin.cflac_plugin.cmad_plugin.cmikmod_plugin.cmp4ff_plugin.cmpcdec_plugin.coggflac_plugin.cvorbis_plugin.cwavpack_plugin.c
decoder_api.cdecoder_thread.cdirectory_print.cdirectory_print.hinput
archive_input_plugin.ccurl_input_plugin.ccurl_input_plugin.hfile_input_plugin.cmms_input_plugin.crewind_input_plugin.crewind_input_plugin.h
input_stream.clisten.clocate.cmapper.cmapper.hmixer_control.cpcm_buffer.hplayer_thread.cplaylist.cplaylist_control.cplaylist_save.cqueue.cstored_playlist.ctag.ctag.htag_ape.ctag_id3.ctag_table.htest
@@ -75,6 +75,7 @@ mpd_headers = \
|
||||
src/input_stream.h \
|
||||
src/input/file_input_plugin.h \
|
||||
src/input/curl_input_plugin.h \
|
||||
src/input/rewind_input_plugin.h \
|
||||
src/input/lastfm_input_plugin.h \
|
||||
src/input/mms_input_plugin.h \
|
||||
src/icy_server.h \
|
||||
@@ -141,6 +142,7 @@ mpd_headers = \
|
||||
src/tag.h \
|
||||
src/tag_internal.h \
|
||||
src/tag_pool.h \
|
||||
src/tag_table.h \
|
||||
src/tag_ape.h \
|
||||
src/tag_id3.h \
|
||||
src/tag_print.h \
|
||||
@@ -467,7 +469,9 @@ INPUT_SRC = \
|
||||
src/input/file_input_plugin.c
|
||||
|
||||
if HAVE_CURL
|
||||
INPUT_SRC += src/input/curl_input_plugin.c src/icy_metadata.c
|
||||
INPUT_SRC += src/input/curl_input_plugin.c \
|
||||
src/input/rewind_input_plugin.c \
|
||||
src/icy_metadata.c
|
||||
endif
|
||||
|
||||
if ENABLE_LASTFM
|
||||
@@ -637,6 +641,7 @@ test_run_decoder_SOURCES = test/run_decoder.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/replay_gain.c \
|
||||
src/uri.c \
|
||||
src/timer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(TAG_SRC) \
|
||||
@@ -656,6 +661,7 @@ test_read_tags_SOURCES = test/read_tags.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/replay_gain.c \
|
||||
src/uri.c \
|
||||
src/timer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(TAG_SRC) \
|
||||
@@ -748,6 +754,7 @@ DOCBOOK_HTML =
|
||||
endif
|
||||
|
||||
doc/api/html/index.html: doc/doxygen.conf
|
||||
@mkdir -p $(@D)
|
||||
$(DOXYGEN) $<
|
||||
|
||||
all-local: $(DOCBOOK_HTML) doc/api/html/index.html
|
||||
|
63
NEWS
63
NEWS
@@ -1,3 +1,66 @@
|
||||
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)
|
||||
* input:
|
||||
- curl: allow rewinding with Icy-Metadata
|
||||
* decoders:
|
||||
- ffmpeg, flac, vorbis: added more flac/vorbis MIME types
|
||||
- ffmpeg: enabled libavformat's file name extension detection
|
||||
* dbUtils: return empty tag value only if no value was found
|
||||
* decoder_thread: fix CUE track playback
|
||||
* queue: don't repeat current song in consume mode
|
||||
|
||||
|
||||
ver 0.15.7 (2009/12/27)
|
||||
* archive:
|
||||
- close archive when stream is closed
|
||||
- iso, zip: fixed memory leak in destructor
|
||||
* input:
|
||||
- file: don't fall back to parent directory
|
||||
- archive: fixed memory leak in error handler
|
||||
* tags:
|
||||
- id3: fix ID3v1 charset conversion
|
||||
* decoders:
|
||||
- eliminate jitter after seek failure
|
||||
- ffmpeg: don't try to force stereo
|
||||
- wavpack: allow fine-grained seeking
|
||||
* mixer: explicitly close all mixers on shutdown
|
||||
* mapper: fix memory leak when playlist_directory is not set
|
||||
* mapper: apply filesystem_charset to playlists
|
||||
* command: verify playlist name in the "rm" command
|
||||
* database: return multiple tag values per song
|
||||
|
||||
|
||||
ver 0.15.6 (2009/11/18)
|
||||
* input:
|
||||
- lastfm: fixed variable name in GLib<2.16 code path
|
||||
|
14
configure.ac
14
configure.ac
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ(2.60)
|
||||
AC_INIT(mpd, 0.15.6, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.15.11, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_CONFIG_SRCDIR([src/main.c])
|
||||
AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
|
||||
AM_CONFIG_HEADER(config.h)
|
||||
@@ -177,6 +177,7 @@ AC_ARG_ENABLE(un,
|
||||
if test x$enable_un = xyes; then
|
||||
AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled])
|
||||
STRUCT_UCRED
|
||||
AC_CHECK_FUNCS(getpeereid)
|
||||
fi
|
||||
|
||||
|
||||
@@ -913,17 +914,9 @@ fi
|
||||
|
||||
AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
|
||||
|
||||
MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat libavcodec libavutil],
|
||||
MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat >= 52 libavcodec >= 51 libavutil >= 49],
|
||||
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
|
||||
|
||||
if test x$enable_ffmpeg = xyes; then
|
||||
old_LIBS=$LIBS
|
||||
LIBS="$LIBS $FFMPEG_LIBS"
|
||||
AC_CHECK_LIB(avcodec, avcodec_decode_audio2,,
|
||||
enable_ffmpeg=no)
|
||||
LIBS=$old_LIBS
|
||||
fi
|
||||
|
||||
if test x$enable_ffmpeg = xyes; then
|
||||
# prior to ffmpeg svn12865, you had to specify include files
|
||||
# without path prefix
|
||||
@@ -1139,7 +1132,6 @@ then
|
||||
MPD_CHECK_FLAG([-Wextra])
|
||||
MPD_CHECK_FLAG([-Wno-deprecated-declarations])
|
||||
MPD_CHECK_FLAG([-Wmissing-prototypes])
|
||||
MPD_CHECK_FLAG([-Wdeclaration-after-statement])
|
||||
MPD_CHECK_FLAG([-Wshadow])
|
||||
MPD_CHECK_FLAG([-Wpointer-arith])
|
||||
MPD_CHECK_FLAG([-Wstrict-prototypes])
|
||||
|
@@ -140,8 +140,8 @@ static void
|
||||
bz2_close(struct archive_file *file)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) file;
|
||||
if (context->name)
|
||||
g_free(context->name);
|
||||
|
||||
g_free(context->name);
|
||||
|
||||
input_stream_close(&context->istream);
|
||||
g_free(context);
|
||||
@@ -173,6 +173,8 @@ bz2_is_close(struct input_stream *is)
|
||||
bz2_context *context = (bz2_context *) is->data;
|
||||
bz2_destroy(context);
|
||||
is->data = NULL;
|
||||
|
||||
bz2_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@@ -132,7 +132,8 @@ iso_close(struct archive_file *file)
|
||||
}
|
||||
//close archive
|
||||
iso9660_close(context->iso);
|
||||
context->iso = NULL;
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
@@ -165,6 +166,8 @@ iso_is_close(struct input_stream *is)
|
||||
{
|
||||
iso_context *context = (iso_context *) is->data;
|
||||
g_free(context->statbuf);
|
||||
|
||||
iso_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -99,7 +99,8 @@ zip_close(struct archive_file *file)
|
||||
}
|
||||
//close archive
|
||||
zzip_dir_close (context->dir);
|
||||
context->dir = NULL;
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
@@ -133,6 +134,8 @@ zip_is_close(struct input_stream *is)
|
||||
{
|
||||
zip_context *context = (zip_context *) is->data;
|
||||
zzip_file_close (context->file);
|
||||
|
||||
zip_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
static size_t
|
||||
|
@@ -73,6 +73,9 @@ struct archive_plugin {
|
||||
/**
|
||||
* Opens an input_stream of a file within the archive.
|
||||
*
|
||||
* If this function succeeds, then the #input_stream "owns"
|
||||
* the archive file and will automatically close it.
|
||||
*
|
||||
* @param path the path within the archive
|
||||
*/
|
||||
bool (*open_stream)(struct archive_file *, struct input_stream *is,
|
||||
|
@@ -173,7 +173,6 @@ cue_tag_file( FILE* fp,
|
||||
{
|
||||
struct tag* cd_tag = NULL;
|
||||
struct tag* track_tag = NULL;
|
||||
struct tag* merge_tag = NULL;
|
||||
struct Cd* cd = NULL;
|
||||
|
||||
if (tnum > 256)
|
||||
@@ -199,26 +198,7 @@ cue_tag_file( FILE* fp,
|
||||
cd_delete(cd);
|
||||
}
|
||||
|
||||
if ((cd_tag != NULL) && (track_tag != NULL))
|
||||
{
|
||||
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;
|
||||
return tag_merge_replace(cd_tag, track_tag);
|
||||
}
|
||||
|
||||
struct tag*
|
||||
|
@@ -234,6 +234,7 @@ visitTag(struct client *client, struct strset *set,
|
||||
struct song *song, enum tag_type tagType)
|
||||
{
|
||||
struct tag *tag = song->tag;
|
||||
bool found = false;
|
||||
|
||||
if (tagType == LOCATE_TAG_FILE_TYPE) {
|
||||
song_print_url(client, song);
|
||||
@@ -246,11 +247,12 @@ visitTag(struct client *client, struct strset *set,
|
||||
for (unsigned i = 0; i < tag->num_items; i++) {
|
||||
if (tag->items[i]->type == tagType) {
|
||||
strset_add(set, tag->items[i]->value);
|
||||
return;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
strset_add(set, "");
|
||||
if (!found)
|
||||
strset_add(set, "");
|
||||
}
|
||||
|
||||
struct list_tags_data {
|
||||
|
@@ -419,9 +419,7 @@ flac_vtrack_tnum(const char* fname)
|
||||
return 0;
|
||||
|
||||
// copy ascii tracknumber to int
|
||||
char vtrack[4];
|
||||
g_strlcpy(vtrack, ++ptr, 4);
|
||||
return (unsigned int)strtol(vtrack, NULL, 10);
|
||||
return (unsigned int)strtol(++ptr, NULL, 10);
|
||||
}
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
@@ -53,48 +53,27 @@ struct ffmpeg_context {
|
||||
struct tag *tag;
|
||||
};
|
||||
|
||||
struct ffmpeg_stream {
|
||||
/** hack - see url_to_struct() */
|
||||
char url[8];
|
||||
|
||||
struct mpd_ffmpeg_stream {
|
||||
struct decoder *decoder;
|
||||
struct input_stream *input;
|
||||
|
||||
ByteIOContext *io;
|
||||
unsigned char buffer[8192];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a
|
||||
* hack because ffmpeg does not provide a nice API for passing a
|
||||
* user-defined pointer to mpdurl_open().
|
||||
*/
|
||||
static struct ffmpeg_stream *url_to_struct(const char *url)
|
||||
static int
|
||||
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
union {
|
||||
const char *in;
|
||||
struct ffmpeg_stream *out;
|
||||
} u = { .in = url };
|
||||
return u.out;
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_open(URLContext *h, const char *filename,
|
||||
G_GNUC_UNUSED int flags)
|
||||
{
|
||||
struct ffmpeg_stream *stream = url_to_struct(filename);
|
||||
h->priv_data = stream;
|
||||
h->is_streamed = stream->input->seekable ? 0 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size)
|
||||
{
|
||||
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
|
||||
struct mpd_ffmpeg_stream *stream = opaque;
|
||||
|
||||
return decoder_read(stream->decoder, stream->input,
|
||||
(void *)buf, size);
|
||||
}
|
||||
|
||||
static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
|
||||
static int64_t
|
||||
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
|
||||
struct mpd_ffmpeg_stream *stream = opaque;
|
||||
bool ret;
|
||||
|
||||
if (whence == AVSEEK_SIZE)
|
||||
@@ -107,25 +86,36 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
|
||||
return stream->input->offset;
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_close(URLContext *h)
|
||||
static struct mpd_ffmpeg_stream *
|
||||
mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
|
||||
{
|
||||
h->priv_data = NULL;
|
||||
return 0;
|
||||
struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
|
||||
stream->decoder = decoder;
|
||||
stream->input = input;
|
||||
stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
|
||||
false, stream,
|
||||
mpd_ffmpeg_stream_read, NULL,
|
||||
input->seekable
|
||||
? mpd_ffmpeg_stream_seek : NULL);
|
||||
if (stream->io == NULL) {
|
||||
g_free(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
static URLProtocol mpd_ffmpeg_fileops = {
|
||||
.name = "mpd",
|
||||
.url_open = mpd_ffmpeg_open,
|
||||
.url_read = mpd_ffmpeg_read,
|
||||
.url_seek = mpd_ffmpeg_seek,
|
||||
.url_close = mpd_ffmpeg_close,
|
||||
};
|
||||
static void
|
||||
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
|
||||
{
|
||||
av_free(stream->io);
|
||||
g_free(stream);
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
|
||||
{
|
||||
av_register_all();
|
||||
register_protocol(&mpd_ffmpeg_fileops);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,41 +130,86 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static AVInputFormat *
|
||||
ffmpeg_probe(struct decoder *decoder, struct input_stream *is,
|
||||
const char *uri)
|
||||
{
|
||||
enum {
|
||||
BUFFER_SIZE = 16384,
|
||||
PADDING = 16,
|
||||
};
|
||||
|
||||
unsigned char *buffer = g_malloc(BUFFER_SIZE);
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
||||
if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET)) {
|
||||
g_free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||
beyond the declared buffer limit, which makes valgrind
|
||||
angry; this workaround removes some padding from the buffer
|
||||
size */
|
||||
nbytes -= PADDING;
|
||||
|
||||
AVProbeData avpd = {
|
||||
.buf = buffer,
|
||||
.buf_size = nbytes,
|
||||
.filename = uri,
|
||||
};
|
||||
|
||||
AVInputFormat *format = av_probe_input_format(&avpd, true);
|
||||
g_free(buffer);
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_helper(struct input_stream *input,
|
||||
ffmpeg_helper(const char *uri,
|
||||
struct decoder *decoder, struct input_stream *input,
|
||||
bool (*callback)(struct ffmpeg_context *ctx),
|
||||
struct ffmpeg_context *ctx)
|
||||
{
|
||||
AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri);
|
||||
if (input_format == NULL)
|
||||
return false;
|
||||
|
||||
g_debug("detected input format '%s' (%s)",
|
||||
input_format->name, input_format->long_name);
|
||||
|
||||
struct mpd_ffmpeg_stream *stream =
|
||||
mpd_ffmpeg_stream_open(decoder, input);
|
||||
if (stream == NULL) {
|
||||
g_warning("Failed to open stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
AVFormatContext *format_context;
|
||||
AVCodecContext *codec_context;
|
||||
AVCodec *codec;
|
||||
int audio_stream;
|
||||
struct ffmpeg_stream stream = {
|
||||
.url = "mpd://X", /* only the mpd:// prefix matters */
|
||||
};
|
||||
bool ret;
|
||||
|
||||
stream.input = input;
|
||||
if (ctx && ctx->decoder) {
|
||||
stream.decoder = ctx->decoder; //are we in decoding loop ?
|
||||
} else {
|
||||
stream.decoder = NULL;
|
||||
}
|
||||
|
||||
//ffmpeg works with ours "fileops" helper
|
||||
if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
|
||||
if (av_open_input_stream(&format_context, stream->io, uri,
|
||||
input_format, NULL) != 0) {
|
||||
g_warning("Open failed\n");
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(format_context)<0) {
|
||||
g_warning("Couldn't find stream info\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
if (audio_stream == -1) {
|
||||
g_warning("No audio stream inside\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -186,11 +221,15 @@ ffmpeg_helper(struct input_stream *input,
|
||||
|
||||
if (!codec) {
|
||||
g_warning("Unsupported audio codec\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avcodec_open(codec_context, codec)<0) {
|
||||
g_warning("Could not open codec\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -204,7 +243,8 @@ ffmpeg_helper(struct input_stream *input,
|
||||
ret = true;
|
||||
|
||||
avcodec_close(codec_context);
|
||||
av_close_input_file(format_context);
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -289,10 +329,6 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
|
||||
|
||||
total_time = 0;
|
||||
|
||||
if (codec_context->channels > 2) {
|
||||
codec_context->channels = 2;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
|
||||
audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
|
||||
#else
|
||||
@@ -353,7 +389,10 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
ctx.input = input;
|
||||
ctx.decoder = decoder;
|
||||
|
||||
ffmpeg_helper(input, ffmpeg_decode_internal, &ctx);
|
||||
char *uri = decoder_get_uri(decoder);
|
||||
ffmpeg_helper(uri, decoder, input,
|
||||
ffmpeg_decode_internal, &ctx);
|
||||
g_free(uri);
|
||||
}
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
||||
@@ -381,12 +420,21 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
|
||||
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
|
||||
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date");
|
||||
#else
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
|
||||
#endif
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM_ARTIST, "album_artist");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc");
|
||||
#else
|
||||
if (f->author[0])
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
|
||||
@@ -430,7 +478,7 @@ static struct tag *ffmpeg_tag(const char *file)
|
||||
ctx.decoder = NULL;
|
||||
ctx.tag = tag_new();
|
||||
|
||||
ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &ctx);
|
||||
ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx);
|
||||
if (!ret) {
|
||||
tag_free(ctx.tag);
|
||||
ctx.tag = NULL;
|
||||
@@ -466,25 +514,31 @@ static const char *const ffmpeg_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const ffmpeg_mime_types[] = {
|
||||
"application/m4a",
|
||||
"application/mp4",
|
||||
"application/octet-stream",
|
||||
"application/ogg",
|
||||
"application/x-ms-wmz",
|
||||
"application/x-ms-wmd",
|
||||
"application/x-ogg",
|
||||
"application/x-shockwave-flash",
|
||||
"application/x-shorten",
|
||||
"audio/8svx",
|
||||
"audio/16sv",
|
||||
"audio/aac",
|
||||
"audio/ac3",
|
||||
"audio/aiff"
|
||||
"audio/amr",
|
||||
"audio/basic",
|
||||
"audio/flac",
|
||||
"audio/m4a",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
"audio/musepack",
|
||||
"audio/ogg",
|
||||
"audio/qcelp",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/x-8svx",
|
||||
"audio/x-16sv",
|
||||
"audio/x-aac",
|
||||
@@ -497,15 +551,20 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"audio/x-flac",
|
||||
"audio/x-gsm",
|
||||
"audio/x-mace",
|
||||
"audio/x-matroska",
|
||||
"audio/x-monkeys-audio",
|
||||
"audio/x-mpeg",
|
||||
"audio/x-ms-wma",
|
||||
"audio/x-ms-wax",
|
||||
"audio/x-musepack",
|
||||
"audio/x-ogg",
|
||||
"audio/x-vorbis",
|
||||
"audio/x-vorbis+ogg",
|
||||
"audio/x-pn-realaudio",
|
||||
"audio/x-pn-multirate-realaudio",
|
||||
"audio/x-speex",
|
||||
"audio/x-tta"
|
||||
"audio/x-voc",
|
||||
"audio/x-wav",
|
||||
"audio/x-wma",
|
||||
"audio/x-wv",
|
||||
|
@@ -871,9 +871,11 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
|
||||
|
||||
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
|
||||
static const char *const oggflac_mime_types[] = {
|
||||
"audio/x-flac+ogg",
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/x-flac+ogg",
|
||||
"audio/x-ogg",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -894,7 +896,11 @@ const struct decoder_plugin oggflac_decoder_plugin = {
|
||||
|
||||
static const char *const flac_suffixes[] = { "flac", NULL };
|
||||
static const char *const flac_mime_types[] = {
|
||||
"audio/x-flac", "application/x-flac", NULL
|
||||
"application/flac",
|
||||
"application/x-flac",
|
||||
"audio/flac",
|
||||
"audio/x-flac",
|
||||
NULL
|
||||
};
|
||||
|
||||
const struct decoder_plugin flac_decoder_plugin = {
|
||||
|
@@ -425,7 +425,27 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
|
||||
/* This code is enabled when libid3tag is disabled. Instead
|
||||
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
|
||||
}
|
||||
|
||||
@@ -433,16 +453,16 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
|
||||
/**
|
||||
* This function emulates libid3tag when it is disabled. Instead of
|
||||
* doing a real analyzation of the frame, it just checks whether the
|
||||
* frame begins with the string "ID3". If so, it returns the full
|
||||
* length.
|
||||
* frame begins with the string "ID3". If so, it returns the length
|
||||
* of the ID3 frame.
|
||||
*/
|
||||
static signed long
|
||||
id3_tag_query(const void *p0, size_t length)
|
||||
{
|
||||
const char *p = p0;
|
||||
|
||||
return length > 3 && memcmp(p, "ID3", 3) == 0
|
||||
? length
|
||||
return length >= 10 && memcmp(p, "ID3", 3) == 0
|
||||
? (p[8] << 7) + p[9] + 10
|
||||
: 0;
|
||||
}
|
||||
#endif /* !HAVE_ID3TAG */
|
||||
@@ -1207,10 +1227,6 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
|
||||
if (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);
|
||||
}
|
||||
|
||||
|
@@ -219,10 +219,12 @@ static struct tag *modTagDup(const char *file)
|
||||
ret->time = 0;
|
||||
|
||||
path2 = g_strdup(file);
|
||||
title = g_strdup(Player_LoadTitle(path2));
|
||||
title = Player_LoadTitle(path2);
|
||||
g_free(path2);
|
||||
if (title)
|
||||
if (title) {
|
||||
tag_add_item(ret, TAG_ITEM_TITLE, title);
|
||||
free(title);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "config.h"
|
||||
#include "tag_table.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -339,6 +340,22 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
|
||||
mp4ff_close(mp4fh);
|
||||
}
|
||||
|
||||
static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
|
||||
[TAG_ITEM_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 *
|
||||
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);
|
||||
|
||||
if (0 == g_ascii_strcasecmp("artist", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_ARTIST, value);
|
||||
} else if (0 == g_ascii_strcasecmp("title", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_TITLE, value);
|
||||
} else if (0 == g_ascii_strcasecmp("album", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_ALBUM, value);
|
||||
} else if (0 == g_ascii_strcasecmp("track", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_TRACK, value);
|
||||
} else if (0 == g_ascii_strcasecmp("disc", item)) {
|
||||
/* Is that the correct id? */
|
||||
tag_add_item(ret, TAG_ITEM_DISC, value);
|
||||
} else if (0 == g_ascii_strcasecmp("genre", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_GENRE, value);
|
||||
} else if (0 == g_ascii_strcasecmp("date", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_DATE, value);
|
||||
} else if (0 == g_ascii_strcasecmp("writer", item)) {
|
||||
tag_add_item(ret, TAG_ITEM_COMPOSER, value);
|
||||
}
|
||||
enum tag_type type = mp4ff_tag_name_parse(item);
|
||||
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||
tag_add_item(ret, type, value);
|
||||
|
||||
free(item);
|
||||
free(value);
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include <mpcdec/mpcdec.h>
|
||||
#else
|
||||
#include <mpc/mpcdec.h>
|
||||
#include <math.h>
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
@@ -103,7 +104,7 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
|
||||
|
||||
if (shift < 0)
|
||||
val = sample << -shift;
|
||||
val = sample >> -shift;
|
||||
else
|
||||
val = sample << shift;
|
||||
#else
|
||||
@@ -209,10 +210,17 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
}
|
||||
|
||||
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].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].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,
|
||||
is->seekable,
|
||||
|
@@ -357,9 +357,11 @@ fail:
|
||||
|
||||
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
|
||||
static const char *const oggflac_mime_types[] = {
|
||||
"audio/x-flac+ogg",
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/x-ogg",
|
||||
"audio/x-flac+ogg",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@@ -97,6 +97,13 @@ static long ogg_tell_cb(void *vdata)
|
||||
return (long)data->input_stream->offset;
|
||||
}
|
||||
|
||||
static const ov_callbacks vorbis_is_callbacks = {
|
||||
.read_func = ogg_read_cb,
|
||||
.seek_func = ogg_seek_cb,
|
||||
.close_func = ogg_close_cb,
|
||||
.tell_func = ogg_tell_cb,
|
||||
};
|
||||
|
||||
static const char *
|
||||
vorbis_comment_value(const char *comment, const char *needle)
|
||||
{
|
||||
@@ -226,6 +233,9 @@ oggvorbis_seekable(struct decoder *decoder)
|
||||
bool seekable;
|
||||
|
||||
uri = decoder_get_uri(decoder);
|
||||
if (uri == NULL)
|
||||
return false;
|
||||
|
||||
/* disable seeking on remote streams, because libvorbis seeks
|
||||
around like crazy, and due to being very expensive, this
|
||||
delays song playback my 10 or 20 seconds */
|
||||
@@ -241,7 +251,6 @@ vorbis_stream_decode(struct decoder *decoder,
|
||||
struct input_stream *input_stream)
|
||||
{
|
||||
OggVorbis_File vf;
|
||||
ov_callbacks callbacks;
|
||||
OggCallbackData data;
|
||||
struct audio_format audio_format;
|
||||
int current_section;
|
||||
@@ -266,13 +275,9 @@ vorbis_stream_decode(struct decoder *decoder,
|
||||
data.input_stream = input_stream;
|
||||
data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
|
||||
|
||||
callbacks.read_func = ogg_read_cb;
|
||||
callbacks.seek_func = ogg_seek_cb;
|
||||
callbacks.close_func = ogg_close_cb;
|
||||
callbacks.tell_func = ogg_tell_cb;
|
||||
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
|
||||
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0,
|
||||
vorbis_is_callbacks)) < 0) {
|
||||
const char *error;
|
||||
|
||||
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
|
||||
return;
|
||||
|
||||
@@ -405,8 +410,13 @@ static const char *const vorbis_suffixes[] = {
|
||||
|
||||
static const char *const vorbis_mime_types[] = {
|
||||
"application/ogg",
|
||||
"audio/x-vorbis+ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/x-ogg",
|
||||
"audio/x-vorbis",
|
||||
"audio/x-vorbis+ogg",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
@@ -72,7 +72,7 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
|
||||
|
||||
switch (bytes_per_sample) {
|
||||
case 1: {
|
||||
uchar *dst = buffer;
|
||||
int8_t *dst = buffer;
|
||||
/*
|
||||
* The asserts like the following one are because we do the
|
||||
* formatting of samples within a single buffer. The size
|
||||
@@ -185,10 +185,9 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
|
||||
do {
|
||||
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
|
||||
if (can_seek) {
|
||||
int where;
|
||||
unsigned where = decoder_seek_where(decoder) *
|
||||
audio_format.sample_rate;
|
||||
|
||||
where = decoder_seek_where(decoder);
|
||||
where *= audio_format.sample_rate;
|
||||
if (WavpackSeekSample(wpc, where)) {
|
||||
position = where;
|
||||
decoder_command_finished(decoder);
|
||||
|
@@ -93,7 +93,9 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
|
||||
dc.seek_error || decoder->seeking);
|
||||
assert(dc.pipe != NULL);
|
||||
|
||||
if (dc.command == DECODE_COMMAND_SEEK) {
|
||||
if (decoder->seeking) {
|
||||
decoder->seeking = false;
|
||||
|
||||
/* delete frames from the old song position */
|
||||
|
||||
if (decoder->chunk != NULL) {
|
||||
@@ -124,6 +126,8 @@ void decoder_seek_error(struct decoder * decoder)
|
||||
assert(dc.pipe != NULL);
|
||||
|
||||
dc.seek_error = true;
|
||||
decoder->seeking = false;
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
|
||||
struct input_stream input_stream;
|
||||
const struct decoder_plugin *plugin;
|
||||
|
||||
if (!input_stream_open(&input_stream, uri)) {
|
||||
close_instream = input_stream_open(&input_stream, uri);
|
||||
if (!close_instream && !song_is_file(song)) {
|
||||
dc.state = DECODE_STATE_ERROR;
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +109,7 @@ static void decoder_run_song(const struct song *song, const char *uri)
|
||||
/* wait for the input stream to become ready; its metadata
|
||||
will be available then */
|
||||
|
||||
while (!input_stream.ready) {
|
||||
while (close_instream && !input_stream.ready) {
|
||||
if (dc.command == DECODE_COMMAND_STOP) {
|
||||
input_stream_close(&input_stream);
|
||||
dc.state = DECODE_STATE_STOP;
|
||||
@@ -124,7 +125,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
|
||||
}
|
||||
|
||||
if (dc.command == DECODE_COMMAND_STOP) {
|
||||
input_stream_close(&input_stream);
|
||||
if (close_instream)
|
||||
input_stream_close(&input_stream);
|
||||
dc.state = DECODE_STATE_STOP;
|
||||
return;
|
||||
}
|
||||
@@ -179,8 +181,11 @@ static void decoder_run_song(const struct song *song, const char *uri)
|
||||
const char *s = uri_get_suffix(uri);
|
||||
while ((plugin = decoder_plugin_from_suffix(s, next++))) {
|
||||
if (plugin->file_decode != NULL) {
|
||||
input_stream_close(&input_stream);
|
||||
close_instream = false;
|
||||
if (close_instream) {
|
||||
input_stream_close(&input_stream);
|
||||
close_instream = false;
|
||||
}
|
||||
|
||||
ret = decoder_file_decode(plugin,
|
||||
&decoder, uri);
|
||||
if (ret)
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#include "client.h"
|
||||
#include "song_print.h"
|
||||
|
||||
static int
|
||||
static void
|
||||
dirvec_print(struct client *client, const struct dirvec *dv)
|
||||
{
|
||||
size_t i;
|
||||
@@ -30,15 +30,11 @@ dirvec_print(struct client *client, const struct dirvec *dv)
|
||||
for (i = 0; i < dv->nr; ++i)
|
||||
client_printf(client, DIRECTORY_DIR "%s\n",
|
||||
directory_get_path(dv->base[i]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
void
|
||||
directory_print(struct client *client, const struct directory *directory)
|
||||
{
|
||||
dirvec_print(client, &directory->children);
|
||||
songvec_print(client, &directory->songs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@
|
||||
struct client;
|
||||
struct directory;
|
||||
|
||||
int
|
||||
void
|
||||
directory_print(struct client *client, const struct directory *directory);
|
||||
|
||||
#endif
|
||||
|
@@ -66,6 +66,7 @@ input_archive_open(struct input_stream *is, const char *pathname)
|
||||
|
||||
if (!opened) {
|
||||
g_warning("open inarchive file %s failed\n\n",filename);
|
||||
arplug->close(file);
|
||||
} else {
|
||||
is->ready = true;
|
||||
}
|
||||
|
@@ -41,9 +41,6 @@
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "input_curl"
|
||||
|
||||
/** rewinding is possible after up to 64 kB */
|
||||
static const off_t max_rewind_size = 64 * 1024;
|
||||
|
||||
/**
|
||||
* Buffers created by input_curl_writefunction().
|
||||
*/
|
||||
@@ -78,9 +75,6 @@ struct input_curl {
|
||||
/** did libcurl tell us the we're at the end of the response body? */
|
||||
bool eof;
|
||||
|
||||
/** limited list of old buffers, for rewinding */
|
||||
GQueue *rewind;
|
||||
|
||||
/** error message provided by libcurl */
|
||||
char error[CURL_ERROR_SIZE];
|
||||
|
||||
@@ -176,11 +170,6 @@ input_curl_easy_free(struct input_curl *c)
|
||||
|
||||
g_queue_foreach(c->buffers, buffer_free_callback, NULL);
|
||||
g_queue_clear(c->buffers);
|
||||
|
||||
if (c->rewind != NULL) {
|
||||
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
|
||||
g_queue_clear(c->rewind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,8 +190,6 @@ input_curl_free(struct input_stream *is)
|
||||
curl_multi_cleanup(c->multi);
|
||||
|
||||
g_queue_free(c->buffers);
|
||||
if (c->rewind != NULL)
|
||||
g_queue_free(c->rewind);
|
||||
|
||||
g_free(c->url);
|
||||
g_free(c);
|
||||
@@ -322,7 +309,7 @@ fill_buffer(struct input_stream *is)
|
||||
* Mark a part of the buffer object as consumed.
|
||||
*/
|
||||
static struct buffer *
|
||||
consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
|
||||
consume_buffer(struct buffer *buffer, size_t length)
|
||||
{
|
||||
assert(buffer != NULL);
|
||||
assert(buffer->consumed < buffer->size);
|
||||
@@ -333,19 +320,14 @@ consume_buffer(struct buffer *buffer, size_t length, GQueue *rewind_buffers)
|
||||
|
||||
assert(buffer->consumed == buffer->size);
|
||||
|
||||
if (rewind_buffers != NULL)
|
||||
/* append this buffer to the rewind buffer list */
|
||||
g_queue_push_tail(rewind_buffers, buffer);
|
||||
else
|
||||
g_free(buffer);
|
||||
g_free(buffer);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t
|
||||
read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
|
||||
void *dest0, size_t length,
|
||||
GQueue *rewind_buffers)
|
||||
void *dest0, size_t length)
|
||||
{
|
||||
struct buffer *buffer = g_queue_pop_head(buffers);
|
||||
uint8_t *dest = dest0;
|
||||
@@ -364,7 +346,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
|
||||
if (chunk > 0) {
|
||||
memcpy(dest, buffer->data + buffer->consumed,
|
||||
chunk);
|
||||
buffer = consume_buffer(buffer, chunk, rewind_buffers);
|
||||
buffer = consume_buffer(buffer, chunk);
|
||||
|
||||
nbytes += chunk;
|
||||
dest += chunk;
|
||||
@@ -379,7 +361,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, GQueue *buffers,
|
||||
chunk = icy_meta(icy_metadata, buffer->data + buffer->consumed,
|
||||
length);
|
||||
if (chunk > 0) {
|
||||
buffer = consume_buffer(buffer, chunk, rewind_buffers);
|
||||
buffer = consume_buffer(buffer, chunk);
|
||||
|
||||
length -= chunk;
|
||||
|
||||
@@ -418,31 +400,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
|
||||
{
|
||||
struct input_curl *c = is->data;
|
||||
bool success;
|
||||
GQueue *rewind_buffers;
|
||||
size_t nbytes = 0;
|
||||
char *dest = ptr;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (c->rewind != NULL &&
|
||||
(!g_queue_is_empty(c->rewind) || is->offset == 0)) {
|
||||
off_t offset = 0;
|
||||
struct buffer *buffer;
|
||||
|
||||
for (GList *list = g_queue_peek_head_link(c->rewind);
|
||||
list != NULL; list = g_list_next(list)) {
|
||||
buffer = list->data;
|
||||
offset += buffer->consumed;
|
||||
assert(offset <= is->offset);
|
||||
}
|
||||
|
||||
buffer = g_queue_peek_head(c->buffers);
|
||||
if (buffer != NULL)
|
||||
offset += buffer->consumed;
|
||||
|
||||
assert(offset == is->offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
do {
|
||||
/* fill the buffer */
|
||||
|
||||
@@ -452,19 +412,9 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
|
||||
|
||||
/* send buffer contents */
|
||||
|
||||
if (c->rewind != NULL &&
|
||||
(!g_queue_is_empty(c->rewind) || is->offset == 0))
|
||||
/* at the beginning or already writing the rewind
|
||||
buffer list */
|
||||
rewind_buffers = c->rewind;
|
||||
else
|
||||
/* we don't need the rewind buffers anymore */
|
||||
rewind_buffers = NULL;
|
||||
|
||||
while (size > 0 && !g_queue_is_empty(c->buffers)) {
|
||||
size_t copy = read_from_buffer(&c->icy_metadata, c->buffers,
|
||||
dest + nbytes, size,
|
||||
rewind_buffers);
|
||||
dest + nbytes, size);
|
||||
|
||||
nbytes += copy;
|
||||
size -= copy;
|
||||
@@ -476,33 +426,6 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
|
||||
|
||||
is->offset += (off_t)nbytes;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (rewind_buffers != NULL) {
|
||||
off_t offset = 0;
|
||||
struct buffer *buffer;
|
||||
|
||||
for (GList *list = g_queue_peek_head_link(c->rewind);
|
||||
list != NULL; list = g_list_next(list)) {
|
||||
buffer = list->data;
|
||||
offset += buffer->consumed;
|
||||
assert(offset <= is->offset);
|
||||
}
|
||||
|
||||
buffer = g_queue_peek_head(c->buffers);
|
||||
if (buffer != NULL)
|
||||
offset += buffer->consumed;
|
||||
|
||||
assert(offset == is->offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rewind_buffers != NULL && is->offset > max_rewind_size) {
|
||||
/* drop the rewind buffer, it has grown too large */
|
||||
|
||||
g_queue_foreach(c->rewind, buffer_free_callback, NULL);
|
||||
g_queue_clear(c->rewind);
|
||||
}
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
@@ -635,15 +558,6 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
|
||||
/* a stream with icy-metadata is not
|
||||
seekable */
|
||||
is->seekable = false;
|
||||
|
||||
if (c->rewind != NULL) {
|
||||
/* rewinding with icy-metadata is too
|
||||
hairy for me .. */
|
||||
assert(g_queue_is_empty(c->rewind));
|
||||
|
||||
g_queue_free(c->rewind);
|
||||
c->rewind = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,6 +646,18 @@ input_curl_easy_init(struct input_stream *is)
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
input_curl_reinit(struct input_stream *is)
|
||||
{
|
||||
struct input_curl *c = is->data;
|
||||
|
||||
assert(is->plugin == &input_plugin_curl);
|
||||
assert(c->easy != NULL);
|
||||
|
||||
curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is);
|
||||
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is);
|
||||
}
|
||||
|
||||
static bool
|
||||
input_curl_send_request(struct input_curl *c)
|
||||
{
|
||||
@@ -751,72 +677,6 @@ input_curl_send_request(struct input_curl *c)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
input_curl_can_rewind(struct input_stream *is)
|
||||
{
|
||||
struct input_curl *c = is->data;
|
||||
struct buffer *buffer;
|
||||
|
||||
if (c->rewind == NULL)
|
||||
return false;
|
||||
|
||||
if (!g_queue_is_empty(c->rewind))
|
||||
/* the rewind buffer hasn't been wiped yet */
|
||||
return true;
|
||||
|
||||
if (g_queue_is_empty(c->buffers))
|
||||
/* there are no buffers at all - cheap rewind not
|
||||
possible */
|
||||
return false;
|
||||
|
||||
/* rewind is possible if this is the very first buffer of the
|
||||
resource */
|
||||
buffer = (struct buffer*)g_queue_peek_head(c->buffers);
|
||||
return (off_t)buffer->consumed == is->offset;
|
||||
}
|
||||
|
||||
static void
|
||||
input_curl_rewind(struct input_stream *is)
|
||||
{
|
||||
struct input_curl *c = is->data;
|
||||
#ifndef NDEBUG
|
||||
off_t offset = 0;
|
||||
#endif
|
||||
|
||||
assert(c->rewind != NULL);
|
||||
|
||||
/* rewind the current buffer */
|
||||
|
||||
if (!g_queue_is_empty(c->buffers)) {
|
||||
struct buffer *buffer =
|
||||
(struct buffer*)g_queue_peek_head(c->buffers);
|
||||
#ifndef NDEBUG
|
||||
offset += buffer->consumed;
|
||||
#endif
|
||||
buffer->consumed = 0;
|
||||
}
|
||||
|
||||
/* reset and move all rewind buffers back to the regular buffer list */
|
||||
|
||||
while (!g_queue_is_empty(c->rewind)) {
|
||||
struct buffer *buffer =
|
||||
(struct buffer*)g_queue_pop_tail(c->rewind);
|
||||
#ifndef NDEBUG
|
||||
offset += buffer->consumed;
|
||||
#endif
|
||||
buffer->consumed = 0;
|
||||
g_queue_push_head(c->buffers, buffer);
|
||||
}
|
||||
|
||||
assert(offset == is->offset);
|
||||
|
||||
is->offset = 0;
|
||||
|
||||
/* rewind the icy_metadata object */
|
||||
|
||||
icy_reset(&c->icy_metadata);
|
||||
}
|
||||
|
||||
static bool
|
||||
input_curl_seek(struct input_stream *is, off_t offset, int whence)
|
||||
{
|
||||
@@ -825,17 +685,9 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
|
||||
|
||||
assert(is->ready);
|
||||
|
||||
if (whence == SEEK_SET && offset == 0) {
|
||||
if (is->offset == 0)
|
||||
/* no-op */
|
||||
return true;
|
||||
|
||||
if (input_curl_can_rewind(is)) {
|
||||
/* we have enough rewind buffers left */
|
||||
input_curl_rewind(is);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (whence == SEEK_SET && offset == is->offset)
|
||||
/* no-op */
|
||||
return true;
|
||||
|
||||
if (!is->seekable)
|
||||
return false;
|
||||
@@ -868,26 +720,16 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
|
||||
/* check if we can fast-forward the buffer */
|
||||
|
||||
while (offset > is->offset && !g_queue_is_empty(c->buffers)) {
|
||||
GQueue *rewind_buffers;
|
||||
struct buffer *buffer;
|
||||
size_t length;
|
||||
|
||||
if (c->rewind != NULL &&
|
||||
(!g_queue_is_empty(c->rewind) || is->offset == 0))
|
||||
/* at the beginning or already writing the rewind
|
||||
buffer list */
|
||||
rewind_buffers = c->rewind;
|
||||
else
|
||||
/* we don't need the rewind buffers anymore */
|
||||
rewind_buffers = NULL;
|
||||
|
||||
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
|
||||
|
||||
length = buffer->size - buffer->consumed;
|
||||
if (offset - is->offset < (off_t)length)
|
||||
length = offset - is->offset;
|
||||
|
||||
buffer = consume_buffer(buffer, length, rewind_buffers);
|
||||
buffer = consume_buffer(buffer, length);
|
||||
if (buffer != NULL)
|
||||
g_queue_push_head(c->buffers, buffer);
|
||||
|
||||
@@ -940,7 +782,6 @@ input_curl_open(struct input_stream *is, const char *url)
|
||||
c = g_new0(struct input_curl, 1);
|
||||
c->url = g_strdup(url);
|
||||
c->buffers = g_queue_new();
|
||||
c->rewind = g_queue_new();
|
||||
|
||||
is->plugin = &input_plugin_curl;
|
||||
is->data = c;
|
||||
|
@@ -20,6 +20,16 @@
|
||||
#ifndef MPD_INPUT_CURL_H
|
||||
#define MPD_INPUT_CURL_H
|
||||
|
||||
struct input_stream;
|
||||
|
||||
extern const struct input_plugin input_plugin_curl;
|
||||
|
||||
/**
|
||||
* This is a workaround for an input_stream API deficiency; after
|
||||
* exchanging the input_stream pointer in input_rewind_open(), this
|
||||
* function is called to reinitialize CURL's data pointers.
|
||||
*/
|
||||
void
|
||||
input_curl_reinit(struct input_stream *is);
|
||||
|
||||
#endif
|
||||
|
@@ -36,25 +36,14 @@ input_file_open(struct input_stream *is, const char *filename)
|
||||
int fd, ret;
|
||||
struct stat st;
|
||||
|
||||
char* pathname = g_strdup(filename);
|
||||
|
||||
if (filename[0] != '/')
|
||||
{
|
||||
g_free(pathname);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stat(filename, &st) < 0) {
|
||||
char* slash = strrchr(pathname, '/');
|
||||
*slash = '\0';
|
||||
}
|
||||
|
||||
fd = open(pathname, O_RDONLY);
|
||||
fd = open(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
is->error = errno;
|
||||
g_debug("Failed to open \"%s\": %s",
|
||||
pathname, g_strerror(errno));
|
||||
g_free(pathname);
|
||||
filename, g_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -64,15 +53,13 @@ input_file_open(struct input_stream *is, const char *filename)
|
||||
if (ret < 0) {
|
||||
is->error = errno;
|
||||
close(fd);
|
||||
g_free(pathname);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
g_debug("Not a regular file: %s", pathname);
|
||||
g_debug("Not a regular file: %s", filename);
|
||||
is->error = EINVAL;
|
||||
close(fd);
|
||||
g_free(pathname);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,8 +73,6 @@ input_file_open(struct input_stream *is, const char *filename)
|
||||
is->data = GINT_TO_POINTER(fd);
|
||||
is->ready = true;
|
||||
|
||||
g_free(pathname);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -49,10 +49,13 @@ input_mms_open(struct input_stream *is, const char *url)
|
||||
m = g_new(struct input_mms, 1);
|
||||
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
|
||||
if (m->mms == NULL) {
|
||||
g_free(m);
|
||||
g_warning("mmsx_connect() failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m->eof = false;
|
||||
|
||||
/* XX is this correct? at least this selects the ffmpeg
|
||||
decoder, which seems to work fine*/
|
||||
is->mime = g_strdup("audio/x-ms-wma");
|
||||
|
238
src/input/rewind_input_plugin.c
Normal file
238
src/input/rewind_input_plugin.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "input/rewind_input_plugin.h"
|
||||
#include "input/curl_input_plugin.h"
|
||||
#include "input_plugin.h"
|
||||
#include "tag.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "input_rewind"
|
||||
|
||||
struct input_rewind {
|
||||
struct input_stream input;
|
||||
|
||||
/**
|
||||
* The read position within the buffer. Undefined as long as
|
||||
* reading_from_buffer() returns false.
|
||||
*/
|
||||
size_t head;
|
||||
|
||||
/**
|
||||
* The write/append position within the buffer.
|
||||
*/
|
||||
size_t tail;
|
||||
|
||||
/**
|
||||
* The size of this buffer is the maximum number of bytes
|
||||
* which can be rewinded cheaply without passing the "seek"
|
||||
* call to CURL.
|
||||
*
|
||||
* The origin of this buffer is always the beginning of the
|
||||
* stream (offset 0).
|
||||
*/
|
||||
char buffer[64 * 1024];
|
||||
};
|
||||
|
||||
/**
|
||||
* Are we currently reading from the buffer, and does the buffer
|
||||
* contain more data for the next read operation?
|
||||
*/
|
||||
static bool
|
||||
reading_from_buffer(const struct input_stream *is)
|
||||
{
|
||||
const struct input_rewind *r = is->data;
|
||||
|
||||
return r->tail > 0 && is->offset < r->input.offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy public attributes from the underlying input stream to the
|
||||
* "rewind" input stream. This function is called when a method of
|
||||
* the underlying stream has returned, which may have modified these
|
||||
* attributes.
|
||||
*/
|
||||
static void
|
||||
copy_attributes(struct input_stream *dest)
|
||||
{
|
||||
const struct input_rewind *r = dest->data;
|
||||
const struct input_stream *src = &r->input;
|
||||
|
||||
dest->ready = src->ready;
|
||||
dest->seekable = src->seekable;
|
||||
dest->error = src->error;
|
||||
dest->size = src->size;
|
||||
dest->offset = src->offset;
|
||||
|
||||
if (dest->mime == NULL && src->mime != NULL)
|
||||
/* this is set only once, and the duplicated pointer
|
||||
is freed by input_stream_close() */
|
||||
dest->mime = g_strdup(src->mime);
|
||||
}
|
||||
|
||||
static void
|
||||
input_rewind_close(struct input_stream *is)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
input_stream_close(&r->input);
|
||||
|
||||
g_free(r);
|
||||
}
|
||||
|
||||
static struct tag *
|
||||
input_rewind_tag(struct input_stream *is)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
return input_stream_tag(&r->input);
|
||||
}
|
||||
|
||||
static int
|
||||
input_rewind_buffer(struct input_stream *is)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
int ret = input_stream_buffer(&r->input);
|
||||
if (ret < 0 || !reading_from_buffer(is))
|
||||
copy_attributes(is);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t
|
||||
input_rewind_read(struct input_stream *is, void *ptr, size_t size)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
if (reading_from_buffer(is)) {
|
||||
/* buffered read */
|
||||
|
||||
assert(r->head == (size_t)is->offset);
|
||||
assert(r->tail == (size_t)r->input.offset);
|
||||
|
||||
if (size > r->tail - r->head)
|
||||
size = r->tail - r->head;
|
||||
|
||||
memcpy(ptr, r->buffer + r->head, size);
|
||||
r->head += size;
|
||||
is->offset += size;
|
||||
|
||||
return size;
|
||||
} else {
|
||||
/* pass method call to underlying stream */
|
||||
|
||||
size_t nbytes = input_stream_read(&r->input, ptr, size);
|
||||
|
||||
if (r->input.offset > (off_t)sizeof(r->buffer))
|
||||
/* disable buffering */
|
||||
r->tail = 0;
|
||||
else if (r->tail == (size_t)is->offset) {
|
||||
/* append to buffer */
|
||||
|
||||
memcpy(r->buffer + r->tail, ptr, nbytes);
|
||||
r->tail += nbytes;
|
||||
|
||||
assert(r->tail == (size_t)r->input.offset);
|
||||
}
|
||||
|
||||
copy_attributes(is);
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
input_rewind_eof(G_GNUC_UNUSED struct input_stream *is)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
return !reading_from_buffer(is) && input_stream_eof(&r->input);
|
||||
}
|
||||
|
||||
static bool
|
||||
input_rewind_seek(struct input_stream *is, off_t offset, int whence)
|
||||
{
|
||||
struct input_rewind *r = is->data;
|
||||
|
||||
assert(is->ready);
|
||||
|
||||
if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) {
|
||||
/* buffered seek */
|
||||
|
||||
assert(!reading_from_buffer(is) ||
|
||||
r->head == (size_t)is->offset);
|
||||
assert(r->tail == (size_t)r->input.offset);
|
||||
|
||||
r->head = (size_t)offset;
|
||||
is->offset = offset;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
bool success = input_stream_seek(&r->input, offset, whence);
|
||||
copy_attributes(is);
|
||||
|
||||
/* disable the buffer, because r->input has left the
|
||||
buffered range now */
|
||||
r->tail = 0;
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct input_plugin rewind_input_plugin = {
|
||||
.close = input_rewind_close,
|
||||
.tag = input_rewind_tag,
|
||||
.buffer = input_rewind_buffer,
|
||||
.read = input_rewind_read,
|
||||
.eof = input_rewind_eof,
|
||||
.seek = input_rewind_seek,
|
||||
};
|
||||
|
||||
void
|
||||
input_rewind_open(struct input_stream *is)
|
||||
{
|
||||
struct input_rewind *c;
|
||||
|
||||
assert(is != NULL);
|
||||
assert(is->offset == 0);
|
||||
|
||||
if (is->plugin != &input_plugin_curl)
|
||||
/* due to limitations in the input_plugin API, we only
|
||||
(explicitly) support the CURL input plugin */
|
||||
return;
|
||||
|
||||
c = g_new(struct input_rewind, 1);
|
||||
c->tail = 0;
|
||||
|
||||
/* move the CURL input stream to c->input */
|
||||
c->input = *is;
|
||||
input_curl_reinit(&c->input);
|
||||
|
||||
/* convert the existing input_stream pointer to a "rewind"
|
||||
input stream */
|
||||
is->plugin = &rewind_input_plugin;
|
||||
is->data = c;
|
||||
}
|
49
src/input/rewind_input_plugin.h
Normal file
49
src/input/rewind_input_plugin.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 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.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
*
|
||||
* A wrapper for an input_stream object which allows cheap buffered
|
||||
* rewinding. This is useful while detecting the stream codec (let
|
||||
* each decoder plugin peek a portion from the stream).
|
||||
*/
|
||||
|
||||
#ifndef MPD_INPUT_REWIND_H
|
||||
#define MPD_INPUT_REWIND_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
struct input_stream;
|
||||
|
||||
#ifdef HAVE_CURL
|
||||
|
||||
void
|
||||
input_rewind_open(struct input_stream *is);
|
||||
|
||||
#else
|
||||
|
||||
static inline void
|
||||
input_rewind_open(struct input_stream *is)
|
||||
{
|
||||
(void)is;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@@ -22,6 +22,7 @@
|
||||
#include "conf.h"
|
||||
|
||||
#include "input/file_input_plugin.h"
|
||||
#include "input/rewind_input_plugin.h"
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
#include "input/archive_input_plugin.h"
|
||||
@@ -131,6 +132,8 @@ input_stream_open(struct input_stream *is, const char *url)
|
||||
assert(is->plugin->eof != NULL);
|
||||
assert(!is->seekable || is->plugin->seek != NULL);
|
||||
|
||||
input_rewind_open(is);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -407,7 +407,13 @@ static int get_remote_uid(int fd)
|
||||
|
||||
return cred.uid;
|
||||
#else
|
||||
(void)fd;
|
||||
#ifdef HAVE_GETPEEREID
|
||||
uid_t euid;
|
||||
gid_t egid;
|
||||
|
||||
if (getpeereid(fd, &euid, &egid) == 0)
|
||||
return euid;
|
||||
#endif
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
@@ -42,9 +42,9 @@ locate_parse_type(const char *str)
|
||||
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
|
||||
return LOCATE_TAG_ANY_TYPE;
|
||||
|
||||
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
|
||||
if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
|
||||
return i;
|
||||
i = tag_name_parse_i(str);
|
||||
if (i != TAG_NUM_OF_ITEM_TYPES)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
13
src/mapper.c
13
src/mapper.c
@@ -221,14 +221,19 @@ map_spl_path(void)
|
||||
char *
|
||||
map_spl_utf8_to_fs(const char *name)
|
||||
{
|
||||
char *filename = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
||||
char *path;
|
||||
char *filename_utf8, *filename_fs, *path;
|
||||
|
||||
if (playlist_dir == NULL)
|
||||
return NULL;
|
||||
|
||||
path = g_build_filename(playlist_dir, filename, NULL);
|
||||
g_free(filename);
|
||||
filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
||||
filename_fs = utf8_to_fs_charset(filename_utf8);
|
||||
g_free(filename_utf8);
|
||||
if (filename_fs == NULL)
|
||||
return NULL;
|
||||
|
||||
path = g_build_filename(playlist_dir, filename_fs, NULL);
|
||||
g_free(filename_fs);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
@@ -99,6 +99,8 @@ map_spl_path(void);
|
||||
* Maps a playlist name (without the ".m3u" suffix) to a file system
|
||||
* path. The return value is allocated on the heap and must be freed
|
||||
* with g_free().
|
||||
*
|
||||
* @return the path in file system encoding, or NULL if mapping failed
|
||||
*/
|
||||
char *
|
||||
map_spl_utf8_to_fs(const char *name);
|
||||
|
@@ -62,6 +62,10 @@ mixer_free(struct mixer *mixer)
|
||||
assert(mixer->plugin != NULL);
|
||||
assert(mixer->mutex != NULL);
|
||||
|
||||
/* mixers with the "global" flag set might still be open at
|
||||
this point (see mixer_auto_close()) */
|
||||
mixer_close(mixer);
|
||||
|
||||
g_mutex_free(mixer->mutex);
|
||||
|
||||
mixer->plugin->finish(mixer);
|
||||
|
@@ -28,7 +28,7 @@
|
||||
* would put too much stress on the allocator.
|
||||
*/
|
||||
struct pcm_buffer {
|
||||
char *buffer;
|
||||
void *buffer;
|
||||
|
||||
size_t size;
|
||||
};
|
||||
|
@@ -89,6 +89,13 @@ struct player {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -518,6 +525,14 @@ play_next_chunk(struct player *player)
|
||||
chunk = music_pipe_shift(player->pipe);
|
||||
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,
|
||||
&dc.out_audio_format,
|
||||
cross_fade_position,
|
||||
@@ -544,6 +559,14 @@ play_next_chunk(struct player *player)
|
||||
|
||||
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 */
|
||||
|
||||
success = play_chunk(player->song, chunk, &player->play_audio_format,
|
||||
@@ -608,6 +631,7 @@ static void do_play(void)
|
||||
.xfade = XFADE_UNKNOWN,
|
||||
.cross_fading = false,
|
||||
.cross_fade_chunks = 0,
|
||||
.cross_fade_tag = NULL,
|
||||
.size_to_time = 0.0,
|
||||
};
|
||||
|
||||
@@ -754,6 +778,9 @@ static void do_play(void)
|
||||
music_pipe_clear(player.pipe, player_buffer);
|
||||
music_pipe_free(player.pipe);
|
||||
|
||||
if (player.cross_fade_tag != NULL)
|
||||
tag_free(player.cross_fade_tag);
|
||||
|
||||
pc.state = PLAYER_STATE_STOP;
|
||||
event_pipe_emit(PIPE_EVENT_PLAYLIST);
|
||||
}
|
||||
|
@@ -140,7 +140,8 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
|
||||
? queue_next_order(&playlist->queue, playlist->current)
|
||||
: 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
|
||||
order each time the playlist is played
|
||||
completely */
|
||||
|
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "playlist_internal.h"
|
||||
#include "player_control.h"
|
||||
#include "idle.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -156,6 +157,8 @@ nextSongInPlaylist(struct playlist *playlist)
|
||||
if (next_order < 0) {
|
||||
/* cancel single */
|
||||
playlist->queue.single = false;
|
||||
idle_add(IDLE_OPTIONS);
|
||||
|
||||
/* no song after this one: stop playback */
|
||||
stopPlaylist(playlist);
|
||||
|
||||
|
@@ -71,12 +71,15 @@ spl_save_queue(const char *name_utf8, const struct queue *queue)
|
||||
char *path_fs;
|
||||
FILE *file;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(name_utf8))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) {
|
||||
g_free(path_fs);
|
||||
|
@@ -45,14 +45,14 @@ queue_next_order(const struct queue *queue, unsigned order)
|
||||
|
||||
if (queue->single)
|
||||
{
|
||||
if (queue->repeat)
|
||||
if (queue->repeat && !queue->consume)
|
||||
return order;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
if (order + 1 < queue->length)
|
||||
return order + 1;
|
||||
else if (queue->repeat)
|
||||
else if (queue->repeat && (order > 0 || !queue->consume))
|
||||
/* restart at first song */
|
||||
return 0;
|
||||
else
|
||||
|
@@ -152,9 +152,12 @@ spl_save(GPtrArray *list, const char *utf8path)
|
||||
|
||||
assert(utf8path != NULL);
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(utf8path);
|
||||
if (path_fs == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
while (!(file = fopen(path_fs, "w")) && errno == EINTR);
|
||||
g_free(path_fs);
|
||||
@@ -178,7 +181,7 @@ spl_load(const char *utf8path)
|
||||
char buffer[MPD_PATH_MAX];
|
||||
char *path_fs;
|
||||
|
||||
if (!spl_valid_name(utf8path))
|
||||
if (!spl_valid_name(utf8path) || map_spl_path() == NULL)
|
||||
return NULL;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(utf8path);
|
||||
@@ -299,12 +302,15 @@ spl_clear(const char *utf8path)
|
||||
char *path_fs;
|
||||
FILE *file;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(utf8path))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(utf8path);
|
||||
if (path_fs == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
while (!(file = fopen(path_fs, "w")) && errno == EINTR);
|
||||
g_free(path_fs);
|
||||
@@ -323,9 +329,15 @@ spl_delete(const char *name_utf8)
|
||||
char *path_fs;
|
||||
int ret;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(name_utf8))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
ret = unlink(path_fs);
|
||||
g_free(path_fs);
|
||||
@@ -370,12 +382,15 @@ spl_append_song(const char *utf8path, struct song *song)
|
||||
struct stat st;
|
||||
char *path_fs;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(utf8path))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(utf8path);
|
||||
if (path_fs == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
while (!(file = fopen(path_fs, "a")) && errno == EINTR);
|
||||
g_free(path_fs);
|
||||
@@ -445,6 +460,9 @@ spl_rename(const char *utf8from, const char *utf8to)
|
||||
char *from_path_fs, *to_path_fs;
|
||||
static enum playlist_result ret;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(utf8from) || !spl_valid_name(utf8to))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
@@ -454,7 +472,7 @@ spl_rename(const char *utf8from, const char *utf8to)
|
||||
if (from_path_fs != NULL && to_path_fs != NULL)
|
||||
ret = spl_rename_internal(from_path_fs, to_path_fs);
|
||||
else
|
||||
ret = PLAYLIST_RESULT_DISABLED;
|
||||
ret = PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
g_free(from_path_fs);
|
||||
g_free(to_path_fs);
|
||||
|
66
src/tag.c
66
src/tag.c
@@ -64,6 +64,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
|
||||
|
||||
bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
|
||||
|
||||
enum tag_type
|
||||
tag_name_parse(const char *name)
|
||||
{
|
||||
assert(name != NULL);
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||
assert(tag_item_names[i] != NULL);
|
||||
|
||||
if (strcmp(name, tag_item_names[i]) == 0)
|
||||
return (enum tag_type)i;
|
||||
}
|
||||
|
||||
return TAG_NUM_OF_ITEM_TYPES;
|
||||
}
|
||||
|
||||
enum tag_type
|
||||
tag_name_parse_i(const char *name)
|
||||
{
|
||||
assert(name != NULL);
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||
assert(tag_item_names[i] != NULL);
|
||||
|
||||
if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
|
||||
return (enum tag_type)i;
|
||||
}
|
||||
|
||||
return TAG_NUM_OF_ITEM_TYPES;
|
||||
}
|
||||
|
||||
static size_t items_size(const struct tag *tag)
|
||||
{
|
||||
return tag->num_items * sizeof(struct tag_item *);
|
||||
@@ -76,7 +106,7 @@ void tag_lib_init(void)
|
||||
char *temp;
|
||||
char *s;
|
||||
char *c;
|
||||
int i;
|
||||
enum tag_type type;
|
||||
|
||||
/* parse the "metadata_to_use" config parameter below */
|
||||
|
||||
@@ -98,16 +128,18 @@ void tag_lib_init(void)
|
||||
if (*s == '\0')
|
||||
quit = 1;
|
||||
*s = '\0';
|
||||
for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
|
||||
if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
|
||||
ignore_tag_items[i] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
|
||||
|
||||
c = g_strstrip(c);
|
||||
if (*c == 0)
|
||||
continue;
|
||||
|
||||
type = tag_name_parse_i(c);
|
||||
if (type == TAG_NUM_OF_ITEM_TYPES)
|
||||
g_error("error parsing metadata item \"%s\"",
|
||||
c);
|
||||
}
|
||||
|
||||
ignore_tag_items[type] = false;
|
||||
|
||||
s++;
|
||||
c = s;
|
||||
}
|
||||
@@ -247,6 +279,22 @@ tag_merge(const struct tag *base, const struct tag *add)
|
||||
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 *
|
||||
tag_get_value(const struct tag *tag, enum tag_type type)
|
||||
{
|
||||
|
25
src/tag.h
25
src/tag.h
@@ -93,6 +93,22 @@ struct tag {
|
||||
unsigned num_items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the string, and convert it into a #tag_type. Returns
|
||||
* #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
|
||||
*/
|
||||
enum tag_type
|
||||
tag_name_parse(const char *name);
|
||||
|
||||
/**
|
||||
* Parse the string, and convert it into a #tag_type. Returns
|
||||
* #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
|
||||
*
|
||||
* Case does not matter.
|
||||
*/
|
||||
enum tag_type
|
||||
tag_name_parse_i(const char *name);
|
||||
|
||||
/**
|
||||
* Creates an empty #tag.
|
||||
*/
|
||||
@@ -165,6 +181,15 @@ struct tag *tag_dup(const struct tag *tag);
|
||||
struct tag *
|
||||
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"
|
||||
* attribute.
|
||||
|
@@ -19,12 +19,47 @@
|
||||
|
||||
#include "tag_ape.h"
|
||||
#include "tag.h"
|
||||
#include "tag_table.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *const ape_tag_names[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 *
|
||||
tag_ape_load(const char *file)
|
||||
{
|
||||
@@ -36,7 +71,6 @@ tag_ape_load(const char *file)
|
||||
size_t tagLen;
|
||||
size_t size;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
char *key;
|
||||
|
||||
struct {
|
||||
@@ -48,26 +82,6 @@ tag_ape_load(const char *file)
|
||||
unsigned char reserved[8];
|
||||
} footer;
|
||||
|
||||
const char *apeItems[7] = {
|
||||
"title",
|
||||
"artist",
|
||||
"album",
|
||||
"comment",
|
||||
"genre",
|
||||
"track",
|
||||
"year"
|
||||
};
|
||||
|
||||
int tagItems[7] = {
|
||||
TAG_ITEM_TITLE,
|
||||
TAG_ITEM_ARTIST,
|
||||
TAG_ITEM_ALBUM,
|
||||
TAG_ITEM_COMMENT,
|
||||
TAG_ITEM_GENRE,
|
||||
TAG_ITEM_TRACK,
|
||||
TAG_ITEM_DATE,
|
||||
};
|
||||
|
||||
fp = fopen(file, "r");
|
||||
if (!fp)
|
||||
return NULL;
|
||||
@@ -127,17 +141,8 @@ tag_ape_load(const char *file)
|
||||
if (tagLen < size)
|
||||
goto fail;
|
||||
|
||||
/* we only care about utf-8 text tags */
|
||||
if (!(flags & (0x3 << 1))) {
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
|
||||
if (!ret)
|
||||
ret = tag_new();
|
||||
tag_add_item_n(ret, tagItems[i],
|
||||
p, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret = tag_ape_import_item(ret, flags, key, p, size);
|
||||
|
||||
p += size;
|
||||
tagLen -= size;
|
||||
}
|
||||
|
@@ -90,7 +90,7 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4,
|
||||
|
||||
utf8 = (id3_utf8_t *)
|
||||
g_convert_with_fallback((const char*)isostr, -1,
|
||||
encoding, "utf-8",
|
||||
"utf-8", encoding,
|
||||
NULL, NULL, NULL, NULL);
|
||||
if (utf8 == NULL) {
|
||||
g_debug("Unable to convert %s string to UTF-8: '%s'",
|
||||
|
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
|
113
test/run_input.c
113
test/run_input.c
@@ -17,11 +17,16 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "input_stream.h"
|
||||
#include "tag_pool.h"
|
||||
#include "tag_save.h"
|
||||
#include "conf.h"
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
#include "archive_list.h"
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <unistd.h>
|
||||
@@ -36,14 +41,58 @@ my_log_func(const gchar *log_domain, G_GNUC_UNUSED GLogLevelFlags log_level,
|
||||
g_printerr("%s\n", message);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
static int
|
||||
dump_input_stream(struct input_stream *is)
|
||||
{
|
||||
struct input_stream is;
|
||||
bool success;
|
||||
char buffer[4096];
|
||||
size_t num_read;
|
||||
ssize_t num_written;
|
||||
|
||||
/* wait until the stream becomes ready */
|
||||
|
||||
while (!is->ready) {
|
||||
int ret = input_stream_buffer(is);
|
||||
if (ret < 0)
|
||||
/* error */
|
||||
return 2;
|
||||
|
||||
if (ret == 0)
|
||||
/* nothing was buffered - wait */
|
||||
g_usleep(10000);
|
||||
}
|
||||
|
||||
/* print meta data */
|
||||
|
||||
if (is->mime != NULL)
|
||||
g_printerr("MIME type: %s\n", is->mime);
|
||||
|
||||
/* read data and tags from the stream */
|
||||
|
||||
while (!input_stream_eof(is)) {
|
||||
struct tag *tag = input_stream_tag(is);
|
||||
if (tag != NULL) {
|
||||
g_printerr("Received a tag:\n");
|
||||
tag_save(stderr, tag);
|
||||
tag_free(tag);
|
||||
}
|
||||
|
||||
num_read = input_stream_read(is, buffer, sizeof(buffer));
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
num_written = write(1, buffer, num_read);
|
||||
if (num_written <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct input_stream is;
|
||||
int ret;
|
||||
|
||||
if (argc != 2) {
|
||||
g_printerr("Usage: run_input URI\n");
|
||||
return 1;
|
||||
@@ -58,57 +107,33 @@ int main(int argc, char **argv)
|
||||
|
||||
tag_pool_init();
|
||||
config_global_init();
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
archive_plugin_init_all();
|
||||
#endif
|
||||
|
||||
input_stream_global_init();
|
||||
|
||||
/* open the stream and wait until it becomes ready */
|
||||
/* open the stream and dump it */
|
||||
|
||||
success = input_stream_open(&is, argv[1]);
|
||||
if (!success) {
|
||||
if (input_stream_open(&is, argv[1])) {
|
||||
ret = dump_input_stream(&is);
|
||||
input_stream_close(&is);
|
||||
} else {
|
||||
g_printerr("input_stream_open() failed\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
while (!is.ready) {
|
||||
int ret = input_stream_buffer(&is);
|
||||
if (ret < 0)
|
||||
/* error */
|
||||
return 2;
|
||||
|
||||
if (ret == 0)
|
||||
/* nothing was buffered - wait */
|
||||
g_usleep(10000);
|
||||
}
|
||||
|
||||
/* print meta data */
|
||||
|
||||
if (is.mime != NULL)
|
||||
g_printerr("MIME type: %s\n", is.mime);
|
||||
|
||||
/* read data and tags from the stream */
|
||||
|
||||
while (!input_stream_eof(&is)) {
|
||||
struct tag *tag = input_stream_tag(&is);
|
||||
if (tag != NULL) {
|
||||
g_printerr("Received a tag:\n");
|
||||
tag_save(stderr, tag);
|
||||
tag_free(tag);
|
||||
}
|
||||
|
||||
num_read = input_stream_read(&is, buffer, sizeof(buffer));
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
num_written = write(1, buffer, num_read);
|
||||
if (num_written <= 0)
|
||||
break;
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
/* deinitialize everything */
|
||||
|
||||
input_stream_close(&is);
|
||||
input_stream_global_finish();
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
archive_plugin_deinit_all();
|
||||
#endif
|
||||
|
||||
config_global_finish();
|
||||
tag_pool_deinit();
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user