Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
964804a4c2 | ||
![]() |
92495d2b0b | ||
![]() |
9270829b5b | ||
![]() |
b6243a9945 | ||
![]() |
496f88653d | ||
![]() |
5ef645df97 | ||
![]() |
bf49c9e4e2 | ||
![]() |
0da9c91af2 | ||
![]() |
193e637dd9 | ||
![]() |
928bee933d | ||
![]() |
4d1720c886 | ||
![]() |
8f8ed87327 | ||
![]() |
28a441c977 | ||
![]() |
8cf50b08f2 | ||
![]() |
818b7e0641 | ||
![]() |
e70f40fac1 | ||
![]() |
bc89ca92b4 | ||
![]() |
b968e1b6de | ||
![]() |
6c9f9c136b | ||
![]() |
9bff5f9e36 | ||
![]() |
2bf26a2ff8 | ||
![]() |
e33b50d9c5 | ||
![]() |
21fa44c0d5 | ||
![]() |
44444e1b89 | ||
![]() |
ca450663d0 | ||
![]() |
f3d16f6d1b | ||
![]() |
4464cdcc67 | ||
![]() |
2d61e526de | ||
![]() |
7723c481db | ||
![]() |
0ed10542cc | ||
![]() |
ab830f9afd |
18
NEWS
18
NEWS
@@ -1,3 +1,21 @@
|
||||
ver 0.21.15 (2019/09/25)
|
||||
* decoder
|
||||
- dsdiff, dsf: fix displayed bit rate
|
||||
- mpcdec: fix bogus ReplayGain values
|
||||
* output
|
||||
- solaris: fix build with glibc 2.30
|
||||
|
||||
ver 0.21.14 (2019/08/21)
|
||||
* decoder
|
||||
- sidplay: show track durations in database
|
||||
- sidplay: convert tag values from Windows-1252 charset
|
||||
- sidplay: strip text from "Date" tag
|
||||
* player
|
||||
- fix crash after song change
|
||||
- fix seek position after restarting the decoder
|
||||
* protocol
|
||||
- include command name in error responses
|
||||
|
||||
ver 0.21.13 (2019/08/06)
|
||||
* input
|
||||
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="36"
|
||||
android:versionName="0.21.13">
|
||||
android:versionCode="38"
|
||||
android:versionName="0.21.15">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
|
||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.13'
|
||||
version = '0.21.15'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -824,7 +824,8 @@ The music database
|
||||
albumart
|
||||
size: 1024768
|
||||
binary: 8192
|
||||
<8192 bytes>OK
|
||||
<8192 bytes>
|
||||
OK
|
||||
|
||||
:command:`count {FILTER} [group {GROUPTYPE}]`
|
||||
Count the number of songs and their total playtime in
|
||||
|
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.13',
|
||||
version: '0.21.15',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
|
@@ -15,8 +15,8 @@ libmpdclient = MesonProject(
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||
'lib/libogg.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
||||
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
|
||||
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
|
||||
'lib/libopus.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
|
||||
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.2.tar.xz',
|
||||
'023f10831a97ad93d798f53a3640e55cd564abfeba807ecbe8524dac4fedecd5',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
|
||||
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
|
||||
'http://curl.haxx.se/download/curl-7.65.3.tar.xz',
|
||||
'f2d98854813948d157f6a91236ae34ca4a1b4cb302617cebad263d79b0235fea',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -365,8 +365,8 @@ curl = AutotoolsProject(
|
||||
)
|
||||
|
||||
libexpat = AutotoolsProject(
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
||||
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_7/expat-2.2.7.tar.bz2',
|
||||
'cbc9102f4a31a8dafd42d642e9a3aa31e79a0aedaa1f6efd2795ebc83174ec18',
|
||||
'lib/libexpat.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -392,7 +392,7 @@ libnfs = AutotoolsProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
|
||||
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
|
||||
'https://dl.bintray.com/boostorg/release/1.71.0/source/boost_1_71_0.tar.bz2',
|
||||
'd73a8da01e8bf8c7eda40b4c84915071a8c8a0df4a6734537ddde4a8580524ee',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -38,6 +38,10 @@ struct ReplayGainTuple {
|
||||
return gain > -100;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple Undefined() noexcept {
|
||||
return {-200.0f, 0.0f};
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
||||
};
|
||||
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
|
||||
return track.IsDefined() || album.IsDefined();
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo Undefined() noexcept {
|
||||
return {
|
||||
ReplayGainTuple::Undefined(),
|
||||
ReplayGainTuple::Undefined(),
|
||||
};
|
||||
}
|
||||
|
||||
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
|
||||
return mode == ReplayGainMode::ALBUM
|
||||
? (album.IsDefined() ? album : track)
|
||||
|
@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
|
||||
extern size_t client_max_output_buffer_size;
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line);
|
||||
client_process_line(Client &client, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
static CommandResult
|
||||
client_process_command_list(Client &client, bool list_ok,
|
||||
std::list<std::string> &&list)
|
||||
std::list<std::string> &&list) noexcept
|
||||
{
|
||||
CommandResult ret = CommandResult::OK;
|
||||
unsigned num = 0;
|
||||
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
client_process_line(Client &client, char *line)
|
||||
client_process_line(Client &client, char *line) noexcept
|
||||
{
|
||||
CommandResult ret;
|
||||
|
||||
|
@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
|
||||
|
||||
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
command_available(gcc_unused const Partition &partition,
|
||||
gcc_unused const struct command *cmd)
|
||||
gcc_unused const struct command *cmd) noexcept
|
||||
{
|
||||
#ifdef ENABLE_SQLITE
|
||||
if (StringIsEqual(cmd->cmd, "sticker"))
|
||||
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
|
||||
|
||||
static CommandResult
|
||||
PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
unsigned permission)
|
||||
unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
}
|
||||
|
||||
static CommandResult
|
||||
PrintUnavailableCommands(Response &r, unsigned permission)
|
||||
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
|
||||
{
|
||||
for (unsigned i = 0; i < num_commands; ++i) {
|
||||
const struct command *cmd = &commands[i];
|
||||
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
|
||||
}
|
||||
|
||||
void
|
||||
command_init()
|
||||
command_init() noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
/* ensure that the command list is sorted */
|
||||
@@ -285,8 +286,9 @@ command_init()
|
||||
#endif
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const struct command *
|
||||
command_lookup(const char *name)
|
||||
command_lookup(const char *name) noexcept
|
||||
{
|
||||
unsigned a = 0, b = num_commands, i;
|
||||
|
||||
@@ -308,7 +310,7 @@ command_lookup(const char *name)
|
||||
|
||||
static bool
|
||||
command_check_request(const struct command *cmd, Response &r,
|
||||
unsigned permission, Request args)
|
||||
unsigned permission, Request args) noexcept
|
||||
{
|
||||
if (cmd->permission != (permission & cmd->permission)) {
|
||||
r.FormatError(ACK_ERROR_PERMISSION,
|
||||
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
|
||||
|
||||
static const struct command *
|
||||
command_checked_lookup(Response &r, unsigned permission,
|
||||
const char *cmd_name, Request args)
|
||||
const char *cmd_name, Request args) noexcept
|
||||
{
|
||||
const struct command *cmd = command_lookup(cmd_name);
|
||||
if (cmd == nullptr) {
|
||||
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
|
||||
}
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line)
|
||||
try {
|
||||
command_process(Client &client, unsigned num, char *line) noexcept
|
||||
{
|
||||
Response r(client, num);
|
||||
|
||||
/* get the command name (first word on the line) */
|
||||
@@ -389,34 +391,33 @@ try {
|
||||
char *argv[COMMAND_ARGV_MAX];
|
||||
Request args(argv, 0);
|
||||
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
try {
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
while (true) {
|
||||
if (args.size == COMMAND_ARGV_MAX) {
|
||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
|
||||
argv[args.size++] = a;
|
||||
}
|
||||
|
||||
char *a = tokenizer.NextParam();
|
||||
if (a == nullptr)
|
||||
break;
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
argv[args.size++] = a;
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
if (cmd == nullptr)
|
||||
return CommandResult::ERROR;
|
||||
|
||||
return cmd->handler(client, args, r);
|
||||
} catch (...) {
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
const struct command *cmd =
|
||||
command_checked_lookup(r, client.GetPermission(),
|
||||
cmd_name, args);
|
||||
|
||||
CommandResult ret = cmd
|
||||
? cmd->handler(client, args, r)
|
||||
: CommandResult::ERROR;
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
Response r(client, num);
|
||||
PrintError(r, std::current_exception());
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@@ -25,12 +25,9 @@
|
||||
class Client;
|
||||
|
||||
void
|
||||
command_init();
|
||||
|
||||
void
|
||||
command_finish();
|
||||
command_init() noexcept;
|
||||
|
||||
CommandResult
|
||||
command_process(Client &client, unsigned num, char *line);
|
||||
command_process(Client &client, unsigned num, char *line) noexcept;
|
||||
|
||||
#endif
|
||||
|
@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
|
||||
seek_error = false;
|
||||
SynchronousCommandLocked(DecoderCommand::SEEK);
|
||||
|
||||
while (state == DecoderState::START)
|
||||
/* If the decoder falls back to DecoderState::START,
|
||||
this means that our SEEK command arrived too late,
|
||||
and the decoder had meanwhile finished decoding and
|
||||
went idle. Our SEEK command is finished, but that
|
||||
means only that the decoder thread has launched the
|
||||
decoder. To work around illegal states, we wait
|
||||
until the decoder plugin has become ready. This is
|
||||
a kludge, built on top of the "late seek" kludge.
|
||||
Not exactly elegant, sorry. */
|
||||
WaitForDecoder();
|
||||
|
||||
if (seek_error)
|
||||
throw std::runtime_error("Decoder failed to seek");
|
||||
}
|
||||
|
@@ -455,6 +455,11 @@ static void
|
||||
decoder_run_song(DecoderControl &dc,
|
||||
const DetachedSong &song, const char *uri, Path path_fs)
|
||||
{
|
||||
if (dc.command == DecoderCommand::SEEK)
|
||||
/* if the SEEK command arrived too late, start the
|
||||
decoder at the seek position */
|
||||
dc.start_time = dc.seek_time;
|
||||
|
||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local
|
||||
|
@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
unsigned channels, unsigned sample_rate,
|
||||
const offset_type total_bytes)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
uint8_t buffer[8192];
|
||||
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||
|
||||
cmd = client.SubmitData(is, buffer, nbytes,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
offset_type n_blocks,
|
||||
bool bitreverse)
|
||||
{
|
||||
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||
const size_t block_size = channels * DSF_BLOCK_SIZE;
|
||||
const offset_type start_offset = is.GetOffset();
|
||||
|
||||
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
||||
|
||||
cmd = client.SubmitData(is,
|
||||
interleaved_buffer, block_size,
|
||||
sample_rate / 1000);
|
||||
kbit_rate);
|
||||
++i;
|
||||
}
|
||||
|
||||
|
@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
||||
*dest++ = mpc_to_mpd_sample(*src++);
|
||||
}
|
||||
|
||||
static constexpr ReplayGainTuple
|
||||
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
|
||||
{
|
||||
auto t = ReplayGainTuple::Undefined();
|
||||
|
||||
if (gain != 0 && peak != 0) {
|
||||
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
|
||||
t.peak = pow(10, peak / 256. / 20) / 32767;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static constexpr ReplayGainInfo
|
||||
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
|
||||
{
|
||||
auto rgi = ReplayGainInfo::Undefined();
|
||||
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
|
||||
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
|
||||
return rgi;
|
||||
}
|
||||
|
||||
static void
|
||||
mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
{
|
||||
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||
mpcdec_sample_format,
|
||||
info.channels);
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
rgi.Clear();
|
||||
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
|
||||
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
|
||||
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
|
||||
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
|
||||
|
||||
client.SubmitReplayGain(&rgi);
|
||||
{
|
||||
const auto rgi = ImportMpcdecReplayGain(info);
|
||||
if (rgi.IsDefined())
|
||||
client.SubmitReplayGain(&rgi);
|
||||
}
|
||||
|
||||
client.Ready(audio_format, is.IsSeekable(),
|
||||
SongTime::FromS(mpc_streaminfo_get_length(&info)));
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "lib/icu/Converter.hxx"
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#include "fs/io/FileReader.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@@ -32,6 +33,8 @@
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
static AllocatedString<char>
|
||||
Windows1252ToUTF8(const char *s) noexcept
|
||||
{
|
||||
#ifdef HAVE_ICU_CONVERTER
|
||||
try {
|
||||
std::unique_ptr<IcuConverter>
|
||||
converter(IcuConverter::Create("windows-1252"));
|
||||
|
||||
return converter->ToUTF8(s);
|
||||
} catch (...) { }
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fallback to not transcoding windows-1252 to utf-8, that may result
|
||||
* in invalid utf-8 unless nonprintable characters are replaced.
|
||||
*/
|
||||
auto t = AllocatedString<char>::Duplicate(s);
|
||||
|
||||
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (!IsPrintableASCII(t[i]))
|
||||
t[i] = '?';
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
static AllocatedString<char>
|
||||
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
|
||||
{
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
return info.numberOfInfoStrings() > i
|
||||
const char *s = info.numberOfInfoStrings() > i
|
||||
? info.infoString(i)
|
||||
: nullptr;
|
||||
: "";
|
||||
#else
|
||||
return info.numberOfInfoStrings > i
|
||||
const char *s = info.numberOfInfoStrings > i
|
||||
? info.infoString[i]
|
||||
: nullptr;
|
||||
: "";
|
||||
#endif
|
||||
|
||||
return Windows1252ToUTF8(s);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static AllocatedString<char>
|
||||
GetDateString(const SidTuneInfo &info) noexcept
|
||||
{
|
||||
/*
|
||||
* Field 2 is called <released>, previously used as <copyright>.
|
||||
* It is formatted <year><space><company or author or group>,
|
||||
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
|
||||
* example "1987", "199?", "19??" or "1985-87". The <company or
|
||||
* author or group> may be for example Rob Hubbard. A full field
|
||||
* may be for example "1987 Rob Hubbard".
|
||||
*/
|
||||
AllocatedString<char> release = GetInfoString(info, 2);
|
||||
|
||||
/* Keep the <year> part only for the date. */
|
||||
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
|
||||
if (std::isspace(release[i])) {
|
||||
release[i] = AllocatedString<char>::SENTINEL;
|
||||
break;
|
||||
}
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
|
||||
TagHandler &handler) noexcept
|
||||
{
|
||||
/* title */
|
||||
const char *title = GetInfoString(info, 0);
|
||||
if (title == nullptr)
|
||||
title = "";
|
||||
const auto title = GetInfoString(info, 0);
|
||||
|
||||
if (n_tracks > 1) {
|
||||
const auto tag_title =
|
||||
StringFormat<1024>("%s (%u/%u)",
|
||||
title, track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title);
|
||||
title.c_str(), track, n_tracks);
|
||||
handler.OnTag(TAG_TITLE, tag_title.c_str());
|
||||
} else
|
||||
handler.OnTag(TAG_TITLE, title);
|
||||
handler.OnTag(TAG_TITLE, title.c_str());
|
||||
|
||||
/* artist */
|
||||
const char *artist = GetInfoString(info, 1);
|
||||
if (artist != nullptr)
|
||||
handler.OnTag(TAG_ARTIST, artist);
|
||||
const auto artist = GetInfoString(info, 1);
|
||||
if (!artist.empty())
|
||||
handler.OnTag(TAG_ARTIST, artist.c_str());
|
||||
|
||||
/* date */
|
||||
const char *date = GetInfoString(info, 2);
|
||||
if (date != nullptr)
|
||||
handler.OnTag(TAG_DATE, date);
|
||||
const auto date = GetDateString(info);
|
||||
if (!date.empty())
|
||||
handler.OnTag(TAG_DATE, date.c_str());
|
||||
|
||||
/* track */
|
||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
|
||||
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
|
||||
AddTagHandler h(tag_builder);
|
||||
ScanSidTuneInfo(info, i, n_tracks, h);
|
||||
|
||||
const SignedSongTime duration = get_song_length(tune);
|
||||
if (!duration.IsNegative())
|
||||
h.OnDuration(SongTime(duration));
|
||||
|
||||
char track_name[32];
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
|
@@ -159,6 +159,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
Failure(std::current_exception());
|
||||
return;
|
||||
}
|
||||
|
||||
if (f != in_audio_format || f != output->out_audio_format)
|
||||
@@ -458,7 +459,7 @@ AudioOutputControl::Task() noexcept
|
||||
case Command::RELEASE:
|
||||
if (!open) {
|
||||
/* the output has failed after
|
||||
the PAUSE command was submitted; bail
|
||||
the RELEASE command was submitted; bail
|
||||
out */
|
||||
CommandFinished();
|
||||
break;
|
||||
|
@@ -22,7 +22,6 @@
|
||||
#include "system/FileDescriptor.hxx"
|
||||
#include "system/Error.hxx"
|
||||
|
||||
#include <sys/stropts.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -31,11 +30,18 @@
|
||||
|
||||
#ifdef __sun
|
||||
#include <sys/audio.h>
|
||||
#include <sys/stropts.h>
|
||||
#else
|
||||
|
||||
/* some fake declarations that allow build this plugin on systems
|
||||
other than Solaris, just to see if it compiles */
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifndef I_FLUSH
|
||||
#define I_FLUSH 0
|
||||
#endif
|
||||
|
||||
#define AUDIO_GETINFO 0
|
||||
#define AUDIO_SETINFO 0
|
||||
#define AUDIO_ENCODING_LINEAR 0
|
||||
|
Reference in New Issue
Block a user