diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7684050f..5d78452b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,7 +3,7 @@ name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug -assignees: larson-carter +assignees: --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c4e096cf..e5ad1626 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,7 +3,7 @@ name: Feature request about: Suggest an idea for this project title: "[FEATURE]" labels: enhancement -assignees: larson-carter +assignees: --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 531599e3..1c8884d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ * Separate chunks of lyrics with a double newline. * Fix separator between albums with the same name, to check for album 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) * Revert suppression of output of all external commands as that makes e.g album diff --git a/README.md b/README.md index 4a186de8..2cc9b8ad 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # NCurses Music Player Client (Plus Plus) -Project page - https://rybczak.net/ncmpcpp/ - ## ncmpcpp – featureful ncurses based MPD client inspired by ncmpc ### Main features: - * tag editor * playlist editor * easy to use search engine @@ -18,19 +15,18 @@ Project page - https://rybczak.net/ncmpcpp/ …and a lot more minor functions. ### Dependencies: - -* boost library [https://www.boost.org/] -* ncurses library [http://www.gnu.org/software/ncurses/ncurses.html] -* readline library [https://tiswww.case.edu/php/chet/readline/rltop.html] -* curl library (optional, required for fetching lyrics and last.fm data) [https://curl.haxx.se/] -* fftw library (optional, required for frequency spectrum music visualization mode) [http://www.fftw.org/] -* tag library (optional, required for tag editing) [https://taglib.org/] +* [boost](https://www.boost.org/) +* [ncurses](https://invisible-island.net/ncurses/announce.html) +* [readline](https://tiswww.case.edu/php/chet/readline/rltop.html) +* [curl](https://curl.se), for fetching lyrics and last.fm data +#### Optional libraries +* [fftw](http://www.fftw.org), for frequency spectrum music visualization mode +* [taglib](https://taglib.org/), for tag editing ### Known issues: * No full support for handling encodings other than UTF-8. ### Installation: - The simplest way to compile this package is: 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. ### Optional features: - Optional features can be enable by specifying them during configure. For example, to enable visualizer run `./configure --enable-visualizer`. diff --git a/configure.ac b/configure.ac index ef0c52b7..ffe1bd10 100644 --- a/configure.ac +++ b/configure.ac @@ -1,15 +1,15 @@ -AC_INIT([ncmpcpp], [0.10_dev]) +AC_INIT([ncmpcpp],[0.10_dev]) AC_CONFIG_SRCDIR([configure.ac]) AC_CONFIG_HEADERS(config.h) AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_MACRO_DIR([m4]) -AC_PREREQ(2.59) +AC_PREREQ([2.71]) -AC_LANG_CPLUSPLUS +AC_LANG([C++]) 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(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" -# -std=c++14 -AC_MSG_CHECKING([whether compiler supports -std=c++14]) +# -std=c++20 +AC_MSG_CHECKING([whether compiler supports -std=c++20]) old_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="-std=c++14" +CXXFLAGS="-std=c++20" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]])], AC_MSG_RESULT([yes]) - std_cpp14="-std=c++14", + std_cpp14="-std=c++20", 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" @@ -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)]]) ) +# warnings +CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wshadow -Wimplicit-fallthrough" + # boost BOOST_REQUIRE([1.60]) AC_SUBST(BOOST_CPPFLAGS) @@ -149,7 +152,7 @@ PKG_CHECK_MODULES([ICU], [icu-i18n icu-uc], [ old_LIBS="$LIBS" AC_SUBST(ICU_CFLAGS) AC_SUBST(ICU_LIBS) - CPPFLAGS="$CPPFLAGS $ICU_CFLAGS -DU_USING_ICU_NAMESPACE=0" + CPPFLAGS="$CPPFLAGS $ICU_CFLAGS" LIBS="$LIBS $ICU_LIBS" AC_MSG_CHECKING([whether boost.regex was compiled with ICU support]) AC_LINK_IFELSE([AC_LANG_PROGRAM([ @@ -257,19 +260,30 @@ PKG_CHECK_MODULES([libcurl], [libcurl], [ # taglib if test "$taglib" != "no" ; then - AC_PATH_PROG(TAGLIB_CONFIG, taglib-config) - if test "$TAGLIB_CONFIG" != "" ; then - CPPFLAGS="$CPPFLAGS `$TAGLIB_CONFIG --cflags`" - LIBS="$LIBS `$TAGLIB_CONFIG --libs`" + PKG_CHECK_MODULES([taglib], [taglib], [ + AC_SUBST(taglib_CFLAGS) + AC_SUBST(taglib_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], , if test "$taglib" = "yes" ; then AC_MSG_ERROR([missing taglib.h header]) fi ) - else - if test "$taglib" = "yes" ; then - AC_MSG_ERROR([taglib-config executable is missing]) - fi fi fi diff --git a/doc/config b/doc/config index 57daa8ef..cc6d2913 100644 --- a/doc/config +++ b/doc/config @@ -125,6 +125,14 @@ # #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 ## visualizer look at a larger slice of time, which results in less jumpy ## visualizer output. @@ -141,6 +149,11 @@ # #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 ##### ## ## ncmpcpp should detect your charset encoding but if it failed to do so, you @@ -172,6 +185,7 @@ ## ## %l - length ## %f - filename +## %F - full filepath ## %D - directory ## %a - artist ## %A - album artist @@ -408,7 +422,7 @@ # #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 # diff --git a/doc/ncmpcpp.1 b/doc/ncmpcpp.1 index e1889f80..075f68ae 100644 --- a/doc/ncmpcpp.1 +++ b/doc/ncmpcpp.1 @@ -114,6 +114,13 @@ Automatically scale visualizer size. .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. .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 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 @@ -472,6 +479,7 @@ For song format you can use: %l - length %f - filename + %F - full filepath %D - directory %a - artist %A - album artist diff --git a/extras/artist_to_albumartist.cpp b/extras/artist_to_albumartist.cpp index c3ddfee3..0349d0ae 100644 --- a/extras/artist_to_albumartist.cpp +++ b/extras/artist_to_albumartist.cpp @@ -34,7 +34,7 @@ enum class CopyResult { Success, NoArtist, AlbumArtistAlreadyInPlace }; bool is_framelist_empty(const TagLib::ID3v2::FrameList &list) { for (auto it = list.begin(); it != list.end(); ++it) - if ((*it)->toString() != TagLib::String::null) + if (!(*it)->toString().isEmpty()) return false; return true; } diff --git a/src/actions.cpp b/src/actions.cpp index 19f343b1..b5d2a823 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1755,33 +1755,46 @@ void JumpToPositionInSong::run() std::string spos; { 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(); } boost::regex rx; 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(what[1]); auto secs = fromString(what[2]); boundsCheck(secs, 0u, 60u); 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(what[1]); 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(what[1]); boundsCheck(percent, 0u, 100u); int secs = (percent * s.getDuration()) / 100.0; 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(what[1]); + auto mins = fromString(what[2]); + auto secs = fromString(what[3]); + boundsCheck(mins, 0u, 60u); + boundsCheck(secs, 0u, 60u); + Mpd.Seek(s.getPosition(), hours * 3600 + mins * 60 + secs); + } 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() diff --git a/src/configuration.cpp b/src/configuration.cpp index c9eb3fef..c1e26913 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -156,14 +156,10 @@ bool configure(int argc, char **argv) if (vm.count("test-lyrics-fetchers")) { std::vector> fetcher_data = { - std::make_tuple("azlyrics", "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("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("zeneszoveg", "rihanna", "umbrella"), }; diff --git a/src/curses/window.cpp b/src/curses/window.cpp index 59c45456..6c0b6d6b 100644 --- a/src/curses/window.cpp +++ b/src/curses/window.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "utility/readline.h" @@ -198,6 +199,8 @@ int add_base() int color_pair_counter; std::vector color_pair_map; +termios orig_termios; + } namespace NC { @@ -400,6 +403,7 @@ int colorCount() void initScreen(bool enable_colors, bool enable_mouse) { + tcgetattr(STDIN_FILENO, &orig_termios); initscr(); if (has_colors() && enable_colors) { @@ -481,6 +485,7 @@ void destroyScreen() Mouse::disable(); curs_set(1); endwin(); + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); } 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_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) { ++m_start_x; diff --git a/src/curses/window.h b/src/curses/window.h index 15728c41..edb93cfe 100644 --- a/src/curses/window.h +++ b/src/curses/window.h @@ -26,7 +26,6 @@ #include "config.h" #include "curses.h" -#include "gcc.h" #include #include diff --git a/src/display.cpp b/src/display.cpp index 4a52d8ce..ddf87932 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -47,6 +47,8 @@ const wchar_t *toColumnName(char c) return L"Filename"; case 'D': return L"Directory"; + case 'F': + return L"Filepath"; case 'a': return L"Artist"; case 'A': diff --git a/src/gcc.h b/src/gcc.h deleted file mode 100644 index 0a2cbd92..00000000 --- a/src/gcc.h +++ /dev/null @@ -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 diff --git a/src/interfaces.h b/src/interfaces.h index abbc31e0..869a2fc7 100644 --- a/src/interfaces.h +++ b/src/interfaces.h @@ -25,7 +25,6 @@ #include #include #include "enums.h" -#include "gcc.h" #include "screens/screen.h" #include "song.h" diff --git a/src/lyrics_fetcher.cpp b/src/lyrics_fetcher.cpp index 12a6fb2f..8a7d2f51 100644 --- a/src/lyrics_fetcher.cpp +++ b/src/lyrics_fetcher.cpp @@ -38,16 +38,8 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher) { std::string s; is >> s; - if (s == "azlyrics") - fetcher = std::make_unique(); - else if (s == "genius") + if (s == "genius") fetcher = std::make_unique(); - else if (s == "musixmatch") - fetcher = std::make_unique(); - else if (s == "sing365") - fetcher = std::make_unique(); - else if (s == "metrolyrics") - fetcher = std::make_unique(); else if (s == "justsomelyrics") fetcher = std::make_unique(); else if (s == "jahlyrics") @@ -76,7 +68,7 @@ LyricsFetcher::Result LyricsFetcher::fetch(const std::string &artist, std::string url = urlTemplate(); boost::replace_all(url, "%artist%", Curl::escape(artist)); boost::replace_all(url, "%title%", Curl::escape(title)); - + std::string data; CURLcode code = Curl::perform(data, url, "", true); @@ -88,9 +80,11 @@ LyricsFetcher::Result LyricsFetcher::fetch(const std::string &artist, auto lyrics = getContent(regex(), data); + //std::cerr << "URL: " << url << "\n"; + //std::cerr << "Data: " << data << "\n"; + if (lyrics.empty() || notLyrics(data)) { - //std::cerr << "Data: " << data << "\n"; //std::cerr << "Empty: " << lyrics.empty() << "\n"; //std::cerr << "Not Lyrics: " << notLyrics(data) << "\n"; result.second = msgNotFound; @@ -140,9 +134,11 @@ void LyricsFetcher::postProcess(std::string &data) const boost::split(lines, data, boost::is_any_of("\n")); for (auto &line : lines) boost::trim(line); - std::unique(lines.begin(), lines.end(), [](std::string &a, std::string &b) { - return a.empty() && b.empty(); - }); + auto last = std::unique( + 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"); 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, const std::string &title) { diff --git a/src/lyrics_fetcher.h b/src/lyrics_fetcher.h index 672352ff..f4cfc0b2 100644 --- a/src/lyrics_fetcher.h +++ b/src/lyrics_fetcher.h @@ -69,34 +69,6 @@ private: const char *URL; }; -struct MusixmatchFetcher : public GoogleLyricsFetcher -{ - virtual const char *name() const override { return "musixmatch.com"; } - -protected: - virtual const char *regex() const override { return "(.*?)"; } - - 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 "
(.*?)(.*?)(.*?)
"; } - - 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 "
(.*?)
"; } -}; - struct JustSomeLyricsFetcher : public GoogleLyricsFetcher { virtual const char *name() const override { return "justsomelyrics.com"; } @@ -105,14 +77,6 @@ protected: virtual const char *regex() const override { return "
(.*?)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"; } @@ -142,7 +106,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,7 +114,7 @@ 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 "
(.*?)