Compare commits

...

19 Commits

Author SHA1 Message Date
Avuton Olrich
da01c6ef5b mpd version 0.16 2010-12-11 04:19:49 -08:00
Max Kellermann
fcd2355f4f Merge branch 'master' of git://git.musicpd.org/avuton/mpd 2010-12-07 18:18:19 +01:00
Max Kellermann
748a8a6f42 tag_id3: support multiple values
Loop over all frames with a specific id, and import all of them - not
just the first one (index 0).
2010-12-07 18:05:44 +01:00
Anton Khirnov
cb9965bab5 command: don't error when sticker list is run on song with no stickers
this is inconsistent with other commands (e.g. find) and seems wrong --
a song with no stickers attached is a perfectly valid state and an empty
list of stickers is also perfectly valid.
2010-12-07 17:32:52 +01:00
Max Kellermann
429ed24c99 tag_ape: support multiple values
One APE tag may contain more than one value, separated by null bytes.
2010-11-24 08:59:04 +01:00
Max Kellermann
1ab46472ab decoder_thread: load APE replay gain from music files 2010-11-18 23:02:30 +01:00
Max Kellermann
f6bbe1332f replay_gain_ape: parse replay gain from APE tags
Based on the APE reader.
2010-11-18 22:26:06 +01:00
Max Kellermann
11613347be tag_ape: move code to ape.c
Generic library for scanning APE tags.  Eliminated one "goto"!
2010-11-18 21:44:24 +01:00
Max Kellermann
8f46f1520c timer: fix integer overflow in timer_delay()
Fixes a regression: for output_plugin.delay(), we added a method to
the timer class which returns the delay in milliseconds.  This fails
to detect negative values, because the unsigned integer is divided by
1000, and then casted to signed.
2010-11-18 21:29:03 +01:00
Avuton Olrich
f2893b0d0f Modify version string to post-release version 0.16~git 2010-11-08 18:57:09 -08:00
Avuton Olrich
c7265f9689 mpd version 0.16~alpha4 2010-11-08 18:57:09 -08:00
Max Kellermann
46ab8d18e2 playlist_song: calculate duration of last CUE track 2010-11-08 20:16:26 +01:00
Max Kellermann
f384f8da93 Merge release 0.15.15 from branch 'v0.15.x'
Conflicts:
	NEWS
	configure.ac
2010-11-08 18:50:22 +01:00
Max Kellermann
23cd8a74be mpd version 0.15.15 2010-11-08 18:48:28 +01:00
Max Kellermann
cc1debc948 output/shout: artist comes first in stream title
After popular demand, I've switched the order of "artist" and "title"
in the stream title.  There is no standard, and there is no reliable
way to parse those from the stream title.
2010-11-08 18:46:14 +01:00
Max Kellermann
5a3aa1262a update_walk: explicitly check for permission problems
Call access() and print an extra error message when EACCES is
returned.  Hopefully this will reduce the number of support requests
due to wrong file permissions.
2010-11-08 18:24:19 +01:00
Max Kellermann
ad52eb236d input/rewind: fix assertion failure
The assertion added in MPD 0.15.14 was too much, it failed when the
MIME type of a stream was NULL.
2010-11-08 10:37:09 +01:00
Avuton Olrich
d2c2cbd0ae Modify version string to post-release version 0.16~git 2010-11-07 06:39:31 -08:00
Avuton Olrich
462bba8e2f Modify version string to post-release version 0.15.15~git 2010-11-06 14:42:03 -07:00
16 changed files with 413 additions and 116 deletions

View File

@@ -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

12
NEWS
View File

@@ -1,4 +1,4 @@
ver 0.16 (20??/??/??)
ver 0.16 (2010/12/11)
* protocol:
- send song modification time to client
- added "update" idle event
@@ -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
@@ -109,6 +112,13 @@ ver 0.16 (20??/??/??)
* make single mode 'sticky'
ver 0.15.15 (2010/11/08)
* input:
- rewind: fix assertion failure
* output:
- shout: artist comes first in stream title
ver 0.15.14 (2010/11/06)
* player_thread: fix assertion failure due to wrong music pipe on seek
* output_thread: fix assertion failure due to race condition in OPEN

