Compare commits
19 Commits
v0.16_alph
...
v0.16
Author | SHA1 | Date | |
---|---|---|---|
![]() |
da01c6ef5b | ||
![]() |
fcd2355f4f | ||
![]() |
748a8a6f42 | ||
![]() |
cb9965bab5 | ||
![]() |
429ed24c99 | ||
![]() |
1ab46472ab | ||
![]() |
f6bbe1332f | ||
![]() |
11613347be | ||
![]() |
8f46f1520c | ||
![]() |
f2893b0d0f | ||
![]() |
c7265f9689 | ||
![]() |
46ab8d18e2 | ||
![]() |
f384f8da93 | ||
![]() |
23cd8a74be | ||
![]() |
cc1debc948 | ||
![]() |
5a3aa1262a | ||
![]() |
ad52eb236d | ||
![]() |
d2c2cbd0ae | ||
![]() |
462bba8e2f |
@@ -34,6 +34,7 @@ mpd_headers = \
|
|||||||
src/check.h \
|
src/check.h \
|
||||||
src/notify.h \
|
src/notify.h \
|
||||||
src/ack.h \
|
src/ack.h \
|
||||||
|
src/ape.h \
|
||||||
src/audio.h \
|
src/audio.h \
|
||||||
src/audio_format.h \
|
src/audio_format.h \
|
||||||
src/audio_check.h \
|
src/audio_check.h \
|
||||||
@@ -186,6 +187,7 @@ mpd_headers = \
|
|||||||
src/refcount.h \
|
src/refcount.h \
|
||||||
src/replay_gain_config.h \
|
src/replay_gain_config.h \
|
||||||
src/replay_gain_info.h \
|
src/replay_gain_info.h \
|
||||||
|
src/replay_gain_ape.h \
|
||||||
src/sig_handlers.h \
|
src/sig_handlers.h \
|
||||||
src/song.h \
|
src/song.h \
|
||||||
src/song_print.h \
|
src/song_print.h \
|
||||||
@@ -412,6 +414,8 @@ TAG_LIBS = \
|
|||||||
$(ID3TAG_LIBS)
|
$(ID3TAG_LIBS)
|
||||||
|
|
||||||
TAG_SRC = \
|
TAG_SRC = \
|
||||||
|
src/ape.c \
|
||||||
|
src/replay_gain_ape.c \
|
||||||
src/tag_ape.c
|
src/tag_ape.c
|
||||||
|
|
||||||
if HAVE_ID3TAG
|
if HAVE_ID3TAG
|
||||||
|
12
NEWS
12
NEWS
@@ -1,4 +1,4 @@
|
|||||||
ver 0.16 (20??/??/??)
|
ver 0.16 (2010/12/11)
|
||||||
* protocol:
|
* protocol:
|
||||||
- send song modification time to client
|
- send song modification time to client
|
||||||
- added "update" idle event
|
- added "update" idle event
|
||||||
@@ -20,7 +20,9 @@ ver 0.16 (20??/??/??)
|
|||||||
* tags:
|
* tags:
|
||||||
- added tags "ArtistSort", "AlbumArtistSort"
|
- added tags "ArtistSort", "AlbumArtistSort"
|
||||||
- id3: revised "performer" tag support
|
- id3: revised "performer" tag support
|
||||||
|
- id3: support multiple values
|
||||||
- ape: MusicBrainz tags
|
- ape: MusicBrainz tags
|
||||||
|
- ape: support multiple values
|
||||||
* decoders:
|
* decoders:
|
||||||
- don't try a plugin twice (MIME type & suffix)
|
- don't try a plugin twice (MIME type & suffix)
|
||||||
- don't fall back to "mad" unless no plugin matches
|
- 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
|
- fall back to track gain if album gain is unavailable
|
||||||
- optionally use hardware mixer to apply replay gain
|
- optionally use hardware mixer to apply replay gain
|
||||||
- added mode "auto"
|
- added mode "auto"
|
||||||
|
- parse replay gain from APE tags
|
||||||
* log unused/unknown block parameters
|
* log unused/unknown block parameters
|
||||||
* removed the deprecated "error_file" option
|
* removed the deprecated "error_file" option
|
||||||
* save state when stopped
|
* save state when stopped
|
||||||
@@ -109,6 +112,13 @@ ver 0.16 (20??/??/??)
|
|||||||
* make single mode 'sticky'
|
* 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)
|
ver 0.15.14 (2010/11/06)
|
||||||
* player_thread: fix assertion failure due to wrong music pipe on seek
|
* player_thread: fix assertion failure due to wrong music pipe on seek
|
||||||
* output_thread: fix assertion failure due to race condition in OPEN
|
* output_thread: fix assertion failure due to race condition in OPEN
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
AC_PREREQ(2.60)
|
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])
|
AC_CONFIG_SRCDIR([src/main.c])
|
||||||
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
|
AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2 subdir-objects])
|
||||||
AM_CONFIG_HEADER(config.h)
|
AM_CONFIG_HEADER(config.h)
|
||||||
|
113
src/ape.c
Normal file
113
src/ape.c
Normal 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
42
src/ape.h
Normal 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
|
@@ -1715,14 +1715,10 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
sticker = sticker_song_get(song);
|
sticker = sticker_song_get(song);
|
||||||
if (NULL == sticker) {
|
if (sticker) {
|
||||||
command_error(client, ACK_ERROR_NO_EXIST,
|
|
||||||
"no stickers found");
|
|
||||||
return COMMAND_RETURN_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
sticker_print(client, sticker);
|
sticker_print(client, sticker);
|
||||||
sticker_free(sticker);
|
sticker_free(sticker);
|
||||||
|
}
|
||||||
|
|
||||||
return COMMAND_RETURN_OK;
|
return COMMAND_RETURN_OK;
|
||||||
/* set song song_id id key */
|
/* set song song_id id key */
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "decoder_list.h"
|
#include "decoder_list.h"
|
||||||
#include "decoder_plugin.h"
|
#include "decoder_plugin.h"
|
||||||
#include "decoder_api.h"
|
#include "decoder_api.h"
|
||||||
|
#include "replay_gain_ape.h"
|
||||||
#include "input_stream.h"
|
#include "input_stream.h"
|
||||||
#include "player_control.h"
|
#include "player_control.h"
|
||||||
#include "pipe.h"
|
#include "pipe.h"
|
||||||
@@ -297,6 +298,18 @@ decoder_run_stream(struct decoder *decoder, const char *uri)
|
|||||||
return success;
|
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.
|
* Try decoding a file.
|
||||||
*/
|
*/
|
||||||
@@ -312,6 +325,8 @@ decoder_run_file(struct decoder *decoder, const char *path_fs)
|
|||||||
|
|
||||||
decoder_unlock(dc);
|
decoder_unlock(dc);
|
||||||
|
|
||||||
|
decoder_load_replay_gain(decoder, path_fs);
|
||||||
|
|
||||||
while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
|
while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
|
||||||
if (plugin->file_decode != NULL) {
|
if (plugin->file_decode != NULL) {
|
||||||
decoder_lock(dc);
|
decoder_lock(dc);
|
||||||
|
@@ -81,7 +81,7 @@ copy_attributes(struct input_rewind *r)
|
|||||||
const struct input_stream *src = r->input;
|
const struct input_stream *src = r->input;
|
||||||
|
|
||||||
assert(dest != src);
|
assert(dest != src);
|
||||||
assert(dest->mime != src->mime);
|
assert(src->mime == NULL || dest->mime != src->mime);
|
||||||
|
|
||||||
dest->ready = src->ready;
|
dest->ready = src->ready;
|
||||||
dest->seekable = src->seekable;
|
dest->seekable = src->seekable;
|
||||||
|
@@ -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,
|
static void my_shout_set_tag(void *data,
|
||||||
|
@@ -72,6 +72,14 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
|||||||
song_free(dest);
|
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;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
78
src/replay_gain_ape.c
Normal file
78
src/replay_gain_ape.c
Normal 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
32
src/replay_gain_ape.h
Normal 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
|
128
src/tag_ape.c
128
src/tag_ape.c
@@ -21,11 +21,7 @@
|
|||||||
#include "tag_ape.h"
|
#include "tag_ape.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
#include "tag_table.h"
|
#include "tag_table.h"
|
||||||
|
#include "ape.h"
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
|
static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = {
|
||||||
[TAG_ALBUM_ARTIST] = "album artist",
|
[TAG_ALBUM_ARTIST] = "album artist",
|
||||||
@@ -56,101 +52,45 @@ tag_ape_import_item(struct tag *tag, unsigned long flags,
|
|||||||
|
|
||||||
if (tag == NULL)
|
if (tag == NULL)
|
||||||
tag = tag_new();
|
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;
|
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 *
|
struct tag *
|
||||||
tag_ape_load(const char *file)
|
tag_ape_load(const char *file)
|
||||||
{
|
{
|
||||||
struct tag *ret = NULL;
|
struct tag_ape_ctx ctx = { .tag = NULL };
|
||||||
FILE *fp;
|
|
||||||
int tagCount;
|
|
||||||
char *buffer = NULL;
|
|
||||||
char *p;
|
|
||||||
size_t tagLen;
|
|
||||||
size_t size;
|
|
||||||
unsigned long flags;
|
|
||||||
char *key;
|
|
||||||
|
|
||||||
struct {
|
tag_ape_scan(file, tag_ape_callback, &ctx);
|
||||||
unsigned char id[8];
|
return ctx.tag;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@@ -126,17 +126,16 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
|
|||||||
* - string list
|
* - string list
|
||||||
*/
|
*/
|
||||||
static void
|
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)
|
enum tag_type type)
|
||||||
{
|
{
|
||||||
struct id3_frame const *frame;
|
|
||||||
id3_ucs4_t const *ucs4;
|
id3_ucs4_t const *ucs4;
|
||||||
id3_utf8_t *utf8;
|
id3_utf8_t *utf8;
|
||||||
union id3_field const *field;
|
union id3_field const *field;
|
||||||
unsigned int nstrings, i;
|
unsigned int nstrings, i;
|
||||||
|
|
||||||
frame = id3_tag_findframe(tag, id, 0);
|
if (frame->nfields != 2)
|
||||||
if (frame == NULL || frame->nfields != 2)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* check the encoding field */
|
/* 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
|
* Import a "Comment frame" (ID3v2.4.0 section 4.10). It
|
||||||
* contains 4 fields:
|
* 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)
|
* - full string (we use this one)
|
||||||
*/
|
*/
|
||||||
static void
|
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)
|
enum tag_type type)
|
||||||
{
|
{
|
||||||
struct id3_frame const *frame;
|
|
||||||
id3_ucs4_t const *ucs4;
|
id3_ucs4_t const *ucs4;
|
||||||
id3_utf8_t *utf8;
|
id3_utf8_t *utf8;
|
||||||
union id3_field const *field;
|
union id3_field const *field;
|
||||||
|
|
||||||
frame = id3_tag_findframe(tag, id, 0);
|
if (frame->nfields != 4)
|
||||||
if (frame == NULL || frame->nfields != 4)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* for now I only read the 4th field, with the fullstring */
|
/* 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);
|
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.
|
* 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.
|
* Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
|
||||||
|
@@ -74,7 +74,7 @@ void timer_add(Timer *timer, int size)
|
|||||||
unsigned
|
unsigned
|
||||||
timer_delay(const Timer *timer)
|
timer_delay(const Timer *timer)
|
||||||
{
|
{
|
||||||
int64_t delay = (timer->time - now()) / 1000;
|
int64_t delay = (int64_t)(timer->time - now()) / 1000;
|
||||||
if (delay < 0)
|
if (delay < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@@ -546,6 +546,31 @@ update_container_file( struct directory* directory,
|
|||||||
return true;
|
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
|
static void
|
||||||
update_regular_file(struct directory *directory,
|
update_regular_file(struct directory *directory,
|
||||||
const char *name, const struct stat *st)
|
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);
|
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 &&
|
if (!(song != NULL && st->st_mtime == song->mtime &&
|
||||||
!walk_discard) &&
|
!walk_discard) &&
|
||||||
plugin->container_scan != NULL)
|
plugin->container_scan != NULL)
|
||||||
|
Reference in New Issue
Block a user