Compare commits
42 Commits
v0.16_alph
...
v0.16.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9ad862d3a6 | ||
![]() |
77d71c4ee6 | ||
![]() |
8c0afd8557 | ||
![]() |
2a56300f7b | ||
![]() |
5f06999686 | ||
![]() |
4c09aeb5a1 | ||
![]() |
af892e7e80 | ||
![]() |
0022fb100b | ||
![]() |
4f2d67dfb0 | ||
![]() |
b5645ab29f | ||
![]() |
3149d1abf9 | ||
![]() |
59a417fc84 | ||
![]() |
b75d53413d | ||
![]() |
c44a744c0b | ||
![]() |
76cddfab90 | ||
![]() |
60b4f6b3eb | ||
![]() |
546232b1c0 | ||
![]() |
42c5788de3 | ||
![]() |
fb00e7fddc | ||
![]() |
41fdcf328c | ||
![]() |
144ad7992e | ||
![]() |
a0dd1a1b8b | ||
![]() |
c360e69162 | ||
![]() |
da01c6ef5b | ||
![]() |
fcd2355f4f | ||
![]() |
748a8a6f42 | ||
![]() |
cb9965bab5 | ||
![]() |
429ed24c99 | ||
![]() |
1ab46472ab | ||
![]() |
f6bbe1332f | ||
![]() |
11613347be | ||
![]() |
8f46f1520c | ||
![]() |
f2893b0d0f | ||
![]() |
c7265f9689 | ||
![]() |
46ab8d18e2 | ||
![]() |
f384f8da93 | ||
![]() |
23cd8a74be | ||
![]() |
cc1debc948 | ||
![]() |
5a3aa1262a | ||
![]() |
ad52eb236d | ||
![]() |
d2c2cbd0ae | ||
![]() |
462bba8e2f |
14
Makefile.am
14
Makefile.am
@@ -7,6 +7,8 @@ AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
|
||||
|
||||
bin_PROGRAMS = src/mpd
|
||||
|
||||
noinst_LIBRARIES =
|
||||
|
||||
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
|
||||
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(LIBWRAP_CFLAGS) \
|
||||
@@ -34,6 +36,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 +189,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 +416,8 @@ TAG_LIBS = \
|
||||
$(ID3TAG_LIBS)
|
||||
|
||||
TAG_SRC = \
|
||||
src/ape.c \
|
||||
src/replay_gain_ape.c \
|
||||
src/tag_ape.c
|
||||
|
||||
if HAVE_ID3TAG
|
||||
@@ -428,7 +434,6 @@ DECODER_CFLAGS = \
|
||||
$(SNDFILE_CFLAGS) \
|
||||
$(AUDIOFILE_CFLAGS) \
|
||||
$(LIBMIKMOD_CFLAGS) \
|
||||
$(MODPLUG_CFLAGS) \
|
||||
$(GME_CFLAGS) \
|
||||
$(SIDPLAY_CFLAGS) \
|
||||
$(FLUIDSYNTH_CFLAGS) \
|
||||
@@ -444,7 +449,6 @@ DECODER_LIBS = \
|
||||
$(FLAC_LIBS) \
|
||||
$(SNDFILE_LIBS) \
|
||||
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
|
||||
$(MODPLUG_LIBS) \
|
||||
$(GME_LIBS) \
|
||||
$(SIDPLAY_LIBS) \
|
||||
$(FLUIDSYNTH_LIBS) \
|
||||
@@ -517,7 +521,11 @@ DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MODPLUG
|
||||
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
|
||||
libmodplug_decoder_plugin_a_SOURCES = src/decoder/modplug_decoder_plugin.c
|
||||
libmodplug_decoder_plugin_a_CFLAGS = $(src_mpd_CFLAGS) $(MODPLUG_CFLAGS)
|
||||
libmodplug_decoder_plugin_a_CPPFLAGS = $(src_mpd_CPPFLAGS)
|
||||
noinst_LIBRARIES += libmodplug_decoder_plugin.a
|
||||
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
|
||||
endif
|
||||
|
||||
if ENABLE_SIDPLAY
|
||||
|
33
NEWS
33
NEWS
@@ -1,4 +1,20 @@
|
||||
ver 0.16 (20??/??/??)
|
||||
ver 0.16.1 (2010/01/09)
|
||||
* audio_check: fix parameter in prototype
|
||||
* add void casts to suppress "result unused" warnings (clang)
|
||||
* input:
|
||||
- ffado: disable by default
|
||||
* decoder:
|
||||
- mad: work around build failure on Solaris
|
||||
- resolve modplug vs. libsndfile cflags/headers conflict
|
||||
* output:
|
||||
- solaris: add missing parameter to open_cloexec() cal
|
||||
- osx: fix up audio format first, then apply it to device
|
||||
* player_thread: discard empty chunks while cross-fading
|
||||
* player_thread: fix assertion failure due to early seek
|
||||
* output_thread: fix double lock
|
||||
|
||||
|
||||
ver 0.16 (2010/12/11)
|
||||
* protocol:
|
||||
- send song modification time to client
|
||||
- added "update" idle event
|
||||
@@ -20,7 +36,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 +106,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 +128,18 @@ ver 0.16 (20??/??/??)
|
||||
* make single mode 'sticky'
|
||||
|
||||
|
||||
ver 0.15.16 (2010/??/??)
|
||||
* encoders:
|
||||
- lame: explicitly configure the output sample rate
|
||||
|
||||
|
||||
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
|
||||
|
49
configure.ac
49
configure.ac
@@ -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.1, 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)
|
||||
@@ -13,6 +13,7 @@ dnl Programs
|
||||
dnl ---------------------------------------------------------------------------
|
||||
AC_PROG_CC_C99
|
||||
AC_PROG_CXX
|
||||
AC_PROG_RANLIB
|
||||
|
||||
HAVE_CXX=yes
|
||||
if test x$CXX = xg++; then
|
||||
@@ -157,7 +158,7 @@ AC_ARG_ENABLE(documentation,
|
||||
|
||||
AC_ARG_ENABLE(ffado,
|
||||
AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
|
||||
[enable_ffado=auto])
|
||||
[enable_ffado=no])
|
||||
|
||||
AC_ARG_ENABLE(ffmpeg,
|
||||
AS_HELP_STRING([--enable-ffmpeg],
|
||||
@@ -849,15 +850,6 @@ if test x$enable_modplug = xyes; then
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
||||
|
||||
dnl --------------------------- sndfile/modplug test --------------------------
|
||||
if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
|
||||
dnl If modplug is enabled, enable sndfile only if explicitly
|
||||
dnl requested - modplug's modplug/sndfile.h is known to
|
||||
dnl conflict with libsndfile's sndfile.h.
|
||||
AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
|
||||
enable_sndfile=no
|
||||
fi
|
||||
|
||||
dnl -------------------------------- libsndfile -------------------------------
|
||||
dnl See above test, which may disable this.
|
||||
MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
|
||||
@@ -1495,23 +1487,23 @@ dnl ---------------------------------------------------------------------------
|
||||
echo ''
|
||||
echo '########### MPD CONFIGURATION ############'
|
||||
|
||||
echo -ne '\nArchive support:\n\t'
|
||||
printf '\nArchive support:\n\t'
|
||||
results(bzip2,[bzip2])
|
||||
results(iso9660,[ISO9660])
|
||||
results(zzip,[ZIP])
|
||||
|
||||
if test x$with_zeroconf != xno; then
|
||||
echo -ne '\nAutodiscovery support:\n\t'
|
||||
printf '\nAutodiscovery support:\n\t'
|
||||
results(avahi, [Avahi])
|
||||
results(bonjour, [Bonjour])
|
||||
fi
|
||||
|
||||
echo -ne '\nClient support:\n\t'
|
||||
printf '\nClient support:\n\t'
|
||||
results(ipv6, "IPv6")
|
||||
results(tcp, "TCP")
|
||||
results(un,[UNIX Domain Sockets])
|
||||
|
||||
echo -ne '\nFile format support:\n\t'
|
||||
printf '\nFile format support:\n\t'
|
||||
results(aac, [AAC])
|
||||
results(sidplay, [C64 SID])
|
||||
results(ffmpeg, [FFMPEG])
|
||||
@@ -1519,7 +1511,7 @@ results(flac, [FLAC])
|
||||
results(fluidsynth, [FluidSynth])
|
||||
results(gme, [GME])
|
||||
results(sndfile, [libsndfile])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(mikmod, [MikMod])
|
||||
results(modplug, [MODPLUG])
|
||||
results(mad, [MAD])
|
||||
@@ -1527,23 +1519,23 @@ results(mpg123, [MPG123])
|
||||
results(mp4, [MP4])
|
||||
results(mpc, [Musepack])
|
||||
results(oggflac, [OggFLAC], flac)
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(tremor, [OggTremor])
|
||||
results(vorbis, [OggVorbis])
|
||||
results(audiofile, [WAVE])
|
||||
results(wavpack, [WavPack])
|
||||
results(wildmidi, [WildMidi])
|
||||
|
||||
echo -en '\nOther features:\n\t'
|
||||
printf '\nOther features:\n\t'
|
||||
results(lsr, [libsamplerate])
|
||||
results(inotify, [inotify])
|
||||
results(sqlite, [SQLite])
|
||||
|
||||
echo -en '\nMetadata support:\n\t'
|
||||
printf '\nMetadata support:\n\t'
|
||||
results(cue,[cue])
|
||||
results(id3,[ID3])
|
||||
|
||||
echo -en '\nPlayback support:\n\t'
|
||||
printf '\nPlayback support:\n\t'
|
||||
results(alsa,ALSA)
|
||||
results(ffado,FFADO)
|
||||
results(fifo,FIFO)
|
||||
@@ -1552,14 +1544,14 @@ results(httpd_output,[HTTP Daemon])
|
||||
results(jack,[JACK])
|
||||
results(ao,[libao])
|
||||
results(oss,[OSS])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(openal,[OpenAL])
|
||||
results(osx, [OS X])
|
||||
results(pipe_output, [Pipeline])
|
||||
results(pulse, [PulseAudio])
|
||||
results(mvp, [Media MVP])
|
||||
results(shout, [SHOUTcast])
|
||||
echo -ne '\n\t'
|
||||
printf '\n\t'
|
||||
results(solaris, [Solaris])
|
||||
results(winmm_output, [WinMM])
|
||||
|
||||
@@ -1567,7 +1559,7 @@ if
|
||||
test x$enable_shout = xyes ||
|
||||
test x$enable_recorder = xyes ||
|
||||
test x$enable_httpd_output = xyes; then
|
||||
echo -en '\nStreaming encoder support:\n\t'
|
||||
printf '\nStreaming encoder support:\n\t'
|
||||
results(flac_encoder, [FLAC])
|
||||
results(lame_encoder, [LAME])
|
||||
results(vorbis_encoder, [Ogg Vorbis])
|
||||
@@ -1575,19 +1567,14 @@ if
|
||||
results(wave_encoder, [WAVE])
|
||||
fi
|
||||
|
||||
echo -en '\nStreaming support:\n\t'
|
||||
printf '\nStreaming support:\n\t'
|
||||
results(curl,[CURL])
|
||||
results(lastfm,[Last.FM])
|
||||
results(mms,[MMS])
|
||||
|
||||
echo -ne '\n\n##########################################\n\n'
|
||||
printf '\n\n##########################################\n\n'
|
||||
|
||||
if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
|
||||
AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
|
||||
AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
|
||||
fi
|
||||
|
||||
echo -ne 'Generating files needed for compilation\n'
|
||||
echo 'Generating files needed for compilation'
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Generate files
|
||||
|
12
m4/faad.m4
12
m4/faad.m4
@@ -58,7 +58,7 @@ if test x$enable_aac = xyes; then
|
||||
fi
|
||||
if test x$enable_aac = xyes; then
|
||||
AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
|
||||
int main() {
|
||||
@@ -82,9 +82,9 @@ int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
||||
])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
||||
AC_MSG_CHECKING(that FAAD2 can even be used)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
|
||||
int main() {
|
||||
@@ -113,7 +113,7 @@ int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
||||
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
||||
])
|
||||
fi
|
||||
if test x$enable_aac = xyes; then
|
||||
@@ -136,7 +136,7 @@ if test x$enable_aac = xyes; then
|
||||
CPPFLAGS=$CFLAGS
|
||||
|
||||
AC_MSG_CHECKING(for broken libfaad headers)
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -148,7 +148,7 @@ int main() {
|
||||
faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
||||
return 0;
|
||||
}
|
||||
],
|
||||
])],
|
||||
[AC_MSG_RESULT(correct)],
|
||||
[AC_MSG_RESULT(broken);
|
||||
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
||||
|
@@ -4,9 +4,9 @@ AC_DEFUN([MPD_CHECK_FLAG],[
|
||||
[mpd_check_cflag_$var],[
|
||||
save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $1"
|
||||
AC_COMPILE_IFELSE([
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
int main(void) { return 0; }
|
||||
], [ eval "mpd_check_cflag_$var=yes"
|
||||
])], [ eval "mpd_check_cflag_$var=yes"
|
||||
], [ eval "mpd_check_cflag_$var=no" ])
|
||||
CFLAGS="$save_CFLAGS"
|
||||
])
|
||||
|
@@ -1,19 +1,19 @@
|
||||
AC_DEFUN([results], [
|
||||
dnl This is a hack to allow "with" names, otherwise "enable".
|
||||
num=`expr match $1 'with'`
|
||||
num=`expr $1 : 'with'`
|
||||
if test "$num" != "0"; then
|
||||
var="`echo '$'$1`"
|
||||
else
|
||||
var="`echo '$'enable_$1`"
|
||||
fi
|
||||
|
||||
echo -n '('
|
||||
printf '('
|
||||
if eval "test x$var = xyes"; then
|
||||
echo -n '+'
|
||||
printf '+'
|
||||
elif test -n "$3" && eval "test x$var = x$3"; then
|
||||
echo -n '+'
|
||||
printf '+'
|
||||
else
|
||||
echo -n '-'
|
||||
printf '-'
|
||||
fi
|
||||
echo -n "$2) "
|
||||
printf '%s) ' "$2"
|
||||
])
|
||||
|
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
|
@@ -38,7 +38,7 @@ bool
|
||||
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_sample_format(unsigned sample_format, GError **error_r);
|
||||
audio_check_sample_format(enum sample_format, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_channel_count(unsigned sample_format, GError **error_r);
|
||||
|
@@ -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) {
|
||||
|
@@ -547,14 +547,14 @@ enum {
|
||||
XING_SCALE = 0x00000008L
|
||||
};
|
||||
|
||||
struct version {
|
||||
struct lame_version {
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
};
|
||||
|
||||
struct lame {
|
||||
char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
|
||||
struct version version; /* struct containing just the version */
|
||||
struct lame_version version; /* struct containing just the version */
|
||||
float peak; /* replaygain peak */
|
||||
float track_gain; /* replaygain track gain */
|
||||
float album_gain; /* replaygain album gain */
|
||||
|
@@ -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);
|
||||
|
@@ -30,8 +30,8 @@
|
||||
|
||||
#define DIRECTORY_DIR "directory: "
|
||||
|
||||
#define DEVICE_INARCHIVE (dev_t)(-1)
|
||||
#define DEVICE_CONTAINER (dev_t)(-2)
|
||||
#define DEVICE_INARCHIVE (dev_t)(-1)
|
||||
#define DEVICE_CONTAINER (dev_t)(-2)
|
||||
|
||||
struct directory {
|
||||
struct dirvec children;
|
||||
|
@@ -170,6 +170,13 @@ lame_encoder_setup(struct lame_encoder *encoder, GError **error)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != lame_set_out_samplerate(encoder->gfp,
|
||||
encoder->audio_format.sample_rate)) {
|
||||
g_set_error(error, lame_encoder_quark(), 0,
|
||||
"error setting lame out sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 > lame_init_params(encoder->gfp)) {
|
||||
g_set_error(error, lame_encoder_quark(), 0,
|
||||
"error initializing lame params");
|
||||
|
@@ -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;
|
||||
|
@@ -29,6 +29,9 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "httpd_output"
|
||||
|
||||
struct httpd_client {
|
||||
/**
|
||||
* The httpd output object this client is connected to.
|
||||
|
@@ -214,15 +214,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||
stream_description.mSampleRate = audio_format->sample_rate;
|
||||
stream_description.mFormatID = kAudioFormatLinearPCM;
|
||||
stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
|
||||
#endif
|
||||
|
||||
stream_description.mBytesPerPacket =
|
||||
audio_format_frame_size(audio_format);
|
||||
stream_description.mFramesPerPacket = 1;
|
||||
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
|
||||
stream_description.mChannelsPerFrame = audio_format->channels;
|
||||
|
||||
switch (audio_format->format) {
|
||||
case SAMPLE_FORMAT_S8:
|
||||
@@ -239,6 +230,16 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
|
||||
break;
|
||||
}
|
||||
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
|
||||
#endif
|
||||
|
||||
stream_description.mBytesPerPacket =
|
||||
audio_format_frame_size(audio_format);
|
||||
stream_description.mFramesPerPacket = 1;
|
||||
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
|
||||
stream_description.mChannelsPerFrame = audio_format->channels;
|
||||
|
||||
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0,
|
||||
&stream_description,
|
||||
|
@@ -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,
|
||||
|
@@ -93,7 +93,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
|
||||
|
||||
/* open the device in non-blocking mode */
|
||||
|
||||
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
|
||||
so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK, 0);
|
||||
if (so->fd < 0) {
|
||||
g_set_error(error, solaris_output_quark(), errno,
|
||||
"Failed to open %s: %s",
|
||||
|
@@ -303,7 +303,7 @@ ao_wait(struct audio_output *ao)
|
||||
GTimeVal tv;
|
||||
g_get_current_time(&tv);
|
||||
g_time_val_add(&tv, delay * 1000);
|
||||
g_cond_timed_wait(ao->cond, ao->mutex, &tv);
|
||||
(void)g_cond_timed_wait(ao->cond, ao->mutex, &tv);
|
||||
|
||||
if (ao->command != AO_COMMAND_NONE)
|
||||
return false;
|
||||
@@ -463,12 +463,9 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
|
||||
|
||||
/* don't automatically reopen this device for
|
||||
10 seconds */
|
||||
g_mutex_lock(ao->mutex);
|
||||
|
||||
assert(ao->fail_timer == NULL);
|
||||
ao->fail_timer = g_timer_new();
|
||||
|
||||
g_mutex_unlock(ao->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -356,16 +356,9 @@ player_check_decoder_startup(struct player *player)
|
||||
static bool
|
||||
player_send_silence(struct player *player)
|
||||
{
|
||||
struct music_chunk *chunk;
|
||||
size_t frame_size =
|
||||
audio_format_frame_size(&player->play_audio_format);
|
||||
/* this formula ensures that we don't send
|
||||
partial frames */
|
||||
unsigned num_frames = sizeof(chunk->data) / frame_size;
|
||||
|
||||
assert(audio_format_defined(&player->play_audio_format));
|
||||
|
||||
chunk = music_buffer_allocate(player_buffer);
|
||||
struct music_chunk *chunk = music_buffer_allocate(player_buffer);
|
||||
if (chunk == NULL) {
|
||||
g_warning("Failed to allocate silence buffer");
|
||||
return false;
|
||||
@@ -375,6 +368,12 @@ player_send_silence(struct player *player)
|
||||
chunk->audio_format = player->play_audio_format;
|
||||
#endif
|
||||
|
||||
size_t frame_size =
|
||||
audio_format_frame_size(&player->play_audio_format);
|
||||
/* this formula ensures that we don't send
|
||||
partial frames */
|
||||
unsigned num_frames = sizeof(chunk->data) / frame_size;
|
||||
|
||||
chunk->times = -1.0; /* undefined time stamp */
|
||||
chunk->length = num_frames * frame_size;
|
||||
memset(chunk->data, 0, chunk->length);
|
||||
@@ -396,8 +395,6 @@ static bool player_seek_decoder(struct player *player)
|
||||
{
|
||||
struct song *song = pc.next_song;
|
||||
struct decoder_control *dc = player->dc;
|
||||
double where;
|
||||
bool ret;
|
||||
|
||||
assert(pc.next_song != NULL);
|
||||
|
||||
@@ -413,8 +410,7 @@ static bool player_seek_decoder(struct player *player)
|
||||
|
||||
/* re-start the decoder */
|
||||
player_dc_start(player, player->pipe);
|
||||
ret = player_wait_for_decoder(player);
|
||||
if (!ret) {
|
||||
if (!player_wait_for_decoder(player)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
@@ -435,8 +431,7 @@ static bool player_seek_decoder(struct player *player)
|
||||
/* wait for the decoder to complete initialization */
|
||||
|
||||
while (player->decoder_starting) {
|
||||
ret = player_check_decoder_startup(player);
|
||||
if (!ret) {
|
||||
if (!player_check_decoder_startup(player)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
@@ -445,14 +440,13 @@ static bool player_seek_decoder(struct player *player)
|
||||
|
||||
/* send the SEEK command */
|
||||
|
||||
where = pc.seek_where;
|
||||
double where = pc.seek_where;
|
||||
if (where > pc.total_time)
|
||||
where = pc.total_time - 0.1;
|
||||
if (where < 0.0)
|
||||
where = 0.0;
|
||||
|
||||
ret = dc_seek(dc, where + song->start_ms / 1000.0);
|
||||
if (!ret) {
|
||||
if (!dc_seek(dc, where + song->start_ms / 1000.0)) {
|
||||
/* decoder failure */
|
||||
player_command_finished();
|
||||
return false;
|
||||
@@ -583,14 +577,12 @@ static void player_process_command(struct player *player)
|
||||
static void
|
||||
update_song_tag(struct song *song, const struct tag *new_tag)
|
||||
{
|
||||
struct tag *old_tag;
|
||||
|
||||
if (song_is_file(song))
|
||||
/* don't update tags of local files, only remote
|
||||
streams may change tags dynamically */
|
||||
return;
|
||||
|
||||
old_tag = song->tag;
|
||||
struct tag *old_tag = song->tag;
|
||||
song->tag = tag_dup(new_tag);
|
||||
|
||||
if (old_tag != NULL)
|
||||
@@ -648,15 +640,14 @@ static bool
|
||||
play_next_chunk(struct player *player)
|
||||
{
|
||||
struct decoder_control *dc = player->dc;
|
||||
struct music_chunk *chunk = NULL;
|
||||
unsigned cross_fade_position;
|
||||
bool success;
|
||||
|
||||
if (!audio_output_all_wait(64))
|
||||
/* the output pipe is still large enough, don't send
|
||||
another chunk */
|
||||
return true;
|
||||
|
||||
unsigned cross_fade_position;
|
||||
struct music_chunk *chunk = NULL;
|
||||
if (player->xfade == XFADE_ENABLED &&
|
||||
player_dc_at_next_song(player) &&
|
||||
(cross_fade_position = music_pipe_size(player->pipe))
|
||||
@@ -694,6 +685,19 @@ play_next_chunk(struct player *player)
|
||||
chunk->mix_ratio = nan("");
|
||||
}
|
||||
|
||||
if (music_chunk_is_empty(other_chunk)) {
|
||||
/* the "other" chunk was a music_chunk
|
||||
which had only a tag, but no music
|
||||
data - we cannot cross-fade that;
|
||||
but since this happens only at the
|
||||
beginning of the new song, we can
|
||||
easily recover by throwing it away
|
||||
now */
|
||||
music_buffer_return(player_buffer,
|
||||
other_chunk);
|
||||
other_chunk = NULL;
|
||||
}
|
||||
|
||||
chunk->other = other_chunk;
|
||||
} else {
|
||||
/* there are not enough decoded chunks yet */
|
||||
@@ -732,9 +736,7 @@ play_next_chunk(struct player *player)
|
||||
|
||||
/* play the current chunk */
|
||||
|
||||
success = play_chunk(player->song, chunk, &player->play_audio_format);
|
||||
|
||||
if (!success) {
|
||||
if (!play_chunk(player->song, chunk, &player->play_audio_format)) {
|
||||
music_buffer_return(player_buffer, chunk);
|
||||
|
||||
player_lock();
|
||||
@@ -776,11 +778,9 @@ play_next_chunk(struct player *player)
|
||||
static bool
|
||||
player_song_border(struct player *player)
|
||||
{
|
||||
char *uri;
|
||||
|
||||
player->xfade = XFADE_UNKNOWN;
|
||||
|
||||
uri = song_get_uri(player->song);
|
||||
char *uri = song_get_uri(player->song);
|
||||
g_message("played \"%s\"", uri);
|
||||
g_free(uri);
|
||||
|
||||
@@ -875,16 +875,17 @@ static void do_play(struct decoder_control *dc)
|
||||
|
||||
if (player.decoder_starting) {
|
||||
/* wait until the decoder is initialized completely */
|
||||
bool success;
|
||||
const struct song *song;
|
||||
|
||||
success = player_check_decoder_startup(&player);
|
||||
if (!success)
|
||||
if (!player_check_decoder_startup(&player))
|
||||
break;
|
||||
|
||||
/* seek to the beginning of the range */
|
||||
song = decoder_current_song(dc);
|
||||
const struct song *song = decoder_current_song(dc);
|
||||
if (song != NULL && song->start_ms > 0 &&
|
||||
/* we must not send a seek command until
|
||||
the decoder is initialized
|
||||
completely */
|
||||
!player.decoder_starting &&
|
||||
!dc_seek(dc, song->start_ms / 1000.0))
|
||||
player_dc_stop(&player);
|
||||
|
||||
@@ -1092,10 +1093,9 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
|
||||
|
||||
void player_create(void)
|
||||
{
|
||||
GError *e = NULL;
|
||||
|
||||
assert(pc.thread == NULL);
|
||||
|
||||
GError *e = NULL;
|
||||
pc.thread = g_thread_create(player_task, NULL, true, &e);
|
||||
if (pc.thread == NULL)
|
||||
MPD_ERROR("Failed to spawn player task: %s", e->message);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,7 @@ poison_noaccess(void *p, size_t length)
|
||||
memset(p, 0x01, length);
|
||||
|
||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
||||
VALGRIND_MAKE_MEM_NOACCESS(p, length);
|
||||
(void)VALGRIND_MAKE_MEM_NOACCESS(p, length);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -68,7 +68,7 @@ poison_undefined(void *p, size_t length)
|
||||
memset(p, 0x02, length);
|
||||
|
||||
#ifdef HAVE_VALGRIND_MEMCHECK_H
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(p, length);
|
||||
(void)VALGRIND_MAKE_MEM_UNDEFINED(p, length);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
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.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;
|
||||
}
|
||||
|
@@ -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 */
|
||||
@@ -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,
|
||||
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 */
|
||||
@@ -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.
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user