diff options
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | src/ape.c | 113 | ||||
-rw-r--r-- | src/ape.h | 42 | ||||
-rw-r--r-- | src/command.c | 10 | ||||
-rw-r--r-- | src/decoder_thread.c | 15 | ||||
-rw-r--r-- | src/replay_gain_ape.c | 78 | ||||
-rw-r--r-- | src/replay_gain_ape.h | 32 | ||||
-rw-r--r-- | src/tag_ape.c | 128 | ||||
-rw-r--r-- | src/tag_id3.c | 46 | ||||
-rw-r--r-- | src/timer.c | 2 |
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 @@ -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; |