View File

@@ -1,5 +1,5 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.16~alpha3, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.16, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
AM_CONFIG_HEADER(config.h)

113
src/ape.c Normal file
View File

@@ -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;
}

42
src/ape.h Normal file
View File

@@ -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

View File

@@ -1715,14 +1715,10 @@ 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);
}
return COMMAND_RETURN_OK;
/* set song song_id id key */

View File

@@ -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"
@@ -297,6 +298,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
return success;
}
/**
* 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.
*/
@@ -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);

View File

@@ -81,7 +81,7 @@ copy_attributes(struct input_rewind *r)
const struct input_stream *src = r->input;
assert(dest != src);
assert(dest->mime != src->mime);
assert(src->mime == NULL || dest->mime != src->mime);
dest->ready = src->ready;
dest->seekable = src->seekable;

View File

@@ -494,7 +494,7 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
}
}
snprintf(dest, size, "%s - %s", title, artist);
snprintf(dest, size, "%s - %s", artist, title);
}
static void my_shout_set_tag(void *data,

View File

@@ -72,6 +72,14 @@ apply_song_metadata(struct song *dest, const struct song *src)
song_free(dest);
}
if (dest->tag != NULL && dest->tag->time > 0 &&
src->start_ms > 0 && src->end_ms == 0 &&
src->start_ms / 1000 < (unsigned)dest->tag->time)
/* the range is open-ended, and the playlist plugin
did not know the total length of the song file
(e.g. last track on a CUE file); fix it up here */
tmp->tag->time = dest->tag->time - src->start_ms / 1000;
return tmp;
}

78
src/replay_gain_ape.c Normal file
View File

@@ -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;
}

32
src/replay_gain_ape.h Normal file
View File

@@ -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

View File

@@ -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_ape_ctx {
struct tag *tag;
};
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;
ctx->tag = tag_ape_import_item(ctx->tag, flags, key,
value, value_length);
return true;
}
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 tag_ape_ctx ctx = { .tag = NULL };
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--;
/* get the value */
if (tagLen < size)
goto fail;
ret = tag_ape_import_item(ret, flags, key, p, size);
p += size;
tagLen -= size;
}
fail:
if (fp)
fclose(fp);
g_free(buffer);
return ret;
tag_ape_scan(file, tag_ape_callback, &ctx);
return ctx.tag;
}

View File

@@ -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,
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 */
@@ -170,6 +169,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,
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 */
@@ -209,6 +221,20 @@ tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
g_free(utf8);
}
/**
* 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.

View File

@@ -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;

View File

@@ -546,6 +546,31 @@ update_container_file( struct directory* directory,
return true;
}
/**
* Checks if the given permissions on the mapped file are given.
*/
static bool
directory_child_access(const struct directory *directory,
const char *name, int mode)
{
#ifdef WIN32
/* access() is useless on WIN32 */
(void)directory;
(void)name;
return true;
#else
char *path = map_directory_child_fs(directory, name);
if (path == NULL)
/* something went wrong, but that isn't a permission
problem */
return true;
bool success = access(path, mode) == 0 || errno != EACCES;
g_free(path);
return success;
#endif
}
static void
update_regular_file(struct directory *directory,
const char *name, const struct stat *st)
@@ -562,6 +587,14 @@ update_regular_file(struct directory *directory,
{
struct song* song = songvec_find(&directory->songs, name);
if (!directory_child_access(directory, name, R_OK)) {
g_warning("no read permissions on %s/%s",
directory_get_path(directory), name);
if (song != NULL)
delete_song(directory, song);
return;
}
if (!(song != NULL && st->st_mtime == song->mtime &&
!walk_discard) &&
plugin->container_scan != NULL)