aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--NEWS3
-rw-r--r--src/ape.c113
-rw-r--r--src/ape.h42
-rw-r--r--src/command.c10
-rw-r--r--src/decoder_thread.c15
-rw-r--r--src/replay_gain_ape.c78
-rw-r--r--src/replay_gain_ape.h32
-rw-r--r--src/tag_ape.c128
-rw-r--r--src/tag_id3.c46
-rw-r--r--src/timer.c2
11 files changed, 361 insertions, 112 deletions
diff --git a/Makefile.am b/Makefile.am
index 92a2a9f55..0966a2a66 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,6 +34,7 @@ mpd_headers = \
src/check.h \
src/notify.h \
src/ack.h \
+ src/ape.h \
src/audio.h \
src/audio_format.h \
src/audio_check.h \
@@ -186,6 +187,7 @@ mpd_headers = \
src/refcount.h \
src/replay_gain_config.h \
src/replay_gain_info.h \
+ src/replay_gain_ape.h \
src/sig_handlers.h \
src/song.h \
src/song_print.h \
@@ -412,6 +414,8 @@ TAG_LIBS = \
$(ID3TAG_LIBS)
TAG_SRC = \
+ src/ape.c \
+ src/replay_gain_ape.c \
src/tag_ape.c
if HAVE_ID3TAG
diff --git a/NEWS b/NEWS
index 6e85e5283..54d4b3302 100644
--- a/NEWS
+++ b/NEWS
@@ -20,7 +20,9 @@ ver 0.16 (20??/??/??)
* tags:
- added tags "ArtistSort", "AlbumArtistSort"
- id3: revised "performer" tag support
+ - id3: support multiple values
- ape: MusicBrainz tags
+ - ape: support multiple values
* decoders:
- don't try a plugin twice (MIME type & suffix)
- don't fall back to "mad" unless no plugin matches
@@ -88,6 +90,7 @@ ver 0.16 (20??/??/??)
- fall back to track gain if album gain is unavailable
- optionally use hardware mixer to apply replay gain
- added mode "auto"
+ - parse replay gain from APE tags
* log unused/unknown block parameters
* removed the deprecated "error_file" option
* save state when stopped
diff --git a/src/ape.c b/src/ape.c
new file mode 100644
index 000000000..5fca98e28
--- /dev/null
+++ b/src/ape.c
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "ape.h"
+
+#include <glib.h>
+
+#include <stdint.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+struct ape_footer {
+ unsigned char id[8];
+ uint32_t version;
+ uint32_t length;
+ uint32_t count;
+ unsigned char flags[4];
+ unsigned char reserved[8];
+};
+
+static bool
+ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
+{
+ /* determine if file has an apeV2 tag */
+ struct ape_footer footer;
+ if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
+ fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
+ memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
+ GUINT32_FROM_LE(footer.version) != 2000)
+ return false;
+
+ /* find beginning of ape tag */
+ size_t remaining = GUINT32_FROM_LE(footer.length);
+ if (remaining <= sizeof(footer) + 10 ||
+ /* refuse to load more than one megabyte of tag data */
+ remaining > 1024 * 1024 ||
+ fseek(fp, -(long)remaining, SEEK_END))
+ return false;
+
+ /* read tag into buffer */
+ remaining -= sizeof(footer);
+ assert(remaining > 10);
+
+ char *buffer = g_malloc(remaining);
+ if (fread(buffer, 1, remaining, fp) != remaining)
+ return false;
+
+ /* read tags */
+ unsigned n = GUINT32_FROM_LE(footer.count);
+ const char *p = buffer;
+ while (n-- && remaining > 10) {
+ size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+ unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
+ p += 4;
+ remaining -= 4;
+
+ /* get the key */
+ const char *key = p;
+ while (remaining > size && *p != '\0') {
+ p++;
+ remaining--;
+ }
+ p++;
+ remaining--;
+
+ /* get the value */
+ if (remaining < size)
+ break;
+
+ if (!callback(flags, key, p, size, ctx))
+ break;
+
+ p += size;
+ remaining -= size;
+ }
+
+ g_free(buffer);
+ return true;
+}
+
+bool
+tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
+{
+ FILE *fp;
+
+ fp = fopen(path_fs, "rb");
+ if (fp == NULL)
+ return false;
+
+ bool success = ape_scan_internal(fp, callback, ctx);
+ fclose(fp);
+ return success;
+}
diff --git a/src/ape.h b/src/ape.h
new file mode 100644
index 000000000..754b9bb2d
--- /dev/null
+++ b/src/ape.h
@@ -0,0 +1,42 @@
+/*
+ * 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_APE_H
+#define MPD_APE_H
+
+#include "check.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
+ const char *value, size_t value_length,
+ void *ctx);
+
+/**
+ * Scans the APE tag values from a file.
+ *
+ * @param path_fs the path of the file in filesystem encoding
+ * @return false if the file could not be opened or if no APE tag is
+ * present
+ */
+bool
+tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
+
+#endif
diff --git a/src/command.c b/src/command.c
index df0146c16..781547b44 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1715,15 +1715,11 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
sticker = sticker_song_get(song);
- if (NULL == sticker) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "no stickers found");
- return COMMAND_RETURN_ERROR;
+ if (sticker) {
+ sticker_print(client, sticker);
+ sticker_free(sticker);
}
- sticker_print(client, sticker);
- sticker_free(sticker);
-
return COMMAND_RETURN_OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index 1a91b6566..10a796967 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -24,6 +24,7 @@
#include "decoder_list.h"
#include "decoder_plugin.h"
#include "decoder_api.h"
+#include "replay_gain_ape.h"
#include "input_stream.h"
#include "player_control.h"
#include "pipe.h"
@@ -298,6 +299,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
}
/**
+ * Attempt to load replay gain data, and pass it to
+ * decoder_replay_gain().
+ */
+static void
+decoder_load_replay_gain(struct decoder *decoder, const char *path_fs)
+{
+ struct replay_gain_info info;
+ if (replay_gain_ape_read(path_fs, &info))
+ decoder_replay_gain(decoder, &info);
+}
+
+/**
* Try decoding a file.
*/
static bool
@@ -312,6 +325,8 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
decoder_unlock(dc);
+ decoder_load_replay_gain(decoder, path_fs);
+
while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
if (plugin->file_decode != NULL) {
decoder_lock(dc);
diff --git a/src/replay_gain_ape.c b/src/replay_gain_ape.c
new file mode 100644
index 000000000..9ae47468f
--- /dev/null
+++ b/src/replay_gain_ape.c
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#include "config.h"
+#include "replay_gain_ape.h"
+#include "replay_gain_info.h"
+#include "ape.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct rg_ape_ctx {
+ struct replay_gain_info *info;
+ bool found;
+};
+
+static bool
+replay_gain_ape_callback(unsigned long flags, const char *key,
+ const char *_value, size_t value_length, void *_ctx)
+{
+ struct rg_ape_ctx *ctx = _ctx;
+
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return true;
+
+ char value[16];
+ if (value_length >= sizeof(value))
+ return true;
+ memcpy(value, _value, value_length);
+ value[value_length] = 0;
+
+ if (g_ascii_strcasecmp(key, "replaygain_track_gain") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_TRACK].gain = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_gain") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_track_peak") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ ctx->found = true;
+ } else if (g_ascii_strcasecmp(key, "replaygain_album_peak") == 0) {
+ ctx->info->tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
+ ctx->found = true;
+ }
+
+ return true;
+}
+
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info)
+{
+ struct rg_ape_ctx ctx = {
+ .info = info,
+ .found = false,
+ };
+
+ return tag_ape_scan(path_fs, replay_gain_ape_callback, &ctx) &&
+ ctx.found;
+}
diff --git a/src/replay_gain_ape.h b/src/replay_gain_ape.h
new file mode 100644
index 000000000..8525ac85e
--- /dev/null
+++ b/src/replay_gain_ape.h
@@ -0,0 +1,32 @@
+/*
+ * 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_REPLAY_GAIN_APE_H
+#define MPD_REPLAY_GAIN_APE_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+struct replay_gain_info;
+
+bool
+replay_gain_ape_read(const char *path_fs, struct replay_gain_info *info);
+
+#endif
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 4841b3138..79facba1b 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -21,11 +21,7 @@
#include "tag_ape.h"
#include "tag.h"
#include "tag_table.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <stdio.h>
+#include "ape.h"
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
[TAG_ALBUM_ARTIST] = "album artist",
@@ -56,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
if (tag == NULL)
tag = tag_new();
- tag_add_item_n(tag, type, value, value_length);
+
+ const char *end = value + value_length;
+ while (true) {
+ /* multiple values are separated by null bytes */
+ const char *n = memchr(value, 0, end - value);
+ if (n != NULL) {
+ if (n > value)
+ tag_add_item_n(tag, type, value, n - value);
+ value = n + 1;
+ } else {
+ if (end > value)
+ tag_add_item_n(tag, type, value, end - value);
+ break;
+ }
+ }
return tag;
}
-struct tag *
-tag_ape_load(const char *file)
-{
- struct tag *ret = NULL;
- FILE *fp;
- int tagCount;
- char *buffer = NULL;
- char *p;
- size_t tagLen;
- size_t size;
- unsigned long flags;
- char *key;
-
- struct {
- unsigned char id[8];
- uint32_t version;
- uint32_t length;
- uint32_t tagCount;
- unsigned char flags[4];
- unsigned char reserved[8];
- } footer;
-
- fp = fopen(file, "rb");
- if (!fp)
- return NULL;
-
- /* determine if file has an apeV2 tag */
- if (fseek(fp, 0, SEEK_END))
- goto fail;
- size = (size_t)ftell(fp);
- if (fseek(fp, size - sizeof(footer), SEEK_SET))
- goto fail;
- if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer))
- goto fail;
- if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0)
- goto fail;
- if (GUINT32_FROM_LE(footer.version) != 2000)
- goto fail;
-
- /* find beginning of ape tag */
- tagLen = GUINT32_FROM_LE(footer.length);
- if (tagLen <= sizeof(footer) + 10)
- goto fail;
- if (tagLen > 1024 * 1024)
- /* refuse to load more than one megabyte of tag data */
- goto fail;
- if (fseek(fp, size - tagLen, SEEK_SET))
- goto fail;
-
- /* read tag into buffer */
- tagLen -= sizeof(footer);
- assert(tagLen > 10);
-
- buffer = g_malloc(tagLen);
- if (fread(buffer, 1, tagLen, fp) != tagLen)
- goto fail;
-
- /* read tags */
- tagCount = GUINT32_FROM_LE(footer.tagCount);
- p = buffer;
- while (tagCount-- && tagLen > 10) {
- size = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- tagLen -= 4;
- flags = GUINT32_FROM_LE(*(const uint32_t *)p);
- p += 4;
- tagLen -= 4;
-
- /* get the key */
- key = p;
- while (tagLen > size && *p != '\0') {
- p++;
- tagLen--;
- }
- p++;
- tagLen--;
+struct tag_ape_ctx {
+ struct tag *tag;
+};
- /* get the value */
- if (tagLen < size)
- goto fail;
+static bool
+tag_ape_callback(unsigned long flags, const char *key,
+ const char *value, size_t value_length, void *_ctx)
+{
+ struct tag_ape_ctx *ctx = _ctx;
- ret = tag_ape_import_item(ret, flags, key, p, size);
+ ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
+ value, value_length);
+ return true;
+}
- p += size;
- tagLen -= size;
- }
+struct tag *
+tag_ape_load(const char *file)
+{
+ struct tag_ape_ctx ctx = { .tag = NULL };
-fail:
- if (fp)
- fclose(fp);
- g_free(buffer);
- return ret;
+ tag_ape_scan(file, tag_ape_callback, &ctx);
+ return ctx.tag;
}
diff --git a/src/tag_id3.c b/src/tag_id3.c
index c1302ca86..9c0a98d40 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -126,17 +126,16 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
* - string list
*/
static void
-tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
- enum tag_type type)
+tag_id3_import_text_frame(struct tag *dest, struct id3_tag *tag,
+ const struct id3_frame *frame,
+ enum tag_type type)
{
- struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
union id3_field const *field;
unsigned int nstrings, i;
- frame = id3_tag_findframe(tag, id, 0);
- if (frame == NULL || frame->nfields != 2)
+ if (frame->nfields != 2)
return;
/* check the encoding field */
@@ -171,6 +170,20 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
}
/**
+ * Import all text frames with the specified id (ID3v2.4.0 section
+ * 4.2). This is a wrapper for tag_id3_import_text_frame().
+ */
+static void
+tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
+ tag_id3_import_text_frame(dest, tag, frame, type);
+}
+
+/**
* Import a "Comment frame" (ID3v2.4.0 section 4.10). It
* contains 4 fields:
*
@@ -180,16 +193,15 @@ tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
* - full string (we use this one)
*/
static void
-tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
- enum tag_type type)
+tag_id3_import_comment_frame(struct tag *dest, struct id3_tag *tag,
+ const struct id3_frame *frame,
+ enum tag_type type)
{
- struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
id3_utf8_t *utf8;
union id3_field const *field;
- frame = id3_tag_findframe(tag, id, 0);
- if (frame == NULL || frame->nfields != 4)
+ if (frame->nfields != 4)
return;
/* for now I only read the 4th field, with the fullstring */
@@ -210,6 +222,20 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
}
/**
+ * Import all comment frames (ID3v2.4.0 section 4.10). This is a
+ * wrapper for tag_id3_import_comment_frame().
+ */
+static void
+tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ const struct id3_frame *frame;
+ for (unsigned i = 0;
+ (frame = id3_tag_findframe(tag, id, i)) != NULL; ++i)
+ tag_id3_import_comment_frame(dest, tag, frame, type);
+}
+
+/**
* Parse a TXXX name, and convert it to a tag_type enum value.
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
*/
diff --git a/src/timer.c b/src/timer.c
index e125b1009..0b3b1198a 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -74,7 +74,7 @@ void timer_add(Timer *timer, int size)
unsigned
timer_delay(const Timer *timer)
{
- int64_t delay = (timer->time - now()) / 1000;
+ int64_t delay = (int64_t)(timer->time - now()) / 1000;
if (delay < 0)
return 0;