Merge branch 'ncmpcpp:master' into master

This commit is contained in:
Salastil
2024-08-25 21:35:43 -04:00
committed by GitHub
33 changed files with 321 additions and 211 deletions

View File

@@ -3,7 +3,7 @@ name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "[BUG]" title: "[BUG]"
labels: bug labels: bug
assignees: larson-carter assignees:
--- ---

View File

@@ -3,7 +3,7 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "[FEATURE]" title: "[FEATURE]"
labels: enhancement labels: enhancement
assignees: larson-carter assignees:
--- ---

View File

@@ -3,6 +3,22 @@
* Separate chunks of lyrics with a double newline. * Separate chunks of lyrics with a double newline.
* Fix separator between albums with the same name, to check for album artist * Fix separator between albums with the same name, to check for album artist
instead of artist. instead of artist.
* Column widths are now configurable via `media_library_column_width_ratio_two`,
`media_library_column_width_ratio_three` and
`playlist_editor_column_width_ratio`.
* Removed deprecated `visualizer_fifo_path` and `visualizer_sync_interval`
options.
* Added support for italic text.
* Framerate limit for visualizer was removed.
* Performer tag is now read from the `TPE4` frame instead of `TPE3`.
* Potential crash when fetching lyrics for streams in the background was fixed.
* Seeking now accepts the `hh:mm:ss` format.
* Tag editor now only writes to files with modified tags.
* Column view can now display full filepaths.
* Updated the list of working lyrics fetchers.
* Add `visualizer_spectrum_smooth_look_legacy_chars` option (enabled by default)
for potentially improved bottom part of the spectrum visualizer in terminals
with transparent background.
# ncmpcpp-0.9.2 (2021-01-24) # ncmpcpp-0.9.2 (2021-01-24)
* Revert suppression of output of all external commands as that makes e.g album * Revert suppression of output of all external commands as that makes e.g album

View File

