(.*?)See also"; }
};
-struct AzLyricsFetcher : public GoogleLyricsFetcher
-{
- virtual const char *name() const override { return "azlyrics.com"; }
-
-protected:
- virtual const char *regex() const override { return "
.*?.*
(.*?)
"; }
-};
-
struct GeniusFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "genius.com"; }
protected:
- virtual const char *regex() const override { return "
(.*?)"; }
+ virtual const char *regex() const override { return "
(.*?)
"; }
};
struct JahLyricsFetcher : public GoogleLyricsFetcher
@@ -142,7 +108,7 @@ struct TekstowoFetcher : public GoogleLyricsFetcher
virtual const char *name() const override { return "tekstowo.pl"; }
protected:
- virtual const char *regex() const override { return "
"; }
};
struct ZeneszovegFetcher : public GoogleLyricsFetcher
@@ -150,13 +116,13 @@ struct ZeneszovegFetcher : public GoogleLyricsFetcher
virtual const char *name() const override { return "zeneszoveg.hu"; }
protected:
- virtual const char *regex() const override { return "
(.*?)
"; }
+ virtual const char *regex() const override { return "
(.*?)
"; }
};
struct InternetLyricsFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "the Internet"; }
- virtual Result fetch(const std::string &artist, const std::string &title) override;
+ virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song) override;
protected:
virtual const char *siteKeyword() const override { return nullptr; }
@@ -168,4 +134,16 @@ struct InternetLyricsFetcher : public GoogleLyricsFetcher
std::string URL;
};
+#ifdef HAVE_TAGLIB_H
+struct TagsLyricsFetcher : public LyricsFetcher
+{
+ virtual const char *name() const override { return "tags"; }
+ virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song) override;
+
+protected:
+ virtual const char *urlTemplate() const override { return ""; }
+ virtual const char *regex() const override { return ""; }
+};
+#endif // HAVE_TAGLIB_H
+
#endif // NCMPCPP_LYRICS_FETCHER_H
diff --git a/src/mpdpp.h b/src/mpdpp.h
index ce23c8357..902606d7a 100644
--- a/src/mpdpp.h
+++ b/src/mpdpp.h
@@ -353,8 +353,14 @@ struct Output
};
template
-struct Iterator: std::iterator
+struct Iterator
{
+ using iterator_category = std::input_iterator_tag;
+ using value_type = ObjectT;
+ using difference_type = std::ptrdiff_t;
+ using pointer = ObjectT *;
+ using reference = ObjectT &;
+
// shared state of the iterator
struct State
{
diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp
index 87bd54475..533b31b55 100644
--- a/src/ncmpcpp.cpp
+++ b/src/ncmpcpp.cpp
@@ -219,7 +219,10 @@ int main(int argc, char **argv)
try
{
auto k = Bindings.get(input);
- std::any_of(k.first, k.second, std::bind(&Binding::execute, ph::_1));
+ [[maybe_unused]] bool executed = std::any_of(
+ k.first,
+ k.second,
+ std::bind(&Binding::execute, ph::_1));
}
catch (ConversionError &e)
{
diff --git a/src/screens/lyrics.cpp b/src/screens/lyrics.cpp
index d34ba617b..70ea26cb6 100644
--- a/src/screens/lyrics.cpp
+++ b/src/screens/lyrics.cpp
@@ -151,7 +151,7 @@ boost::optional downloadLyrics(
<< NC::Format::NoBold << "... ";
}
}
- auto result_ = fetcher_->fetch(s_artist, s_title);
+ auto result_ = fetcher_->fetch(s_artist, s_title, s);
if (result_.first == false)
{
if (shared_buffer)
@@ -361,6 +361,25 @@ void Lyrics::toggleFetcher()
Statusbar::print("Using all lyrics fetchers");
}
+/* For HTTP(S) streams, fetchInBackground makes ncmpcpp crash: the lyrics_file
+ * gets set to the stream URL, which is too long, hence
+ * boost::filesystem::exists() fails.
+ * Possible solutions:
+ * - truncate the URL and use that as a filename. Problem: resulting filename
+ * might not be unique.
+ * - generate filenames in a different way for streams. Problem: what is a good
+ * method for this? Perhaps hashing -- but then the lyrics filenames are not
+ * informative.
+ * - skip fetching lyrics for streams with URLs that are too long. Problem:
+ * this leads to inconsistent behavior since lyrics will be fetched for some
+ * streams but not others.
+ * - avoid fetching lyrics for streams altogether.
+ *
+ * We choose the last solution, and ignore streams when fetching lyrics. This
+ * is because fetching lyrics for a stream may not be accurate (streams do not
+ * always provide the necessary metadata to look up lyrics reliably).
+ * Furthermore, fetching lyrics for streams is not necessarily desirable.
+ */
void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
{
auto consumer_impl = [this] {
@@ -377,7 +396,10 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
break;
}
lyrics_file = lyricsFilename(consumer->songs.front().song());
- if (!boost::filesystem::exists(lyrics_file))
+
+ // For long filenames (e.g. http(s) stream URLs), boost::filesystem::exists() fails.
+ // This if condition is fine, because evaluation order is guaranteed.
+ if (!consumer->songs.front().song().isStream() && !boost::filesystem::exists(lyrics_file))
{
cs = consumer->songs.front();
if (cs.notify())
@@ -389,7 +411,7 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
}
consumer->songs.pop();
}
- if (!cs.song().empty())
+ if (!cs.song().empty() && !cs.song().isStream())
{
auto lyrics = downloadLyrics(cs.song(), nullptr, nullptr, m_fetcher);
if (lyrics)
diff --git a/src/screens/media_library.cpp b/src/screens/media_library.cpp
index df2b58662..2cdfceef1 100644
--- a/src/screens/media_library.cpp
+++ b/src/screens/media_library.cpp
@@ -98,7 +98,7 @@ bool MoveToAlbum(NC::Menu &albums, const std::string &primary_tag, c
struct SortSongs {
typedef NC::Menu::Item SongItem;
- static const std::array GetFuns;
+ static const std::array GetFuns;
LocaleStringComparison m_cmp;
@@ -117,26 +117,16 @@ struct SortSongs {
return ret < 0;
}
- // Sort by track numbers.
- try {
- ret = boost::lexical_cast(a.getTags(&MPD::Song::getTrackNumber))
- - boost::lexical_cast(b.getTags(&MPD::Song::getTrackNumber));
- } catch (boost::bad_lexical_cast &) {
- ret = a.getTrackNumber().compare(b.getTrackNumber());
- }
- if (ret != 0)
- return ret < 0;
-
- // If track numbers are equal, sort by the display format.
return Format::stringify(Config.song_library_format, &a)
< Format::stringify(Config.song_library_format, &b);
}
};
-const std::array SortSongs::GetFuns = {{
+const std::array SortSongs::GetFuns = {{
&MPD::Song::getDate,
&MPD::Song::getAlbum,
- &MPD::Song::getDisc
+ &MPD::Song::getDisc,
+ &MPD::Song::getTrackNumber,
}};
class SortAlbumEntries {
@@ -346,9 +336,9 @@ void MediaLibrary::update()
for (const auto &album : albums)
{
auto entry = AlbumEntry(
- Album(std::move(std::get<0>(album.first)),
- std::move(std::get<1>(album.first)),
- std::move(std::get<2>(album.first)),
+ Album(std::get<0>(album.first),
+ std::get<1>(album.first),
+ std::get<2>(album.first),
album.second));
if (idx < Albums.size())
Albums[idx].value() = std::move(entry);
@@ -445,8 +435,8 @@ void MediaLibrary::update()
{
auto entry = AlbumEntry(
Album(primary_tag,
- std::move(std::get<0>(album.first)),
- std::move(std::get<1>(album.first)),
+ std::get<0>(album.first),
+ std::get<1>(album.first),
album.second));
if (idx < Albums.size())
{
diff --git a/src/screens/tag_editor.cpp b/src/screens/tag_editor.cpp
index d754f4990..5ad68382b 100644
--- a/src/screens/tag_editor.cpp
+++ b/src/screens/tag_editor.cpp
@@ -798,13 +798,16 @@ void TagEditor::runAction()
Statusbar::print("Writing changes...");
for (auto it = EditedSongs.begin(); it != EditedSongs.end(); ++it)
{
- Statusbar::printf("Writing tags in \"%1%\"...", (*it)->getName());
- if (!Tags::write(**it))
+ if ((*it)->isModified())
{
- Statusbar::printf("Error while writing tags to \"%1%\": %2%",
- (*it)->getName(), strerror(errno));
- success = 0;
- break;
+ Statusbar::printf("Writing tags in \"%1%\"...", (*it)->getName());
+ if (!Tags::write(**it))
+ {
+ Statusbar::printf("Error while writing tags to \"%1%\": %2%",
+ (*it)->getName(), strerror(errno));
+ success = 0;
+ break;
+ }
}
}
if (success)
diff --git a/src/screens/visualizer.cpp b/src/screens/visualizer.cpp
index 26eac7bfb..66fc546d1 100644
--- a/src/screens/visualizer.cpp
+++ b/src/screens/visualizer.cpp
@@ -83,7 +83,8 @@ Visualizer::Visualizer()
HZ_MIN(Config.visualizer_spectrum_hz_min),
HZ_MAX(Config.visualizer_spectrum_hz_max),
GAIN(Config.visualizer_spectrum_gain),
- SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█"))
+ SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█")),
+ SMOOTH_CHARS_FLIPPED(ToWString("▔🮂🮃🮄🬎🮅🮆█")) // https://unicode.org/charts/PDF/U1FB00.pdf
#endif
{
InitDataSource();
@@ -95,7 +96,7 @@ Visualizer::Visualizer()
memset(m_fftw_input, 0, sizeof(double)*DFT_TOTAL_SIZE);
m_fftw_output = static_cast(fftw_malloc(sizeof(fftw_complex)*m_fftw_results));
m_fftw_plan = fftw_plan_dft_r2c_1d(DFT_TOTAL_SIZE, m_fftw_input, m_fftw_output, FFTW_ESTIMATE);
- m_dft_logspace.reserve(500);
+ m_dft_freqspace.reserve(500);
m_bar_heights.reserve(100);
# endif // HAVE_FFTW3_H
}
@@ -107,7 +108,7 @@ void Visualizer::switchTo()
m_reset_output = true;
drawHeader();
# ifdef HAVE_FFTW3_H
- GenLogspace();
+ GenFreqSpace();
m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H
}
@@ -121,7 +122,7 @@ void Visualizer::resize()
hasToBeResized = 0;
InitVisualization();
# ifdef HAVE_FFTW3_H
- GenLogspace();
+ GenFreqSpace();
m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H
}
@@ -420,7 +421,7 @@ void Visualizer::DrawSoundEllipseStereo(const int16_t *buf_left, const int16_t *
auto c = toColor(sqrt(x*x + 4*y*y), radius, true);
w << NC::XY(left_half_width + x, top_half_height + y)
<< c
- << Config.visualizer_chars[1]
+ << Config.visualizer_chars[0]
<< NC::FormattedColor::End<>(c);
}
}
@@ -449,7 +450,7 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
const size_t win_width = w.getWidth();
size_t cur_bin = 0;
- while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[0])
+ while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_freqspace[0])
++cur_bin;
for (size_t x = 0; x < win_width; ++x)
{
@@ -458,10 +459,10 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// accumulate bins
size_t count = 0;
// check right bound
- while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[x])
+ while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_freqspace[x])
{
// check left bound if not first index
- if (x == 0 || Bin2Hz(cur_bin) >= m_dft_logspace[x-1])
+ if (x == 0 || Bin2Hz(cur_bin) >= m_dft_freqspace[x-1])
{
bar_height += m_freq_magnitudes[cur_bin];
++count;
@@ -475,8 +476,19 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// average bins
bar_height /= count;
- // log scale bar heights
- bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE;
+ // apply scaling to bar heights
+ if (Config.visualizer_spectrum_log_scale_y) {
+ bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE;
+ } else {
+ // apply gain
+ bar_height *= pow(10, 1.8 + GAIN / 20);
+ // buff higher frequencies
+ bar_height *= log2(2 + x) * 80.0/win_width;
+ // moderately normalize the heights
+ bar_height = pow(bar_height, 0.65);
+
+ //bar_height = pow(10, 1 + GAIN / 20) * bar_height;
+ }
// Scale bar height between 0 and height
bar_height = bar_height > 0 ? bar_height * height : 0;
bar_height = bar_height > height ? height : bar_height;
@@ -498,7 +510,12 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
++h_idx;
} else {
// data point does not exist, need to interpolate
- h = Interpolate(x, h_idx);
+ if (Config.visualizer_spectrum_log_scale_x) {
+ h = InterpolateCubic(x, h_idx);
+ } else {
+ h = std::min(InterpolateLinear(x, h_idx), height / 1.0);
+ //h = 0;
+ }
}
for (size_t j = 0; j < h; ++j)
@@ -518,8 +535,12 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
} else {
// fractional height
if (flipped) {
- ch = SMOOTH_CHARS[size-idx-2];
- color = NC::FormattedColor(color.color(), {NC::Format::Reverse});
+ if (Config.visualizer_spectrum_smooth_look_legacy_chars) {
+ ch = SMOOTH_CHARS_FLIPPED[idx];
+ } else {
+ ch = SMOOTH_CHARS[size-idx-2];
+ color = NC::FormattedColor(color.color(), {NC::Format::Reverse});
+ }
} else {
ch = SMOOTH_CHARS[idx];
}
@@ -544,7 +565,7 @@ void Visualizer::DrawFrequencySpectrumStereo(const int16_t *buf_left, const int1
DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height);
}
-double Visualizer::Interpolate(size_t x, size_t h_idx)
+double Visualizer::InterpolateCubic(size_t x, size_t h_idx)
{
const double x_next = m_bar_heights[h_idx].first;
const double h_next = m_bar_heights[h_idx].second;
@@ -589,6 +610,33 @@ double Visualizer::Interpolate(size_t x, size_t h_idx)
return h_next;
}
+double Visualizer::InterpolateLinear(size_t x, size_t h_idx)
+{
+ const double x_next = m_bar_heights[h_idx].first;
+ const double h_next = m_bar_heights[h_idx].second;
+
+ double dh = 0;
+ if (h_idx == 0) {
+ // no data points on the left, linear extrapolation
+ if (h_idx < m_bar_heights.size()) {
+ const double x_next2 = m_bar_heights[h_idx+1].first;
+ const double h_next2 = m_bar_heights[h_idx+1].second;
+ dh = (h_next2 - h_next) / (x_next2 - x_next);
+ }
+ return h_next - dh * (x_next - x);
+ } else if (h_idx < m_bar_heights.size()) {
+ // simple linear interpolation
+ const double x_prev = m_bar_heights[h_idx-1].first;
+ const double h_prev = m_bar_heights[h_idx-1].second;
+
+ const double m = (h_next - h_prev) / (x_next - x_prev);
+ return h_prev + m * (x - x_prev);
+ }
+
+ // no data points on the right: don't interpolate
+ return h_next;
+}
+
void Visualizer::ApplyWindow(double *output, const int16_t *input, ssize_t samples)
{
// Use Blackman window for low sidelobes and fast sidelobe rolloff
@@ -617,10 +665,36 @@ void Visualizer::GenLogspace()
const size_t win_width = w.getWidth();
const size_t left_bins = (log10(HZ_MIN) - win_width*log10(HZ_MIN)) / (log10(HZ_MIN) - log10(HZ_MAX));
// Generate logspaced frequencies
- m_dft_logspace.resize(win_width);
- const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_logspace.size() - 1);
- for (size_t i = left_bins; i < m_dft_logspace.size() + left_bins; ++i) {
- m_dft_logspace[i - left_bins] = pow(10, i * log_scale);
+ m_dft_freqspace.resize(win_width);
+ const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_freqspace.size() - 1);
+ for (size_t i = left_bins; i < m_dft_freqspace.size() + left_bins; ++i) {
+ m_dft_freqspace[i - left_bins] = pow(10, i * log_scale);
+ }
+}
+
+// Generate vector of linearly-spaced frequencies from HZ_MIN to HZ_MAX
+void Visualizer::GenLinspace()
+{
+ // Calculate number of extra bins needed between 0 HZ and HZ_MIN
+ const size_t win_width = w.getWidth();
+ const size_t left_bins = (HZ_MIN - win_width * HZ_MIN) / (HZ_MIN - HZ_MAX);
+ // Generate linspaced frequencies
+ m_dft_freqspace.resize(win_width);
+ const double lin_scale = HZ_MAX / (left_bins + m_dft_freqspace.size() - 1);
+ for (size_t i = left_bins; i < m_dft_freqspace.size() + left_bins; ++i) {
+ m_dft_freqspace[i - left_bins] = i * lin_scale;
+ }
+}
+
+// Generate vector of spectrum frequencies from HZ_MIN to HZ_MAX
+// Frequencies are (not) log-scaled depending on
+// Config.visualizer_spectrum_log_scale_x
+void Visualizer::GenFreqSpace()
+{
+ if (Config.visualizer_spectrum_log_scale_x) {
+ GenLogspace();
+ } else {
+ GenLinspace();
}
}
#endif // HAVE_FFTW3_H
diff --git a/src/screens/visualizer.h b/src/screens/visualizer.h
index 170a1ed7b..3a7a65ba7 100644
--- a/src/screens/visualizer.h
+++ b/src/screens/visualizer.h
@@ -76,8 +76,11 @@ struct Visualizer: Screen, Tabbable
void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t);
void ApplyWindow(double *, const int16_t *, ssize_t);
void GenLogspace();
+ void GenLinspace();
+ void GenFreqSpace();
double Bin2Hz(size_t);
- double Interpolate(size_t, size_t);
+ double InterpolateCubic(size_t, size_t);
+ double InterpolateLinear(size_t, size_t);
# endif // HAVE_FFTW3_H
void InitDataSource();
@@ -113,7 +116,8 @@ struct Visualizer: Screen, Tabbable
const double HZ_MAX;
const double GAIN;
const std::wstring SMOOTH_CHARS;
- std::vector m_dft_logspace;
+ const std::wstring SMOOTH_CHARS_FLIPPED;
+ std::vector m_dft_freqspace;
std::vector> m_bar_heights;
std::vector m_freq_magnitudes;
diff --git a/src/settings.cpp b/src/settings.cpp
index 7b9ebf9f5..ddde73e07 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -253,6 +253,7 @@ bool Configuration::read(const std::vector &config_paths, bool igno
});
p.add("visualizer_autoscale", &visualizer_autoscale, "no", yes_no);
p.add("visualizer_spectrum_smooth_look", &visualizer_spectrum_smooth_look, "yes", yes_no);
+ p.add("visualizer_spectrum_smooth_look_legacy_chars", &visualizer_spectrum_smooth_look_legacy_chars, "yes", yes_no);
p.add("visualizer_spectrum_dft_size", &visualizer_spectrum_dft_size,
"2", [](std::string v) {
auto result = verbose_lexical_cast(v);
@@ -277,6 +278,8 @@ bool Configuration::read(const std::vector &config_paths, bool igno
lowerBoundCheck(result, Config.visualizer_spectrum_hz_min+1);
return result;
});
+ p.add("visualizer_spectrum_log_scale_x", &visualizer_spectrum_log_scale_x, "yes", yes_no);
+ p.add("visualizer_spectrum_log_scale_y", &visualizer_spectrum_log_scale_y, "yes", yes_no);
p.add("visualizer_color", &visualizer_colors,
"blue, cyan, green, yellow, magenta, red", list_of);
p.add("system_encoding", &system_encoding, "", [](std::string encoding) {
@@ -458,9 +461,27 @@ bool Configuration::read(const std::vector &config_paths, bool igno
p.add("titles_visibility", &titles_visibility, "yes", yes_no);
p.add("header_text_scrolling", &header_text_scrolling, "yes", yes_no);
p.add("cyclic_scrolling", &use_cyclic_scrolling, "no", yes_no);
- p.add("lyrics_fetchers", &lyrics_fetchers,
- "azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet",
- list_of);
+ p.add("lyrics_fetchers", nullptr,
+#ifdef HAVE_TAGLIB_H
+ "tags, "
+#endif
+ "genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet", [this](std::string v) {
+ lyrics_fetchers = list_of(v, [](std::string s) {
+ LyricsFetcher_ fetcher;
+ try {
+ fetcher = boost::lexical_cast(s);
+ } catch (boost::bad_lexical_cast &) {
+ std::clog << "Unknown lyrics fetcher: " << s << "\n";
+ }
+ return fetcher;
+ });
+ auto last = std::remove_if(
+ lyrics_fetchers.begin(), lyrics_fetchers.end(),
+ [](const auto &f) { return f.get() == nullptr; });
+ lyrics_fetchers.erase(last, lyrics_fetchers.end());
+ if (lyrics_fetchers.empty())
+ invalid_value(v);
+ });
p.add("follow_now_playing_lyrics", &now_playing_lyrics, "no", yes_no);
p.add("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background,
"no", yes_no);
diff --git a/src/settings.h b/src/settings.h
index bc22b5887..e9a8af087 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -86,10 +86,13 @@ struct Configuration
size_t visualizer_fps;
bool visualizer_autoscale;
bool visualizer_spectrum_smooth_look;
+ bool visualizer_spectrum_smooth_look_legacy_chars;
uint32_t visualizer_spectrum_dft_size;
double visualizer_spectrum_gain;
double visualizer_spectrum_hz_min;
double visualizer_spectrum_hz_max;
+ bool visualizer_spectrum_log_scale_x;
+ bool visualizer_spectrum_log_scale_y;
std::string pattern;
diff --git a/src/song.cpp b/src/song.cpp
index 70822d109..c274d43f1 100644
--- a/src/song.cpp
+++ b/src/song.cpp
@@ -293,7 +293,9 @@ bool Song::isFromDatabase() const
bool Song::isStream() const
{
assert(m_song);
- return !strncmp(mpd_song_get_uri(m_song.get()), "http://", 7);
+ const char *song_uri = mpd_song_get_uri(m_song.get());
+ // Stream schemas: http, https
+ return !strncmp(song_uri, "http://", 7) || !strncmp(song_uri, "https://", 8);
}
bool Song::empty() const
@@ -308,7 +310,7 @@ std::string Song::ShowTime(unsigned length)
int minutes = length/60;
length -= minutes*60;
int seconds = length;
-
+
std::string result;
if (hours > 0)
result = (boost::format("%d:%02d:%02d") % hours % minutes % seconds).str();
diff --git a/src/statusbar.h b/src/statusbar.h
index 0cadaf566..b14c21376 100644
--- a/src/statusbar.h
+++ b/src/statusbar.h
@@ -24,7 +24,6 @@
#include
#include "curses/window.h"
#include "settings.h"
-#include "gcc.h"
#include "interfaces.h"
namespace Progressbar {
diff --git a/src/tags.cpp b/src/tags.cpp
index 6cd6a8c27..d74f56c75 100644
--- a/src/tags.cpp
+++ b/src/tags.cpp
@@ -123,12 +123,12 @@ void writeCommonTags(const MPD::MutableSong &s, TagLib::Tag *tag)
tag->setArtist(ToWString(s.getArtist()));
tag->setAlbum(ToWString(s.getAlbum()));
try {
- tag->setYear(boost::lexical_cast(s.getDate()));
+ tag->setYear(boost::lexical_cast(s.getDate()));
} catch (boost::bad_lexical_cast &) {
std::cerr << "writeCommonTags: couldn't write 'year' tag to '" << s.getURI() << "' as it's not a positive integer\n";
}
try {
- tag->setTrack(boost::lexical_cast(s.getTrack()));
+ tag->setTrack(boost::lexical_cast(s.getTrack()));
} catch (boost::bad_lexical_cast &) {
std::cerr << "writeCommonTags: couldn't write 'track' tag to '" << s.getURI() << "' as it's not a positive integer\n";
}
@@ -174,6 +174,7 @@ void writeID3v2Tags(const MPD::MutableSong &s, TagLib::ID3v2::Tag *tag)
void writeXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag)
{
auto writeXiph = [&](const TagLib::String &type, const TagLib::StringList &list) {
+ tag->removeFields(type);
for (auto it = list.begin(); it != list.end(); ++it)
tag->addField(type, *it, it == list.begin());
};
@@ -261,7 +262,7 @@ void read(mpd_song *s)
if (f.isNull())
return;
- setAttribute(s, "Time", boost::lexical_cast(f.audioProperties()->length()));
+ setAttribute(s, "Time", boost::lexical_cast(f.audioProperties()->lengthInSeconds()));
if (auto mpeg_file = dynamic_cast(f.file()))
{
@@ -301,15 +302,9 @@ bool write(MPD::MutableSong &s)
if (f.isNull())
return false;
- bool saved = false;
if (auto mpeg_file = dynamic_cast(f.file()))
{
writeID3v2Tags(s, mpeg_file->ID3v2Tag(true));
- // write id3v2.4 tags only
- if (!mpeg_file->save(TagLib::MPEG::File::ID3v2, true, 4, false))
- return false;
- // do not call generic save() as it will duplicate tags
- saved = true;
}
else if (auto vorbis_file = dynamic_cast(f.file()))
{
@@ -326,7 +321,7 @@ bool write(MPD::MutableSong &s)
else
writeCommonTags(s, f.tag());
- if (!saved && !f.save())
+ if (!f.save())
return false;
// TODO: move this somewhere else
diff --git a/src/utility/comparators.cpp b/src/utility/comparators.cpp
index a9c0d3394..3959962b2 100644
--- a/src/utility/comparators.cpp
+++ b/src/utility/comparators.cpp
@@ -34,10 +34,23 @@ bool hasTheWord(const std::string &s)
&& (s[3] == ' ');
}
+long long strToLL(const char *s, size_t len)
+{
+ long long n = 0;
+ while (len--)
+ n = 10*n + *s++ - '0';
+ return n;
+}
+
}
int LocaleStringComparison::compare(const char *a, size_t a_len, const char *b, size_t b_len) const
{
+ // If both strings are numbers, compare them as such.
+ if ( a_len > 0 && std::all_of(a, a + a_len, isdigit)
+ && b_len > 0 && std::all_of(b, b + b_len, isdigit))
+ return strToLL(a, a_len) - strToLL(b, b_len);
+
size_t ac_off = 0, bc_off = 0;
if (m_ignore_the)
{
diff --git a/src/utility/conversion.h b/src/utility/conversion.h
index 80d0a8391..109d161ee 100644
--- a/src/utility/conversion.h
+++ b/src/utility/conversion.h
@@ -26,7 +26,6 @@
#include
#include "config.h"
-#include "gcc.h"
struct ConversionError
{
@@ -44,21 +43,21 @@ struct OutOfBounds : std::exception
const std::string &errorMessage() { return m_error_message; }
template
- GNUC_NORETURN static void raise(const Type &value, const Type &lbound, const Type &ubound)
+ [[noreturn]] static void raise(const Type &value, const Type &lbound, const Type &ubound)
{
throw OutOfBounds((boost::format(
"value is out of bounds ([%1%, %2%] expected, %3% given)") % lbound % ubound % value).str());
}
template
- GNUC_NORETURN static void raiseLower(const Type &value, const Type &lbound)
+ [[noreturn]] static void raiseLower(const Type &value, const Type &lbound)
{
throw OutOfBounds((boost::format(
"value is out of bounds ([%1%, ->) expected, %2% given)") % lbound % value).str());
}
template
- GNUC_NORETURN static void raiseUpper(const Type &value, const Type &ubound)
+ [[noreturn]] static void raiseUpper(const Type &value, const Type &ubound)
{
throw OutOfBounds((boost::format(
"value is out of bounds ((<-, %1%] expected, %2% given)") % ubound % value).str());
diff --git a/src/utility/html.cpp b/src/utility/html.cpp
index 9ef71becd..725538450 100644
--- a/src/utility/html.cpp
+++ b/src/utility/html.cpp
@@ -20,16 +20,29 @@
#include
#include
+#include
#include "utility/html.h"
std::string unescapeHtmlUtf8(const std::string &data)
{
+ int base;
+ size_t offset;
std::string result;
for (size_t i = 0, j; i < data.length(); ++i)
{
if (data[i] == '&' && data[i+1] == '#' && (j = data.find(';', i)) != std::string::npos)
{
- int n = atoi(&data.c_str()[i+2]);
+ if (data[i+2] == 'x')
+ {
+ offset = 3;
+ base = 16;
+ }
+ else
+ {
+ offset = 2;
+ base = 10;
+ }
+ int n = strtol(&data.c_str()[i+offset], nullptr, base);
if (n >= 0x800)
{
result += (0xe0 | ((n >> 12) & 0x0f));
diff --git a/src/utility/string.h b/src/utility/string.h
index e86ddeb8a..75d657678 100644
--- a/src/utility/string.h
+++ b/src/utility/string.h
@@ -25,7 +25,6 @@
#include
#include
#include
-#include "gcc.h"
template size_t const_strlen(const char (&)[N]) {
return N-1;
diff --git a/src/utility/type_conversions.cpp b/src/utility/type_conversions.cpp
index d4bfb8cdc..b546fa4a1 100644
--- a/src/utility/type_conversions.cpp
+++ b/src/utility/type_conversions.cpp
@@ -168,6 +168,8 @@ MPD::Song::GetFunction charToGetFunction(char c)
return &MPD::Song::getDirectory;
case 'f':
return &MPD::Song::getName;
+ case 'F':
+ return &MPD::Song::getURI;
case 'a':
return &MPD::Song::getArtist;
case 'A':