Merge branch 'ncmpcpp:master' into master
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -3,7 +3,7 @@ name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: larson-carter
|
||||
assignees:
|
||||
|
||||
---
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -3,7 +3,7 @@ name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: larson-carter
|
||||
assignees:
|
||||
|
||||
---
|
||||
|
||||
|
||||
16
CHANGELOG.md
16
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
|
||||
|
||||
19
README.md
19
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`.
|
||||
|
||||
|
||||
50
configure.ac
50
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
|
||||
|
||||
|
||||
16
doc/config
16
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
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<unsigned>(what[1]);
|
||||
auto secs = fromString<unsigned>(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<unsigned>(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<unsigned>(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<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
|
||||
Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
|
||||
Statusbar::print("Invalid format ([h]:[mm]:[ss], [m]:[ss], [s]s, [%]%, [%] accepted)");
|
||||
}
|
||||
|
||||
bool SelectItem::canBeRun()
|
||||
|
||||
@@ -156,14 +156,10 @@ bool configure(int argc, char **argv)
|
||||
if (vm.count("test-lyrics-fetchers"))
|
||||
{
|
||||
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("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"),
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sys/select.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "utility/readline.h"
|
||||
@@ -198,6 +199,8 @@ int add_base()
|
||||
int color_pair_counter;
|
||||
std::vector<int> 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;
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "curses.h"
|
||||
#include "gcc.h"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <functional>
|
||||
|
||||
@@ -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':
|
||||
|
||||
29
src/gcc.h
29
src/gcc.h
@@ -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
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <string>
|
||||
#include "enums.h"
|
||||
#include "gcc.h"
|
||||
#include "screens/screen.h"
|
||||
#include "song.h"
|
||||
|
||||
|
||||
@@ -38,16 +38,8 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher)
|
||||
{
|
||||
std::string s;
|
||||
is >> s;
|
||||
if (s == "azlyrics")
|
||||
fetcher = std::make_unique<AzLyricsFetcher>();
|
||||
else if (s == "genius")
|
||||
if (s == "genius")
|
||||
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")
|
||||
fetcher = std::make_unique<JustSomeLyricsFetcher>();
|
||||
else if (s == "jahlyrics")
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 "<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
|
||||
{
|
||||
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"; }
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
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 "<div class=\"song-text\".*?>.*?</h2>(.*?)<a"; }
|
||||
virtual const char *regex() const override { return "<div class=\"inner-text\">(.*?)</div>"; }
|
||||
};
|
||||
|
||||
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 "<div class=\"lyrics-plain-text.*?\">(.*?)</div>"; }
|
||||
virtual const char *regex() const override { return "<div id=\"tartalom_slide_content\"> (.*?)<style>"; }
|
||||
};
|
||||
|
||||
struct InternetLyricsFetcher : public GoogleLyricsFetcher
|
||||
|
||||
@@ -353,8 +353,14 @@ private:
|
||||
};
|
||||
|
||||
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
|
||||
struct State
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -98,7 +98,7 @@ bool MoveToAlbum(NC::Menu<AlbumEntry> &albums, const std::string &primary_tag, c
|
||||
struct SortSongs {
|
||||
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;
|
||||
|
||||
@@ -117,26 +117,16 @@ public:
|
||||
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)
|
||||
< 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::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())
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_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_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
|
||||
|
||||
@@ -76,8 +76,11 @@ private:
|
||||
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 @@ private:
|
||||
const double HZ_MAX;
|
||||
const double GAIN;
|
||||
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<double> m_freq_magnitudes;
|
||||
|
||||
@@ -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_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<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);
|
||||
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<NC::FormattedColor>);
|
||||
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("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<LyricsFetcher_>);
|
||||
p.add<void>("lyrics_fetchers", nullptr,
|
||||
"genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet", [this](std::string v) {
|
||||
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("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background,
|
||||
"no", yes_no);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <boost/format.hpp>
|
||||
#include "curses/window.h"
|
||||
#include "settings.h"
|
||||
#include "gcc.h"
|
||||
#include "interfaces.h"
|
||||
|
||||
namespace Progressbar {
|
||||
|
||||
15
src/tags.cpp
15
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<TagLib::uint>(s.getDate()));
|
||||
tag->setYear(boost::lexical_cast<unsigned>(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<TagLib::uint>(s.getTrack()));
|
||||
tag->setTrack(boost::lexical_cast<unsigned>(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<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()))
|
||||
{
|
||||
@@ -301,15 +302,9 @@ bool write(MPD::MutableSong &s)
|
||||
if (f.isNull())
|
||||
return false;
|
||||
|
||||
bool saved = false;
|
||||
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(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<TagLib::Ogg::Vorbis::File *>(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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <boost/type_traits/is_unsigned.hpp>
|
||||
|
||||
#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 <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(
|
||||
"value is out of bounds ([%1%, %2%] expected, %3% given)") % lbound % ubound % value).str());
|
||||
}
|
||||
|
||||
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(
|
||||
"value is out of bounds ([%1%, ->) expected, %2% given)") % lbound % value).str());
|
||||
}
|
||||
|
||||
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(
|
||||
"value is out of bounds ((<-, %1%] expected, %2% given)") % ubound % value).str());
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "gcc.h"
|
||||
|
||||
template <size_t N> size_t const_strlen(const char (&)[N]) {
|
||||
return N-1;
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user