@@ -1,11 +1,8 @@
# NCurses Music Player Client (Plus Plus) # NCurses Music Player Client (Plus Plus)
Project page - https://rybczak.net/ncmpcpp/
## ncmpcpp featureful ncurses based MPD client inspired by ncmpc ## ncmpcpp featureful ncurses based MPD client inspired by ncmpc
### Main features: ### Main features:
* tag editor * tag editor
* playlist editor * playlist editor
* easy to use search engine * easy to use search engine
@@ -18,19 +15,18 @@ Project page - https://rybczak.net/ncmpcpp/
…and a lot more minor functions. …and a lot more minor functions.
### Dependencies: ### Dependencies:
* [boost](https://www.boost.org/)
* boost library [https://www.boost.org/] * [ncurses](https://invisible-island.net/ncurses/announce.html)
* ncurses library [http://www.gnu.org/software/ncurses/ncurses.html] * [readline](https://tiswww.case.edu/php/chet/readline/rltop.html)
* readline library [https://tiswww.case.edu/php/chet/readline/rltop.html] * [curl](https://curl.se), for fetching lyrics and last.fm data
* curl library (optional, required for fetching lyrics and last.fm data) [https://curl.haxx.se/] #### Optional libraries
* fftw library (optional, required for frequency spectrum music visualization mode) [http://www.fftw.org/] * [fftw](http://www.fftw.org), for frequency spectrum music visualization mode
* tag library (optional, required for tag editing) [https://taglib.org/] * [taglib](https://taglib.org/), for tag editing
### Known issues: ### Known issues:
* No full support for handling encodings other than UTF-8. * No full support for handling encodings other than UTF-8.
### Installation: ### Installation:
The simplest way to compile this package is: The simplest way to compile this package is:
1. `cd` to the directory containing the package's source code. 1. `cd` to the directory containing the package's source code.
@@ -55,7 +51,6 @@ The simplest way to compile this package is:
Detailed intallation instructions can be found in the `INSTALL` file. Detailed intallation instructions can be found in the `INSTALL` file.
### Optional features: ### Optional features:
Optional features can be enable by specifying them during configure. For Optional features can be enable by specifying them during configure. For
example, to enable visualizer run `./configure --enable-visualizer`. example, to enable visualizer run `./configure --enable-visualizer`.

View File

@@ -1,15 +1,15 @@
AC_INIT([ncmpcpp], [0.10_dev]) AC_INIT([ncmpcpp],[0.10_dev])
AC_CONFIG_SRCDIR([configure.ac]) AC_CONFIG_SRCDIR([configure.ac])
AC_CONFIG_HEADERS(config.h) AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE([subdir-objects]) AM_INIT_AUTOMAKE([subdir-objects])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_PREREQ(2.59) AC_PREREQ([2.71])
AC_LANG_CPLUSPLUS AC_LANG([C++])
AC_PROG_CXX AC_PROG_CXX
AC_PROG_LIBTOOL LT_INIT
AC_ARG_ENABLE(outputs, AS_HELP_STRING([--enable-outputs], [Enable outputs screen @<:@default=no@:>@]), [outputs=$enableval], [outputs=no]) AC_ARG_ENABLE(outputs, AS_HELP_STRING([--enable-outputs], [Enable outputs screen @<:@default=no@:>@]), [outputs=$enableval], [outputs=no])
AC_ARG_ENABLE(visualizer, AS_HELP_STRING([--enable-visualizer], [Enable music visualizer screen @<:@default=no@:>@]), [visualizer=$enableval], [visualizer=no]) AC_ARG_ENABLE(visualizer, AS_HELP_STRING([--enable-visualizer], [Enable music visualizer screen @<:@default=no@:>@]), [visualizer=$enableval], [visualizer=no])
@@ -70,15 +70,15 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]])],
) )
CXXFLAGS="$old_CXXFLAGS $fast_math" CXXFLAGS="$old_CXXFLAGS $fast_math"
# -std=c++14 # -std=c++20
AC_MSG_CHECKING([whether compiler supports -std=c++14]) AC_MSG_CHECKING([whether compiler supports -std=c++20])
old_CXXFLAGS="$CXXFLAGS" old_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="-std=c++14" CXXFLAGS="-std=c++20"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]])], AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]])],
AC_MSG_RESULT([yes]) AC_MSG_RESULT([yes])
std_cpp14="-std=c++14", std_cpp14="-std=c++20",
AC_MSG_RESULT([no]) AC_MSG_RESULT([no])
AC_MSG_ERROR([[Your compiler doesn't seem to support C++14, please upgrade (GCC >= 5)]]) AC_MSG_ERROR([[Your compiler doesn't seem to support C++20, please upgrade]])
) )
CXXFLAGS="$old_CXXFLAGS $std_cpp14" CXXFLAGS="$old_CXXFLAGS $std_cpp14"
@@ -90,6 +90,9 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[auto f = [](auto n) { return n*n; }; f(
AC_MSG_ERROR([[Your compiler doesn't seem to support generic lambda expressions, please upgrade (GCC >= 5)]]) AC_MSG_ERROR([[Your compiler doesn't seem to support generic lambda expressions, please upgrade (GCC >= 5)]])
) )
# warnings
CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wshadow -Wimplicit-fallthrough"
# boost # boost
BOOST_REQUIRE([1.60]) BOOST_REQUIRE([1.60])
AC_SUBST(BOOST_CPPFLAGS) AC_SUBST(BOOST_CPPFLAGS)
@@ -149,7 +152,7 @@ PKG_CHECK_MODULES([ICU], [icu-i18n icu-uc], [
old_LIBS="$LIBS" old_LIBS="$LIBS"
AC_SUBST(ICU_CFLAGS) AC_SUBST(ICU_CFLAGS)
AC_SUBST(ICU_LIBS) AC_SUBST(ICU_LIBS)
CPPFLAGS="$CPPFLAGS $ICU_CFLAGS -DU_USING_ICU_NAMESPACE=0" CPPFLAGS="$CPPFLAGS $ICU_CFLAGS"
LIBS="$LIBS $ICU_LIBS" LIBS="$LIBS $ICU_LIBS"
AC_MSG_CHECKING([whether boost.regex was compiled with ICU support]) AC_MSG_CHECKING([whether boost.regex was compiled with ICU support])
AC_LINK_IFELSE([AC_LANG_PROGRAM([ AC_LINK_IFELSE([AC_LANG_PROGRAM([
@@ -257,19 +260,30 @@ PKG_CHECK_MODULES([libcurl], [libcurl], [
# taglib # taglib
if test "$taglib" != "no" ; then if test "$taglib" != "no" ; then
AC_PATH_PROG(TAGLIB_CONFIG, taglib-config) PKG_CHECK_MODULES([taglib], [taglib], [
if test "$TAGLIB_CONFIG" != "" ; then AC_SUBST(taglib_CFLAGS)
CPPFLAGS="$CPPFLAGS `$TAGLIB_CONFIG --cflags`" AC_SUBST(taglib_LIBS)
LIBS="$LIBS `$TAGLIB_CONFIG --libs`" ], [
AC_PATH_PROG([TAGLIB_CONFIG], [taglib-config])
if test "$TAGLIB_CONFIG" != ""; then
taglib_CFLAGS=`$TAGLIB_CONFIG --cflags`
taglib_LIBS=`$TAGLIB_CONFIG --libs`
else
if test "$taglib" = "yes" ; then
AC_MSG_ERROR([could not find taglib.pc or taglib-config executable])
fi
fi
])
if test "$TAGLIB_CONFIG$taglib_LIBS" != "" ; then
CPPFLAGS="$CPPFLAGS $taglib_CFLAGS"
LIBS="$LIBS $taglib_LIBS"
AC_CHECK_HEADERS([taglib.h], , AC_CHECK_HEADERS([taglib.h], ,
if test "$taglib" = "yes" ; then if test "$taglib" = "yes" ; then
AC_MSG_ERROR([missing taglib.h header]) AC_MSG_ERROR([missing taglib.h header])
fi fi
) )
else
if test "$taglib" = "yes" ; then
AC_MSG_ERROR([taglib-config executable is missing])
fi
fi fi
fi fi

View File

@@ -125,6 +125,14 @@
# #
#visualizer_spectrum_smooth_look = yes #visualizer_spectrum_smooth_look = yes
# #
## Use unicode block characters from "symbols for legacy computing". This
## improves the smooth look on transparent terminals by using special unicode
## chars instead of reversing the background and foreground color on the bottom
## edge of the spectrum. If it leads to a garbled output on the bottom edge of
## the spectrum, you can either change the font or disable this option.
#
#visualizer_spectrum_smooth_look_legacy_chars = yes
#
## A value between 1 and 5 inclusive. Specifying a larger value makes the ## A value between 1 and 5 inclusive. Specifying a larger value makes the
## visualizer look at a larger slice of time, which results in less jumpy ## visualizer look at a larger slice of time, which results in less jumpy
## visualizer output. ## visualizer output.
@@ -141,6 +149,11 @@
# #
#visualizer_spectrum_hz_max = 20000 #visualizer_spectrum_hz_max = 20000
# #
## Use log scaling for the frequency spectrum axes
#
#visualizer_spectrum_log_scale_x = yes
#visualizer_spectrum_log_scale_y = yes
#
##### system encoding ##### ##### system encoding #####
## ##
## ncmpcpp should detect your charset encoding but if it failed to do so, you ## ncmpcpp should detect your charset encoding but if it failed to do so, you
@@ -172,6 +185,7 @@
## ##
## %l - length ## %l - length
## %f - filename ## %f - filename
## %F - full filepath
## %D - directory ## %D - directory
## %a - artist ## %a - artist
## %A - album artist ## %A - album artist
@@ -408,7 +422,7 @@
# #
#cyclic_scrolling = no #cyclic_scrolling = no
# #
#lyrics_fetchers = azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet #lyrics_fetchers = genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet
# #
#follow_now_playing_lyrics = no #follow_now_playing_lyrics = no
# #

View File

@@ -114,6 +114,13 @@ Automatically scale visualizer size.
.B visualizer_spectrum_smooth_look = yes/no .B visualizer_spectrum_smooth_look = yes/no
For spectrum visualizer, use unicode block characters for a smoother, more continuous look. This will override the visualizer_look option. With transparent terminals and visualizer_in_stereo set, artifacts may be visible on the bottom half of the visualization. For spectrum visualizer, use unicode block characters for a smoother, more continuous look. This will override the visualizer_look option. With transparent terminals and visualizer_in_stereo set, artifacts may be visible on the bottom half of the visualization.
.TP .TP
.B visualizer_spectrum_smooth_look_legacy_chars = yes/no
Use unicode block characters from "symbols for legacy computing". This improves
the smooth look on transparent terminals by using special unicode chars instead
of reversing the background and foreground color on the bottom edge of the
spectrum. If it leads to a garbled output on the bottom edge of the spectrum,
you can either change the font or disable this option.
.TP
.B visualizer_spectrum_dft_size = NUMBER .B visualizer_spectrum_dft_size = NUMBER
For spectrum visualizer, a value between 1 and 5 inclusive. Specifying a larger value makes the visualizer look at a larger slice of time, which results in less jumpy visualizer output. For spectrum visualizer, a value between 1 and 5 inclusive. Specifying a larger value makes the visualizer look at a larger slice of time, which results in less jumpy visualizer output.
.TP .TP
@@ -472,6 +479,7 @@ For song format you can use:
%l - length %l - length
%f - filename %f - filename
%F - full filepath
%D - directory %D - directory
%a - artist %a - artist
%A - album artist %A - album artist

View File

@@ -34,7 +34,7 @@ enum class CopyResult { Success, NoArtist, AlbumArtistAlreadyInPlace };
bool is_framelist_empty(const TagLib::ID3v2::FrameList &list) bool is_framelist_empty(const TagLib::ID3v2::FrameList &list)
{ {
for (auto it = list.begin(); it != list.end(); ++it) for (auto it = list.begin(); it != list.end(); ++it)
if ((*it)->toString() != TagLib::String::null) if (!(*it)->toString().isEmpty())
return false; return false;
return true; return true;
} }

View File

@@ -1755,33 +1755,46 @@ void JumpToPositionInSong::run()
std::string spos; std::string spos;
{ {
Statusbar::ScopedLock slock; Statusbar::ScopedLock slock;
Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): "; Statusbar::put() << "Position to go (in %/h:m:ss/m:ss/seconds(s)): ";
spos = wFooter->prompt(); spos = wFooter->prompt();
} }
boost::regex rx; boost::regex rx;
boost::smatch what; boost::smatch what;
if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss // mm:ss
if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})")))
{ {
auto mins = fromString<unsigned>(what[1]); auto mins = fromString<unsigned>(what[1]);
auto secs = fromString<unsigned>(what[2]); auto secs = fromString<unsigned>(what[2]);
boundsCheck(secs, 0u, 60u); boundsCheck(secs, 0u, 60u);
Mpd.Seek(s.getPosition(), mins * 60 + secs); Mpd.Seek(s.getPosition(), mins * 60 + secs);
} }
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds // position in seconds
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s")))
{ {
auto secs = fromString<unsigned>(what[1]); auto secs = fromString<unsigned>(what[1]);
Mpd.Seek(s.getPosition(), secs); Mpd.Seek(s.getPosition(), secs);
} }
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in % // position in%
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}")))
{ {
auto percent = fromString<unsigned>(what[1]); auto percent = fromString<unsigned>(what[1]);
boundsCheck(percent, 0u, 100u); boundsCheck(percent, 0u, 100u);
int secs = (percent * s.getDuration()) / 100.0; int secs = (percent * s.getDuration()) / 100.0;
Mpd.Seek(s.getPosition(), secs); Mpd.Seek(s.getPosition(), secs);
} }
// position in hh:mm:ss
else if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2}):([0-9]{2})")))
{
auto hours = fromString<unsigned>(what[1]);
auto mins = fromString<unsigned>(what[2]);
auto secs = fromString<unsigned>(what[3]);
boundsCheck(mins, 0u, 60u);
boundsCheck(secs, 0u, 60u);
Mpd.Seek(s.getPosition(), hours * 3600 + mins * 60 + secs);
}
else else
Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)"); Statusbar::print("Invalid format ([h]:[mm]:[ss], [m]:[ss], [s]s, [%]%, [%] accepted)");
} }
bool SelectItem::canBeRun() bool SelectItem::canBeRun()

View File

@@ -156,14 +156,10 @@ bool configure(int argc, char **argv)
if (vm.count("test-lyrics-fetchers")) if (vm.count("test-lyrics-fetchers"))
{ {
std::vector<std::tuple<std::string, std::string, std::string>> fetcher_data = { std::vector<std::tuple<std::string, std::string, std::string>> fetcher_data = {
std::make_tuple("azlyrics", "rihanna", "umbrella"),
std::make_tuple("genius", "rihanna", "umbrella"), std::make_tuple("genius", "rihanna", "umbrella"),
std::make_tuple("musixmatch", "rihanna", "umbrella"),
std::make_tuple("sing365", "rihanna", "umbrella"),
std::make_tuple("metrolyrics", "rihanna", "umbrella"),
std::make_tuple("justsomelyrics", "rihanna", "umbrella"), std::make_tuple("justsomelyrics", "rihanna", "umbrella"),
std::make_tuple("jahlyrics", "sean kingston", "dry your eyes"), std::make_tuple("jahlyrics", "sean kingston", "dry your eyes"),
std::make_tuple("plyrics", "offspring", "genocide"), std::make_tuple("plyrics", "rihanna", "umbrella"),
std::make_tuple("tekstowo", "rihanna", "umbrella"), std::make_tuple("tekstowo", "rihanna", "umbrella"),
std::make_tuple("zeneszoveg", "rihanna", "umbrella"), std::make_tuple("zeneszoveg", "rihanna", "umbrella"),
}; };

View File

@@ -24,6 +24,7 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <sys/select.h> #include <sys/select.h>
#include <termios.h>
#include <unistd.h> #include <unistd.h>
#include "utility/readline.h" #include "utility/readline.h"
@@ -198,6 +199,8 @@ int add_base()
int color_pair_counter; int color_pair_counter;
std::vector<int> color_pair_map; std::vector<int> color_pair_map;
termios orig_termios;
} }
namespace NC { namespace NC {
@@ -400,6 +403,7 @@ int colorCount()
void initScreen(bool enable_colors, bool enable_mouse) void initScreen(bool enable_colors, bool enable_mouse)
{ {
tcgetattr(STDIN_FILENO, &orig_termios);
initscr(); initscr();
if (has_colors() && enable_colors) if (has_colors() && enable_colors)
{ {
@@ -481,6 +485,7 @@ void destroyScreen()
Mouse::disable(); Mouse::disable();
curs_set(1); curs_set(1);
endwin(); endwin();
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
} }
Window::Window(size_t startx, size_t starty, size_t width, size_t height, Window::Window(size_t startx, size_t starty, size_t width, size_t height,
@@ -501,12 +506,6 @@ Window::Window(size_t startx, size_t starty, size_t width, size_t height,
m_alt_charset_counter(0), m_alt_charset_counter(0),
m_italic_counter(0) m_italic_counter(0)
{ {
if (m_start_x > size_t(COLS)
|| m_start_y > size_t(LINES)
|| m_width+m_start_x > size_t(COLS)
|| m_height+m_start_y > size_t(LINES))
throw std::logic_error("constructed window doesn't fit into the terminal");
if (m_border) if (m_border)
{ {
++m_start_x; ++m_start_x;

View File

@@ -26,7 +26,6 @@
#include "config.h" #include "config.h"
#include "curses.h" #include "curses.h"
#include "gcc.h"
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <functional> #include <functional>

View File

@@ -47,6 +47,8 @@ const wchar_t *toColumnName(char c)
return L"Filename"; return L"Filename";
case 'D': case 'D':
return L"Directory"; return L"Directory";
case 'F':
return L"Filepath";
case 'a': case 'a':
return L"Artist"; return L"Artist";
case 'A': case 'A':

View File

@@ -1,29 +0,0 @@
/***************************************************************************
* Copyright (C) 2008-2021 by Andrzej Rybczak *
* andrzej@rybczak.net *
* *
* 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#if defined(__GNUC__) && __GNUC__ >= 3
# define GNUC_NORETURN __attribute__((noreturn))
# define GNUC_UNUSED __attribute__((unused))
# define GNUC_PRINTF(a, b) __attribute__((format(printf, a, b)))
#else
# define GNUC_NORETURN
# define GNUC_UNUSED
# define GNUC_PRINTF(a, b)
#endif

View File

@@ -25,7 +25,6 @@
#include <boost/tuple/tuple.hpp> #include <boost/tuple/tuple.hpp>
#include <string> #include <string>
#include "enums.h" #include "enums.h"
#include "gcc.h"
#include "screens/screen.h" #include "screens/screen.h"
#include "song.h" #include "song.h"

View File

@@ -38,16 +38,8 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher)
{ {
std::string s; std::string s;
is >> s; is >> s;
if (s == "azlyrics") if (s == "genius")
fetcher = std::make_unique<AzLyricsFetcher>();
else if (s == "genius")
fetcher = std::make_unique<GeniusFetcher>(); fetcher = std::make_unique<GeniusFetcher>();
else if (s == "musixmatch")
fetcher = std::make_unique<MusixmatchFetcher>();
else if (s == "sing365")
fetcher = std::make_unique<Sing365Fetcher>();
else if (s == "metrolyrics")
fetcher = std::make_unique<MetrolyricsFetcher>();
else if (s == "justsomelyrics") else if (s == "justsomelyrics")
fetcher = std::make_unique<JustSomeLyricsFetcher>(); fetcher = std::make_unique<JustSomeLyricsFetcher>();
else if (s == "jahlyrics") else if (s == "jahlyrics")
@@ -76,7 +68,7 @@ LyricsFetcher::Result LyricsFetcher::fetch(const std::string &artist,
std::string url = urlTemplate(); std::string url = urlTemplate();
boost::replace_all(url, "%artist%", Curl::escape(artist)); boost::replace_all(url, "%artist%", Curl::escape(artist));
boost::replace_all(url, "%title%", Curl::escape(title)); boost::replace_all(url, "%title%", Curl::escape(title));
std::string data; std::string data;
CURLcode code = Curl::perform(data, url, "", true); CURLcode code = Curl::perform(data, url, "", true);
@@ -88,9 +80,11 @@ LyricsFetcher::Result LyricsFetcher::fetch(const std::string &artist,
auto lyrics = getContent(regex(), data); auto lyrics = getContent(regex(), data);
//std::cerr << "URL: " << url << "\n";
//std::cerr << "Data: " << data << "\n";
if (lyrics.empty() || notLyrics(data)) if (lyrics.empty() || notLyrics(data))
{ {
//std::cerr << "Data: " << data << "\n";
//std::cerr << "Empty: " << lyrics.empty() << "\n"; //std::cerr << "Empty: " << lyrics.empty() << "\n";
//std::cerr << "Not Lyrics: " << notLyrics(data) << "\n"; //std::cerr << "Not Lyrics: " << notLyrics(data) << "\n";
result.second = msgNotFound; result.second = msgNotFound;
@@ -140,9 +134,11 @@ void LyricsFetcher::postProcess(std::string &data) const
boost::split(lines, data, boost::is_any_of("\n")); boost::split(lines, data, boost::is_any_of("\n"));
for (auto &line : lines) for (auto &line : lines)
boost::trim(line); boost::trim(line);
std::unique(lines.begin(), lines.end(), [](std::string &a, std::string &b) { auto last = std::unique(
return a.empty() && b.empty(); lines.begin(),
}); lines.end(),
[](std::string &a, std::string &b) { return a.empty() && b.empty(); });
lines.erase(last, lines.end());
data = boost::algorithm::join(lines, "\n"); data = boost::algorithm::join(lines, "\n");
boost::trim(data); boost::trim(data);
} }
@@ -202,14 +198,6 @@ bool GoogleLyricsFetcher::isURLOk(const std::string &url)
/**********************************************************************/ /**********************************************************************/
bool MetrolyricsFetcher::isURLOk(const std::string &url)
{
// it sometimes return link to sitemap.xml, which is huge so we need to discard it
return GoogleLyricsFetcher::isURLOk(url) && url.find("sitemap") == std::string::npos;
}
/**********************************************************************/
LyricsFetcher::Result InternetLyricsFetcher::fetch(const std::string &artist, LyricsFetcher::Result InternetLyricsFetcher::fetch(const std::string &artist,
const std::string &title) const std::string &title)
{ {

View File

@@ -69,34 +69,6 @@ private:
const char *URL; const char *URL;
}; };
struct MusixmatchFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "musixmatch.com"; }
protected:
virtual const char *regex() const override { return "<span class=\"lyrics__content__.*?>(.*?)</span>"; }
virtual void postProcess(std::string &) const override { }
};
struct MetrolyricsFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "metrolyrics.com"; }
protected:
virtual const char *regex() const override { return "<div class=\"lyrics-body\">(.*?)<!--WIDGET.*?<!-- Second Section -->(.*?)<!--WIDGET.*?<!-- Third Section -->(.*?)</div>"; }
virtual bool isURLOk(const std::string &url) override;
};
struct Sing365Fetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "lyrics007.com"; }
protected:
virtual const char *regex() const override { return "<div class=\"lyrics\">(.*?)</div>"; }
};
struct JustSomeLyricsFetcher : public GoogleLyricsFetcher struct JustSomeLyricsFetcher : public GoogleLyricsFetcher
{ {
virtual const char *name() const override { return "justsomelyrics.com"; } virtual const char *name() const override { return "justsomelyrics.com"; }
@@ -105,14 +77,6 @@ protected:
virtual const char *regex() const override { return "<div class=\"content.*?</div>(.*?)See also"; } virtual const char *regex() const override { return "<div class=\"content.*?</div>(.*?)See also"; }
}; };
struct AzLyricsFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "azlyrics.com"; }
protected:
virtual const char *regex() const override { return "<div class=\"lyricsh\">.*?</h2>.*<div>(.*?)</div>"; }
};
struct GeniusFetcher : public GoogleLyricsFetcher struct GeniusFetcher : public GoogleLyricsFetcher
{ {
virtual const char *name() const override { return "genius.com"; } virtual const char *name() const override { return "genius.com"; }
@@ -142,7 +106,7 @@ struct TekstowoFetcher : public GoogleLyricsFetcher
virtual const char *name() const override { return "tekstowo.pl"; } virtual const char *name() const override { return "tekstowo.pl"; }
protected: protected:
virtual const char *regex() const override { return "<div class=\"song-text\".*?>.*?</h2>(.*?)<a"; } virtual const char *regex() const override { return "<div class=\"inner-text\">(.*?)</div>"; }
}; };
struct ZeneszovegFetcher : public GoogleLyricsFetcher struct ZeneszovegFetcher : public GoogleLyricsFetcher
@@ -150,7 +114,7 @@ struct ZeneszovegFetcher : public GoogleLyricsFetcher
virtual const char *name() const override { return "zeneszoveg.hu"; } virtual const char *name() const override { return "zeneszoveg.hu"; }
protected: protected:
virtual const char *regex() const override { return "<div class=\"lyrics-plain-text.*?\">(.*?)</div>"; } virtual const char *regex() const override { return "<div id=\"tartalom_slide_content\"> (.*?)<style>"; }
}; };
struct InternetLyricsFetcher : public GoogleLyricsFetcher struct InternetLyricsFetcher : public GoogleLyricsFetcher

View File

@@ -353,8 +353,14 @@ private:
}; };
template <typename ObjectT> template <typename ObjectT>
struct Iterator: std::iterator<std::input_iterator_tag, ObjectT> 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 // shared state of the iterator
struct State struct State
{ {

View File

@@ -219,7 +219,10 @@ int main(int argc, char **argv)
try try
{ {
auto k = Bindings.get(input); 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) catch (ConversionError &e)
{ {

View File

@@ -361,6 +361,25 @@ void Lyrics::toggleFetcher()
Statusbar::print("Using all lyrics fetchers"); 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_) void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
{ {
auto consumer_impl = [this] { auto consumer_impl = [this] {
@@ -377,7 +396,10 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
break; break;
} }
lyrics_file = lyricsFilename(consumer->songs.front().song()); 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(); cs = consumer->songs.front();
if (cs.notify()) if (cs.notify())
@@ -389,7 +411,7 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
} }
consumer->songs.pop(); consumer->songs.pop();
} }
if (!cs.song().empty()) if (!cs.song().empty() && !cs.song().isStream())
{ {
auto lyrics = downloadLyrics(cs.song(), nullptr, nullptr, m_fetcher); auto lyrics = downloadLyrics(cs.song(), nullptr, nullptr, m_fetcher);
if (lyrics) if (lyrics)

View File

@@ -98,7 +98,7 @@ bool MoveToAlbum(NC::Menu<AlbumEntry> &albums, const std::string &primary_tag, c
struct SortSongs { struct SortSongs {
typedef NC::Menu<MPD::Song>::Item SongItem; typedef NC::Menu<MPD::Song>::Item SongItem;
static const std::array<MPD::Song::GetFunction, 3> GetFuns; static const std::array<MPD::Song::GetFunction, 4> GetFuns;
LocaleStringComparison m_cmp; LocaleStringComparison m_cmp;
@@ -117,26 +117,16 @@ public:
return ret < 0; return ret < 0;
} }
// Sort by track numbers.
try {
ret = boost::lexical_cast<int>(a.getTags(&MPD::Song::getTrackNumber))
- boost::lexical_cast<int>(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<char>(Config.song_library_format, &a) return Format::stringify<char>(Config.song_library_format, &a)
< Format::stringify<char>(Config.song_library_format, &b); < Format::stringify<char>(Config.song_library_format, &b);
} }
}; };
const std::array<MPD::Song::GetFunction, 3> SortSongs::GetFuns = {{ const std::array<MPD::Song::GetFunction, 4> SortSongs::GetFuns = {{
&MPD::Song::getDate, &MPD::Song::getDate,
&MPD::Song::getAlbum, &MPD::Song::getAlbum,
&MPD::Song::getDisc &MPD::Song::getDisc,
&MPD::Song::getTrackNumber,
}}; }};
class SortAlbumEntries { class SortAlbumEntries {
@@ -346,9 +336,9 @@ void MediaLibrary::update()
for (const auto &album : albums) for (const auto &album : albums)
{ {
auto entry = AlbumEntry( auto entry = AlbumEntry(
Album(std::move(std::get<0>(album.first)), Album(std::get<0>(album.first),
std::move(std::get<1>(album.first)), std::get<1>(album.first),
std::move(std::get<2>(album.first)), std::get<2>(album.first),
album.second)); album.second));
if (idx < Albums.size()) if (idx < Albums.size())
Albums[idx].value() = std::move(entry); Albums[idx].value() = std::move(entry);
@@ -445,8 +435,8 @@ void MediaLibrary::update()
{ {
auto entry = AlbumEntry( auto entry = AlbumEntry(
Album(primary_tag, Album(primary_tag,
std::move(std::get<0>(album.first)), std::get<0>(album.first),
std::move(std::get<1>(album.first)), std::get<1>(album.first),
album.second)); album.second));
if (idx < Albums.size()) if (idx < Albums.size())
{ {

View File

@@ -798,13 +798,16 @@ void TagEditor::runAction()
Statusbar::print("Writing changes..."); Statusbar::print("Writing changes...");
for (auto it = EditedSongs.begin(); it != EditedSongs.end(); ++it) for (auto it = EditedSongs.begin(); it != EditedSongs.end(); ++it)
{ {
Statusbar::printf("Writing tags in \"%1%\"...", (*it)->getName()); if ((*it)->isModified())
if (!Tags::write(**it))
{ {
Statusbar::printf("Error while writing tags to \"%1%\": %2%", Statusbar::printf("Writing tags in \"%1%\"...", (*it)->getName());
(*it)->getName(), strerror(errno)); if (!Tags::write(**it))
success = 0; {
break; Statusbar::printf("Error while writing tags to \"%1%\": %2%",
(*it)->getName(), strerror(errno));
success = 0;
break;
}
} }
} }
if (success) if (success)

View File

@@ -83,7 +83,8 @@ Visualizer::Visualizer()
HZ_MIN(Config.visualizer_spectrum_hz_min), HZ_MIN(Config.visualizer_spectrum_hz_min),
HZ_MAX(Config.visualizer_spectrum_hz_max), HZ_MAX(Config.visualizer_spectrum_hz_max),
GAIN(Config.visualizer_spectrum_gain), GAIN(Config.visualizer_spectrum_gain),
SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█")) SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█")),
SMOOTH_CHARS_FLIPPED(ToWString("▔🮂🮃🮄🬎🮅🮆█")) // https://unicode.org/charts/PDF/U1FB00.pdf
#endif #endif
{ {
InitDataSource(); InitDataSource();
@@ -95,7 +96,7 @@ Visualizer::Visualizer()
memset(m_fftw_input, 0, sizeof(double)*DFT_TOTAL_SIZE); memset(m_fftw_input, 0, sizeof(double)*DFT_TOTAL_SIZE);
m_fftw_output = static_cast<fftw_complex *>(fftw_malloc(sizeof(fftw_complex)*m_fftw_results)); m_fftw_output = static_cast<fftw_complex *>(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_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); m_bar_heights.reserve(100);
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
@@ -107,7 +108,7 @@ void Visualizer::switchTo()
m_reset_output = true; m_reset_output = true;
drawHeader(); drawHeader();
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
GenLogspace(); GenFreqSpace();
m_bar_heights.reserve(w.getWidth()); m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H # endif // HAVE_FFTW3_H
} }
@@ -121,7 +122,7 @@ void Visualizer::resize()
hasToBeResized = 0; hasToBeResized = 0;
InitVisualization(); InitVisualization();
# ifdef HAVE_FFTW3_H # ifdef HAVE_FFTW3_H
GenLogspace(); GenFreqSpace();
m_bar_heights.reserve(w.getWidth()); m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H # 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); auto c = toColor(sqrt(x*x + 4*y*y), radius, true);
w << NC::XY(left_half_width + x, top_half_height + y) w << NC::XY(left_half_width + x, top_half_height + y)
<< c << c
<< Config.visualizer_chars[1] << Config.visualizer_chars[0]
<< NC::FormattedColor::End<>(c); << 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(); const size_t win_width = w.getWidth();
size_t cur_bin = 0; 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; ++cur_bin;
for (size_t x = 0; x < win_width; ++x) 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 // accumulate bins
size_t count = 0; size_t count = 0;
// check right bound // 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 // 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]; bar_height += m_freq_magnitudes[cur_bin];
++count; ++count;
@@ -475,8 +476,19 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// average bins // average bins
bar_height /= count; bar_height /= count;
// log scale bar heights // apply scaling to bar heights
bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE; 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 // Scale bar height between 0 and height
bar_height = bar_height > 0 ? bar_height * height : 0; bar_height = bar_height > 0 ? bar_height * height : 0;
bar_height = bar_height > height ? height : bar_height; 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; ++h_idx;
} else { } else {
// data point does not exist, need to interpolate // 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) for (size_t j = 0; j < h; ++j)
@@ -518,8 +535,12 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
} else { } else {
// fractional height // fractional height
if (flipped) { if (flipped) {
ch = SMOOTH_CHARS[size-idx-2]; if (Config.visualizer_spectrum_smooth_look_legacy_chars) {
color = NC::FormattedColor(color.color(), {NC::Format::Reverse}); ch = SMOOTH_CHARS_FLIPPED[idx];
} else {
ch = SMOOTH_CHARS[size-idx-2];
color = NC::FormattedColor(color.color(), {NC::Format::Reverse});
}
} else { } else {
ch = SMOOTH_CHARS[idx]; 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); 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 x_next = m_bar_heights[h_idx].first;
const double h_next = m_bar_heights[h_idx].second; 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; 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) void Visualizer::ApplyWindow(double *output, const int16_t *input, ssize_t samples)
{ {
// Use Blackman window for low sidelobes and fast sidelobe rolloff // 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 win_width = w.getWidth();
const size_t left_bins = (log10(HZ_MIN) - win_width*log10(HZ_MIN)) / (log10(HZ_MIN) - log10(HZ_MAX)); const size_t left_bins = (log10(HZ_MIN) - win_width*log10(HZ_MIN)) / (log10(HZ_MIN) - log10(HZ_MAX));
// Generate logspaced frequencies // Generate logspaced frequencies
m_dft_logspace.resize(win_width); m_dft_freqspace.resize(win_width);
const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_logspace.size() - 1); const double log_scale = log10(HZ_MAX) / (left_bins + m_dft_freqspace.size() - 1);
for (size_t i = left_bins; i < m_dft_logspace.size() + left_bins; ++i) { for (size_t i = left_bins; i < m_dft_freqspace.size() + left_bins; ++i) {
m_dft_logspace[i - left_bins] = pow(10, i * log_scale); 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 #endif // HAVE_FFTW3_H

View File

@@ -76,8 +76,11 @@ private:
void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t); void DrawFrequencySpectrumStereo(const int16_t *, const int16_t *, ssize_t, size_t);
void ApplyWindow(double *, const int16_t *, ssize_t); void ApplyWindow(double *, const int16_t *, ssize_t);
void GenLogspace(); void GenLogspace();
void GenLinspace();
void GenFreqSpace();
double Bin2Hz(size_t); 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 # endif // HAVE_FFTW3_H
void InitDataSource(); void InitDataSource();
@@ -113,7 +116,8 @@ private:
const double HZ_MAX; const double HZ_MAX;
const double GAIN; const double GAIN;
const std::wstring SMOOTH_CHARS; const std::wstring SMOOTH_CHARS;
std::vector<double> m_dft_logspace; const std::wstring SMOOTH_CHARS_FLIPPED;
std::vector<double> m_dft_freqspace;
std::vector<std::pair<size_t, double>> m_bar_heights; std::vector<std::pair<size_t, double>> m_bar_heights;
std::vector<double> m_freq_magnitudes; std::vector<double> m_freq_magnitudes;

View File

@@ -253,6 +253,7 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
}); });
p.add("visualizer_autoscale", &visualizer_autoscale, "no", yes_no); 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", &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, p.add("visualizer_spectrum_dft_size", &visualizer_spectrum_dft_size,
"2", [](std::string v) { "2", [](std::string v) {
auto result = verbose_lexical_cast<size_t>(v); auto result = verbose_lexical_cast<size_t>(v);
@@ -277,6 +278,8 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
lowerBoundCheck<double>(result, Config.visualizer_spectrum_hz_min+1); lowerBoundCheck<double>(result, Config.visualizer_spectrum_hz_min+1);
return result; 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, p.add("visualizer_color", &visualizer_colors,
"blue, cyan, green, yellow, magenta, red", list_of<NC::FormattedColor>); "blue, cyan, green, yellow, magenta, red", list_of<NC::FormattedColor>);
p.add("system_encoding", &system_encoding, "", [](std::string encoding) { p.add("system_encoding", &system_encoding, "", [](std::string encoding) {
@@ -458,9 +461,24 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
p.add("titles_visibility", &titles_visibility, "yes", yes_no); p.add("titles_visibility", &titles_visibility, "yes", yes_no);
p.add("header_text_scrolling", &header_text_scrolling, "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("cyclic_scrolling", &use_cyclic_scrolling, "no", yes_no);
p.add("lyrics_fetchers", &lyrics_fetchers, p.add<void>("lyrics_fetchers", nullptr,
"azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet", "genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet", [this](std::string v) {
list_of<LyricsFetcher_>); lyrics_fetchers = list_of<LyricsFetcher_>(v, [](std::string s) {
LyricsFetcher_ fetcher;
try {
fetcher = boost::lexical_cast<LyricsFetcher_>(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("follow_now_playing_lyrics", &now_playing_lyrics, "no", yes_no);
p.add("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background, p.add("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background,
"no", yes_no); "no", yes_no);

View File

@@ -86,10 +86,13 @@ struct Configuration
size_t visualizer_fps; size_t visualizer_fps;
bool visualizer_autoscale; bool visualizer_autoscale;
bool visualizer_spectrum_smooth_look; bool visualizer_spectrum_smooth_look;
bool visualizer_spectrum_smooth_look_legacy_chars;
uint32_t visualizer_spectrum_dft_size; uint32_t visualizer_spectrum_dft_size;
double visualizer_spectrum_gain; double visualizer_spectrum_gain;
double visualizer_spectrum_hz_min; double visualizer_spectrum_hz_min;
double visualizer_spectrum_hz_max; double visualizer_spectrum_hz_max;
bool visualizer_spectrum_log_scale_x;
bool visualizer_spectrum_log_scale_y;
std::string pattern; std::string pattern;

View File

@@ -293,7 +293,9 @@ bool Song::isFromDatabase() const
bool Song::isStream() const bool Song::isStream() const
{ {
assert(m_song); 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 bool Song::empty() const
@@ -308,7 +310,7 @@ std::string Song::ShowTime(unsigned length)
int minutes = length/60; int minutes = length/60;
length -= minutes*60; length -= minutes*60;
int seconds = length; int seconds = length;
std::string result; std::string result;
if (hours > 0) if (hours > 0)
result = (boost::format("%d:%02d:%02d") % hours % minutes % seconds).str(); result = (boost::format("%d:%02d:%02d") % hours % minutes % seconds).str();

View File

@@ -24,7 +24,6 @@
#include <boost/format.hpp> #include <boost/format.hpp>
#include "curses/window.h" #include "curses/window.h"
#include "settings.h" #include "settings.h"
#include "gcc.h"
#include "interfaces.h" #include "interfaces.h"
namespace Progressbar { namespace Progressbar {

View File

@@ -123,12 +123,12 @@ void writeCommonTags(const MPD::MutableSong &s, TagLib::Tag *tag)
tag->setArtist(ToWString(s.getArtist())); tag->setArtist(ToWString(s.getArtist()));
tag->setAlbum(ToWString(s.getAlbum())); tag->setAlbum(ToWString(s.getAlbum()));
try { try {
tag->setYear(boost::lexical_cast<TagLib::uint>(s.getDate())); tag->setYear(boost::lexical_cast<unsigned>(s.getDate()));
} catch (boost::bad_lexical_cast &) { } catch (boost::bad_lexical_cast &) {
std::cerr << "writeCommonTags: couldn't write 'year' tag to '" << s.getURI() << "' as it's not a positive integer\n"; std::cerr << "writeCommonTags: couldn't write 'year' tag to '" << s.getURI() << "' as it's not a positive integer\n";
} }
try { try {
tag->setTrack(boost::lexical_cast<TagLib::uint>(s.getTrack())); tag->setTrack(boost::lexical_cast<unsigned>(s.getTrack()));
} catch (boost::bad_lexical_cast &) { } catch (boost::bad_lexical_cast &) {
std::cerr << "writeCommonTags: couldn't write 'track' tag to '" << s.getURI() << "' as it's not a positive integer\n"; 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) void writeXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag)
{ {
auto writeXiph = [&](const TagLib::String &type, const TagLib::StringList &list) { auto writeXiph = [&](const TagLib::String &type, const TagLib::StringList &list) {
tag->removeFields(type);
for (auto it = list.begin(); it != list.end(); ++it) for (auto it = list.begin(); it != list.end(); ++it)
tag->addField(type, *it, it == list.begin()); tag->addField(type, *it, it == list.begin());
}; };
@@ -261,7 +262,7 @@ void read(mpd_song *s)
if (f.isNull()) if (f.isNull())
return; return;
setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->length())); setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->lengthInSeconds()));
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file())) if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
{ {
@@ -301,15 +302,9 @@ bool write(MPD::MutableSong &s)
if (f.isNull()) if (f.isNull())
return false; return false;
bool saved = false;
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file())) if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
{ {
writeID3v2Tags(s, mpeg_file->ID3v2Tag(true)); 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<TagLib::Ogg::Vorbis::File *>(f.file())) else if (auto vorbis_file = dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file()))
{ {
@@ -326,7 +321,7 @@ bool write(MPD::MutableSong &s)
else else
writeCommonTags(s, f.tag()); writeCommonTags(s, f.tag());
if (!saved && !f.save()) if (!f.save())
return false; return false;
// TODO: move this somewhere else // TODO: move this somewhere else

View File

@@ -34,10 +34,23 @@ bool hasTheWord(const std::string &s)
&& (s[3] == ' '); && (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 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; size_t ac_off = 0, bc_off = 0;
if (m_ignore_the) if (m_ignore_the)
{ {

View File

@@ -26,7 +26,6 @@
#include <boost/type_traits/is_unsigned.hpp> #include <boost/type_traits/is_unsigned.hpp>
#include "config.h" #include "config.h"
#include "gcc.h"
struct ConversionError struct ConversionError
{ {
@@ -44,21 +43,21 @@ struct OutOfBounds : std::exception
const std::string &errorMessage() { return m_error_message; } const std::string &errorMessage() { return m_error_message; }
template <typename Type> template <typename Type>
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( throw OutOfBounds((boost::format(
"value is out of bounds ([%1%, %2%] expected, %3% given)") % lbound % ubound % value).str()); "value is out of bounds ([%1%, %2%] expected, %3% given)") % lbound % ubound % value).str());
} }
template <typename Type> template <typename Type>
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( throw OutOfBounds((boost::format(
"value is out of bounds ([%1%, ->) expected, %2% given)") % lbound % value).str()); "value is out of bounds ([%1%, ->) expected, %2% given)") % lbound % value).str());
} }
template <typename Type> template <typename Type>
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( throw OutOfBounds((boost::format(
"value is out of bounds ((<-, %1%] expected, %2% given)") % ubound % value).str()); "value is out of bounds ((<-, %1%] expected, %2% given)") % ubound % value).str());

View File

@@ -25,7 +25,6 @@
#include <locale> #include <locale>
#include <string> #include <string>
#include <vector> #include <vector>
#include "gcc.h"
template <size_t N> size_t const_strlen(const char (&)[N]) { template <size_t N> size_t const_strlen(const char (&)[N]) {
return N-1; return N-1;

View File

@@ -168,6 +168,8 @@ MPD::Song::GetFunction charToGetFunction(char c)
return &MPD::Song::getDirectory; return &MPD::Song::getDirectory;
case 'f': case 'f':
return &MPD::Song::getName; return &MPD::Song::getName;
case 'F':
return &MPD::Song::getURI;
case 'a': case 'a':
return &MPD::Song::getArtist; return &MPD::Song::getArtist;
case 'A': case 'A':