Compare commits

..

No commits in common. 'wilder' and 'master' have entirely different histories.

  1. 2
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 2
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 79
      .github/workflows/build.yml
  4. 28
      CHANGELOG.md
  5. 2
      INSTALL
  6. 31
      README.md
  7. 158
      autogen.sh
  8. 52
      configure.ac
  9. 17
      doc/config
  10. 8
      doc/ncmpcpp.1
  11. 2
      extras/artist_to_albumartist.cpp
  12. 28
      src/actions.cpp
  13. 8
      src/configuration.cpp
  14. 1
      src/curl_handle.cpp
  15. 6
      src/curses/formatted_color.cpp
  16. 32
      src/curses/window.cpp
  17. 10
      src/curses/window.h
  18. 31
      src/display.cpp
  19. 4
      src/format.cpp
  20. 29
      src/gcc.h
  21. 8
      src/helpers/song_iterator_maker.h
  22. 1
      src/interfaces.h
  23. 98
      src/lyrics_fetcher.cpp
  24. 62
      src/lyrics_fetcher.h
  25. 2
      src/mpdpp.cpp
  26. 8
      src/mpdpp.h
  27. 5
      src/ncmpcpp.cpp
  28. 28
      src/screens/lyrics.cpp
  29. 28
      src/screens/media_library.cpp
  30. 5
      src/screens/playlist_editor.cpp
  31. 15
      src/screens/tag_editor.cpp
  32. 105
      src/screens/visualizer.cpp
  33. 8
      src/screens/visualizer.h
  34. 61
      src/settings.cpp
  35. 7
      src/settings.h
  36. 6
      src/song.cpp
  37. 1
      src/statusbar.h
  38. 19
      src/tags.cpp
  39. 8
      src/title.cpp
  40. 13
      src/utility/comparators.cpp
  41. 7
      src/utility/conversion.h
  42. 15
      src/utility/html.cpp
  43. 1
      src/utility/string.h
  44. 2
      src/utility/type_conversions.cpp

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

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

@ -1,79 +0,0 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: build ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install build dependencies
env:
PACKAGES: >
ccache
libboost-dev
libboost-filesystem-dev
libboost-locale-dev
libboost-program-options-dev
libboost-regex-dev
libboost-thread-dev
libcurl4-gnutls-dev
libfftw3-dev
libmpdclient-dev
libncurses-dev
libreadline-dev
libtag1-dev
zlib1g-dev
run: |
sudo apt update -qq
sudo apt install -y --no-install-recommends ${{ env.PACKAGES }}
- name: Set up ccache
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.ccache
key: ccache-${{ matrix.os }}-${{ github.run_id }}
restore-keys: ccache-${{ matrix.os }}-
- name: Run autoreconf
run: |
autoreconf -fiv
- name: Build ncmpcpp (light)
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache/light
run: |
CC='ccache gcc' CXX='ccache g++' ./configure \
--disable-outputs \
--disable-visualizer \
--disable-clock \
--without-fftw \
--without-taglib \
--with-lto=$(nproc)
make -j$(nproc) || exit 1
- name: Build ncmpcpp (full)
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache/full
run: |
CC='ccache gcc' CXX='ccache g++' ./configure \
--enable-outputs \
--enable-visualizer \
--enable-clock \
--with-fftw \
--with-taglib \
--with-lto=$(nproc)
make -j$(nproc) || exit 1

@ -1,34 +1,8 @@
# ncmpcpp-0.10.2 (2025-??-??)
* Update lyrics fetchers.
* Add support for hexadecimal HTML escape codes.
# ncmpcpp-0.10.1 (2024-10-24)
* Fix compilation with `libc++`.
* Remove `autogen.sh` in favour of `autoreconf`.
* Do not crash when trying to reverse an empty playlist.
# ncmpcpp-0.10 (2024-09-03)
# ncmpcpp-0.10 (????-??-??)
* Add the configuration option `mpd_password`.
* 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.
* Add support for fetching lyrics from tags.
# ncmpcpp-0.9.2 (2021-01-24)
* Revert suppression of output of all external commands as that makes e.g album

@ -30,7 +30,7 @@ The simplest way to compile this package is:
For the next two commands, `csh' users will need to prefix them with
`sh '.
2. Run `autoreconf -fiv' to generate the `configure' script.
2. Run `./autogen.sh' to generate the `configure' script.
3. Run `./configure' to configure the package for your system. This
will take a while. While running, it prints some messages

@ -1,18 +1,11 @@
# NCurses Music Player Client (Plus Plus)
## ncmpcpp – featureful ncurses based MPD client inspired by ncmpc
### Project status
Project page - http://rybczak.net/ncmpcpp/
The project is officially in maintenance mode. I (Andrzej Rybczak) still use it
daily, but it's feature complete for me and there is very limited time I have
for tending to the issue tracker and open pull requests.
No new, substantial features should be expected (at least from me). However, if
there are any serious bugs or the project outright stops compiling because of
new, incompatible versions of dependencies, it will be fixed.
## ncmpcpp – featureful ncurses based MPD client inspired by ncmpc
### Main features:
* tag editor
* playlist editor
* easy to use search engine
@ -25,18 +18,19 @@ new, incompatible versions of dependencies, it will be fixed.
…and a lot more minor functions.
### Dependencies:
* [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
* 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/]
### 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.
@ -44,7 +38,7 @@ The simplest way to compile this package is:
For the next two commands, `csh` users will need to prefix them with
`sh `.
2. Run `autoreconf -fiv` to generate the `configure` script.
2. Run `./autogen.sh` to generate the `configure` script.
3. Run `./configure` to configure the package for your system. This
will take a while. While running, it prints some messages
@ -61,6 +55,7 @@ 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`.

@ -0,0 +1,158 @@
#!/bin/sh
# Run this to set up the build system: configure, makefiles, etc.
# (at one point this was based on the version in enlightenment's cvs)
package="ncmpcpp"
olddir="`pwd`"
srcdir="`dirname $0`"
test -z "$srcdir" && srcdir=.
cd "$srcdir"
DIE=
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/"
AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
VERSIONMKINT="sed -e s/[^0-9]//"
if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
AM_VERSIONS='1.6 1.7 1.8 1.9'
fi
if test -n "$AC_FORCE_VERSION"
then
AC_VERSIONS="$AC_FORCE_VERSION"
else
AC_VERSIONS='2.58 2.59'
fi
versioned_bins ()
{
bin="$1"
needed_int=`echo $VERNEEDED | $VERSIONMKINT`
for i in $VERSIONS
do
i_int=`echo $i | $VERSIONMKINT`
if test $i_int -ge $needed_int
then
echo $bin-$i $bin$i $bin-$i_int $bin$i_int
fi
done
echo $bin
}
for c in autoconf autoheader automake aclocal
do
uc=`echo $c | tr a-z A-Z`
eval "val=`echo '$'$uc`"
if test -n "$val"
then
echo "$uc=$val in environment, will not attempt to auto-detect"
continue
fi
case "$c" in
autoconf|autoheader)
VERNEEDED=`fgrep AC_PREREQ configure.ac | $AC_VERSIONGREP`
VERSIONS="$AC_VERSIONS"
pkg=autoconf
;;
automake|aclocal)
VERNEEDED=`fgrep AUTOMAKE_OPTIONS Makefile.am | $AM_VERSIONGREP`
VERSIONS="$AM_VERSIONS"
pkg=automake
;;
esac
printf "checking for $c ... "
for x in `versioned_bins $c`; do
($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1
if test $? -eq 0
then
echo $x
eval $uc=$x
break
fi
done
eval "val=`echo '$'$uc`"
if test -z "$val"
then
if test $c = $pkg
then
DIE="$DIE $c=$VERNEEDED"
else
DIE="$DIE $c($pkg)=$VERNEEDED"
fi
fi
done
if test -n "$LIBTOOLIZE"
then
echo "LIBTOOLIZE=$LIBTOOLIZE in environment," \
"will not attempt to auto-detect"
else
printf "checking for libtoolize ... "
for x in libtoolize glibtoolize
do
($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1
if test $? -eq 0
then
echo $x
LIBTOOLIZE=$x
break
fi
done
fi
if test -z "$LIBTOOLIZE"
then
DIE="$DIE libtoolize(libtool)"
fi
if test -n "$DIE"
then
echo "You must have the following installed to compile $package:"
for i in $DIE
do
printf ' '
echo $i | sed -e 's/(/ (from /' -e 's/=\(.*\)/ (>= \1)/'
done
echo "Download the appropriate package(s) for your system,"
echo "or get the source from one of the GNU ftp sites"
echo "listed in http://www.gnu.org/order/ftp.html"
exit 1
fi
echo "Generating configuration files for $package, please wait...."
ACLOCAL_FLAGS="$ACLOCAL_FLAGS"
# /usr/share/aclocal is most likely included by default, already...
ac_local_paths='
/usr/local/share/aclocal
/sw/share/aclocal
/usr/pkg/share/aclocal
/opt/share/aclocal
/usr/gnu/share/aclocal
'
for i in $ac_local_paths; do
if test -d "$i"; then
ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $i"
# we probably only want one of these...
break
fi
done
echo " $ACLOCAL $ACLOCAL_FLAGS"
$ACLOCAL $ACLOCAL_FLAGS || exit 1
echo " $AUTOHEADER"
$AUTOHEADER || exit 1
echo " $LIBTOOLIZE --automake"
$LIBTOOLIZE --automake || exit 1
echo " $AUTOMAKE --add-missing $AUTOMAKE_FLAGS"
$AUTOMAKE --add-missing $AUTOMAKE_FLAGS || exit 1
echo " $AUTOCONF"
$AUTOCONF || exit 1

@ -1,15 +1,15 @@
AC_INIT([ncmpcpp],[0.10.2_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.71])
AC_PREREQ(2.59)
AC_LANG([C++])
AC_LANG_CPLUSPLUS
AC_PROG_CXX
LT_INIT
AC_PROG_LIBTOOL
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++20
AC_MSG_CHECKING([whether compiler supports -std=c++20])
# -std=c++14
AC_MSG_CHECKING([whether compiler supports -std=c++14])
old_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="-std=c++20"
CXXFLAGS="-std=c++14"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]])],
AC_MSG_RESULT([yes])
std_cpp14="-std=c++20",
std_cpp14="-std=c++14",
AC_MSG_RESULT([no])
AC_MSG_ERROR([[Your compiler doesn't seem to support C++20, please upgrade]])
AC_MSG_ERROR([[Your compiler doesn't seem to support C++14, please upgrade (GCC >= 5)]])
)
CXXFLAGS="$old_CXXFLAGS $std_cpp14"
@ -90,9 +90,6 @@ 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)
@ -142,7 +139,7 @@ LIBS="$LIBS $BOOST_REGEX_LIBS"
BOOST_THREAD
AC_SUBST(BOOST_THREAD_LDFLAGS)
AC_SUBST(BOOST_THREAD_LIBS)
LDFLAGS="$LDFLAGS $BOOST_THREAD_LDFLAGS"
LDFLAGS+="$LDFLAGS $BOOST_THREAD_LDFLAGS"
LIBS="$LIBS $BOOST_THREAD_LIBS"
# icu
@ -152,7 +149,7 @@ PKG_CHECK_MODULES([ICU], [icu-i18n icu-uc], [
old_LIBS="$LIBS"
AC_SUBST(ICU_CFLAGS)
AC_SUBST(ICU_LIBS)
CPPFLAGS="$CPPFLAGS $ICU_CFLAGS"
CPPFLAGS="$CPPFLAGS $ICU_CFLAGS -DU_USING_ICU_NAMESPACE=0"
LIBS="$LIBS $ICU_LIBS"
AC_MSG_CHECKING([whether boost.regex was compiled with ICU support])
AC_LINK_IFELSE([AC_LANG_PROGRAM([
@ -260,30 +257,19 @@ PKG_CHECK_MODULES([libcurl], [libcurl], [
# taglib
if test "$taglib" != "no" ; then
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_PATH_PROG(TAGLIB_CONFIG, taglib-config)
if test "$TAGLIB_CONFIG" != "" ; then
CPPFLAGS="$CPPFLAGS `$TAGLIB_CONFIG --cflags`"
LIBS="$LIBS `$TAGLIB_CONFIG --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

@ -125,14 +125,6 @@
#
#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.
@ -149,11 +141,6 @@
#
#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
@ -185,7 +172,6 @@
##
## %l - length
## %f - filename
## %F - full filepath
## %D - directory
## %a - artist
## %A - album artist
@ -229,7 +215,6 @@
## - 9 - end of current color
## - b - bold text
## - u - underline text
## - i - italic text
## - r - reverse colors
## - a - use alternative character set
##
@ -422,7 +407,7 @@
#
#cyclic_scrolling = no
#
#lyrics_fetchers = tags, genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet
#lyrics_fetchers = azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet
#
#follow_now_playing_lyrics = no
#

@ -114,13 +114,6 @@ 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
@ -479,7 +472,6 @@ 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().isEmpty())
if ((*it)->toString() != TagLib::String::null)
return false;
return true;
}

@ -1746,46 +1746,33 @@ void JumpToPositionInSong::run()
std::string spos;
{
Statusbar::ScopedLock slock;
Statusbar::put() << "Position to go (in %/h:m:ss/m:ss/seconds(s)): ";
Statusbar::put() << "Position to go (in %/m:ss/seconds(s)): ";
spos = wFooter->prompt();
}
boost::regex rx;
boost::smatch what;
// mm:ss
if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})")))
if (boost::regex_match(spos, what, rx.assign("([0-9]+):([0-9]{2})"))) // mm:ss
{
auto mins = fromString<unsigned>(what[1]);
auto secs = fromString<unsigned>(what[2]);
boundsCheck(secs, 0u, 60u);
Mpd.Seek(s.getPosition(), mins * 60 + secs);
}
// position in seconds
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s")))
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)s"))) // position in seconds
{
auto secs = fromString<unsigned>(what[1]);
Mpd.Seek(s.getPosition(), secs);
}
// position in%
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}")))
else if (boost::regex_match(spos, what, rx.assign("([0-9]+)[%]{0,1}"))) // position in %
{
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 ([h]:[mm]:[ss], [m]:[ss], [s]s, [%]%, [%] accepted)");
Statusbar::print("Invalid format ([m]:[ss], [s]s, [%]%, [%] accepted)");
}
bool SelectItem::canBeRun()
@ -1998,10 +1985,7 @@ bool ReversePlaylist::canBeRun()
return false;
m_begin = myPlaylist->main().begin();
m_end = myPlaylist->main().end();
if (m_begin == m_end)
return false;
else
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
return findSelectedRangeAndPrintInfoIfNot(m_begin, m_end);
}
void ReversePlaylist::run()

@ -156,10 +156,14 @@ 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", "rihanna", "umbrella"),
std::make_tuple("plyrics", "offspring", "genocide"),
std::make_tuple("tekstowo", "rihanna", "umbrella"),
std::make_tuple("zeneszoveg", "rihanna", "umbrella"),
};
@ -171,7 +175,7 @@ bool configure(int argc, char **argv)
<< fetcher->name()
<< " : "
<< std::flush;
auto result = fetcher->fetch(std::get<1>(data), std::get<2>(data), {});
auto result = fetcher->fetch(std::get<1>(data), std::get<2>(data));
std::cout << (result.first ? "ok" : "failed")
<< "\n";
}

@ -41,6 +41,7 @@ CURLcode Curl::perform(std::string &data, const std::string &URL, const std::str
curl_easy_setopt(c, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT, timeout);
curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(c, CURLOPT_USERAGENT, "ncmpcpp " VERSION);
if (follow_redirect)
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
if (!referer.empty())

@ -30,8 +30,7 @@ void verifyFormats(const NC::FormattedColor::Formats &formats)
if (fmt == NC::Format::NoBold
|| fmt == NC::Format::NoUnderline
|| fmt == NC::Format::NoReverse
|| fmt == NC::Format::NoAltCharset
|| fmt == NC::Format::NoItalic)
|| fmt == NC::Format::NoAltCharset)
throw std::logic_error("FormattedColor can't hold disabling formats");
}
}
@ -72,9 +71,6 @@ std::istream &NC::operator>>(std::istream &is, NC::FormattedColor &fc)
case 'a':
formats.push_back(NC::Format::AltCharset);
break;
case 'i':
formats.push_back(NC::Format::Italic);
break;
default:
is.setstate(std::ios::failbit);
break;

@ -24,7 +24,6 @@
#include <cstdlib>
#include <iostream>
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>
#include "utility/readline.h"
@ -199,8 +198,6 @@ int add_base()
int color_pair_counter;
std::vector<int> color_pair_map;
termios orig_termios;
}
namespace NC {
@ -349,10 +346,6 @@ NC::Format reverseFormat(NC::Format fmt)
return NC::Format::NoAltCharset;
case NC::Format::NoAltCharset:
return NC::Format::AltCharset;
case NC::Format::Italic:
return NC::Format::NoItalic;
case NC::Format::NoItalic:
return NC::Format::Italic;
}
// Unreachable, silence GCC.
return fmt;
@ -403,7 +396,6 @@ int colorCount()
void initScreen(bool enable_colors, bool enable_mouse)
{
tcgetattr(STDIN_FILENO, &orig_termios);
initscr();
if (has_colors() && enable_colors)
{
@ -485,7 +477,6 @@ 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,
@ -503,9 +494,14 @@ Window::Window(size_t startx, size_t starty, size_t width, size_t height,
m_bold_counter(0),
m_underline_counter(0),
m_reverse_counter(0),
m_alt_charset_counter(0),
m_italic_counter(0)
m_alt_charset_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;
@ -546,7 +542,6 @@ Window::Window(const Window &rhs)
, m_underline_counter(rhs.m_underline_counter)
, m_reverse_counter(rhs.m_reverse_counter)
, m_alt_charset_counter(rhs.m_alt_charset_counter)
, m_italic_counter(rhs.m_italic_counter)
{
setColor(m_color);
}
@ -571,7 +566,6 @@ Window::Window(Window &&rhs)
, m_underline_counter(rhs.m_underline_counter)
, m_reverse_counter(rhs.m_reverse_counter)
, m_alt_charset_counter(rhs.m_alt_charset_counter)
, m_italic_counter(rhs.m_italic_counter)
{
rhs.m_window = nullptr;
}
@ -597,7 +591,6 @@ Window &Window::operator=(Window rhs)
std::swap(m_underline_counter, rhs.m_underline_counter);
std::swap(m_reverse_counter, rhs.m_reverse_counter);
std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
std::swap(m_italic_counter, rhs.m_italic_counter);
return *this;
}
@ -787,11 +780,6 @@ void Window::altCharset(bool altcharset_state) const
(altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
}
void Window::italic(bool italic_state) const
{
(italic_state ? wattron : wattroff)(m_window, A_ITALIC);
}
void Window::setTimeout(int timeout)
{
m_window_timeout = timeout;
@ -1425,12 +1413,6 @@ Window &Window::operator<<(Format format)
case Format::NoAltCharset:
decrease_flag(*this, m_alt_charset_counter, &Window::altCharset);
break;
case Format::Italic:
increase_flag(*this, m_italic_counter, &Window::italic);
break;
case Format::NoItalic:
decrease_flag(*this, m_italic_counter, &Window::italic);
break;
}
return *this;
}

@ -26,6 +26,7 @@
#include "config.h"
#include "curses.h"
#include "gcc.h"
#include <boost/optional.hpp>
#include <functional>
@ -199,8 +200,7 @@ enum class Format {
Bold, NoBold,
Underline, NoUnderline,
Reverse, NoReverse,
AltCharset, NoAltCharset,
Italic, NoItalic
AltCharset, NoAltCharset
};
NC::Format reverseFormat(NC::Format fmt);
@ -531,11 +531,6 @@ private:
/// @param altcharset_state state of altcharset attribute
///
void altCharset(bool altcharset_state) const;
/// Sets state of italic attribute (internal use only)
/// @param italic_state state of italic attribute
///
void italic(bool italic_state) const;
/// pointer to helper function used by getString()
/// @see getString()
@ -567,7 +562,6 @@ private:
int m_underline_counter;
int m_reverse_counter;
int m_alt_charset_counter;
int m_italic_counter;
};
}

@ -47,8 +47,6 @@ 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':
@ -112,8 +110,6 @@ void setProperties(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list,
&& song_pos == Status::State::currentSongPosition();
if (is_now_playing)
menu << Config.now_playing_prefix;
else
menu << Config.not_playing_prefix;
is_in_playlist = !myPlaylist->isActiveWindow(menu)
&& myPlaylist->checkForSong(s);
@ -133,8 +129,6 @@ void unsetProperties(NC::Menu<T> &menu, bool separate_albums, bool is_now_playin
if (is_now_playing)
menu << Config.now_playing_suffix;
else
menu << Config.not_playing_suffix;
if (separate_albums)
menu << NC::Format::NoUnderline;
@ -164,8 +158,6 @@ void showSongs(NC::Menu<T> &menu, const MPD::Song &s, const SongList &list, cons
}
if (is_now_playing)
x_off -= Config.now_playing_suffix_length;
else
x_off -= Config.not_playing_suffix_length;
if (is_selected)
x_off -= Config.selected_item_suffix_length;
menu << NC::TermManip::ClearToEOL << NC::XY(x_off, y) << right_aligned;
@ -199,14 +191,9 @@ void showSongsInColumns(NC::Menu<T> &menu, const MPD::Song &s, const SongList &l
}
if (is_now_playing)
{
// menu_width -= Config.now_playing_prefix_length;
//menu_width -= Config.now_playing_suffix_length;
} else
{
//menu_width -= Config.not_playing_prefix_length;
//menu_width -= Config.not_playing_suffix_length;
menu_width -= Config.now_playing_prefix_length;
menu_width -= Config.now_playing_suffix_length;
}
if (is_selected)
{
menu_width -= Config.selected_item_prefix_length;
@ -287,9 +274,7 @@ std::string Display::Columns(size_t list_width)
std::string result;
if (Config.columns.empty())
return result;
result += Config.not_playing_prefix.str();
int width;
int remained_width = list_width;
std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
@ -308,11 +293,11 @@ std::string Display::Columns(size_t list_width)
// and next column, so we substract it now and restore later.
if (it != last)
--width;
// if column doesn't fit into screen, discard it and any other after it.
if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
break;
std::wstring name;
if (it->name.empty())
{
@ -330,7 +315,7 @@ std::string Display::Columns(size_t list_width)
else
name = it->name;
wideCut(name, width);
int x_off = std::max(0, width - int(wideLength(name)));
if (it->right_alignment)
{
@ -342,7 +327,7 @@ std::string Display::Columns(size_t list_width)
result += Charset::utf8ToLocale(ToString(name));
result += std::string(x_off, NC::Key::Space);
}
if (it != last)
{
// add missing width's part and restore the value.
@ -350,7 +335,7 @@ std::string Display::Columns(size_t list_width)
result += ' ';
}
}
return result;
}

@ -192,8 +192,6 @@ expressions<CharT> parseBracket(const string<CharT> &s,
result.push_back(NC::Format::AltCharset);
else if (flags & Format::Flags::Format && *it == 'r')
result.push_back(NC::Format::Reverse);
else if (flags & Format::Flags::Format && *it == 'i')
result.push_back(NC::Format::Italic);
else if (flags & Format::Flags::Format && *it == '/')
{
++it;
@ -206,8 +204,6 @@ expressions<CharT> parseBracket(const string<CharT> &s,
result.push_back(NC::Format::NoAltCharset);
else if (*it == 'r')
result.push_back(NC::Format::NoReverse);
else if (*it == 'i')
result.push_back(NC::Format::NoItalic);
else
throwError(s, it, invalidCharacter(*it));
}

@ -0,0 +1,29 @@
/***************************************************************************
* 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

@ -46,8 +46,8 @@ SongIterator makeSongIterator(IteratorT it)
> Extractor;
static_assert(
std::is_convertible<
std::invoke_result_t<Extractor, typename IteratorT::reference>,
SongProperties &
typename std::result_of<Extractor(typename IteratorT::reference)>::type,
SongProperties &
>::value, "invalid result type of SongPropertiesExtractor");
return SongIterator(boost::make_transform_iterator(it, Extractor{}));
}
@ -60,8 +60,8 @@ ConstSongIterator makeConstSongIterator(ConstIteratorT it)
> Extractor;
static_assert(
std::is_convertible<
std::invoke_result_t<Extractor, typename ConstIteratorT::reference>,
const SongProperties &
typename std::result_of<Extractor(typename ConstIteratorT::reference)>::type,
const SongProperties &
>::value, "invalid result type of SongPropertiesExtractor");
return ConstSongIterator(boost::make_transform_iterator(it, Extractor{}));
}

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

@ -19,6 +19,7 @@
***************************************************************************/
#include "config.h"
#include "curl_handle.h"
#include <cstdlib>
#include <cstring>
@ -28,15 +29,8 @@
#include <boost/algorithm/string/trim.hpp>
#include <boost/regex.hpp>
#ifdef HAVE_TAGLIB_H
#include <fileref.h>
#include <tpropertymap.h>
#endif // HAVE_TAGLIB_H
#include "charset.h"
#include "curl_handle.h"
#include "lyrics_fetcher.h"
#include "settings.h"
#include "utility/html.h"
#include "utility/string.h"
@ -44,8 +38,16 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher)
{
std::string s;
is >> s;
if (s == "genius")
if (s == "azlyrics")
fetcher = std::make_unique<AzLyricsFetcher>();
else 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")
@ -58,10 +60,6 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher)
fetcher = std::make_unique<ZeneszovegFetcher>();
else if (s == "internet")
fetcher = std::make_unique<InternetLyricsFetcher>();
#ifdef HAVE_TAGLIB_H
else if (s == "tags")
fetcher = std::make_unique<TagsLyricsFetcher>();
#endif // HAVE_TAGLIB_H
else
is.setstate(std::ios::failbit);
return is;
@ -70,8 +68,7 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher)
const char LyricsFetcher::msgNotFound[] = "Not found";
LyricsFetcher::Result LyricsFetcher::fetch(const std::string &artist,
const std::string &title,
[[maybe_unused]] const MPD::Song &song)
const std::string &title)
{
Result result;
result.first = false;
@ -79,7 +76,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);
@ -91,11 +88,9 @@ 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;
@ -142,14 +137,12 @@ void LyricsFetcher::postProcess(std::string &data) const
stripHtmlTags(data);
// Remove indentation from each line and collapse multiple newlines into one.
std::vector<std::string> lines;
boost::split(lines, data, boost::is_any_of("\r\n"));
boost::split(lines, data, boost::is_any_of("\n"));
for (auto &line : lines)
boost::trim(line);
auto last = std::unique(
lines.begin(),
lines.end(),
[](std::string &a, std::string &b) { return a.empty() && b.empty(); });
lines.erase(last, lines.end());
std::unique(lines.begin(), lines.end(), [](std::string &a, std::string &b) {
return a.empty() && b.empty();
});
data = boost::algorithm::join(lines, "\n");
boost::trim(data);
}
@ -157,8 +150,7 @@ void LyricsFetcher::postProcess(std::string &data) const
/**********************************************************************/
LyricsFetcher::Result GoogleLyricsFetcher::fetch(const std::string &artist,
const std::string &title,
const MPD::Song &song)
const std::string &title)
{
Result result;
result.first = false;
@ -200,7 +192,7 @@ LyricsFetcher::Result GoogleLyricsFetcher::fetch(const std::string &artist,
data = unescapeHtmlUtf8(urls[0]);
URL = data.c_str();
return LyricsFetcher::fetch("", "", song);
return LyricsFetcher::fetch("", "");
}
bool GoogleLyricsFetcher::isURLOk(const std::string &url)
@ -210,11 +202,18 @@ 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,
const MPD::Song &song)
const std::string &title)
{
GoogleLyricsFetcher::fetch(artist, title, song);
GoogleLyricsFetcher::fetch(artist, title);
LyricsFetcher::Result result;
result.first = false;
result.second = "The following site may contain lyrics for this song: ";
@ -227,42 +226,3 @@ bool InternetLyricsFetcher::isURLOk(const std::string &url)
URL = url;
return false;
}
#ifdef HAVE_TAGLIB_H
LyricsFetcher::Result TagsLyricsFetcher::fetch([[maybe_unused]] const std::string &artist,
[[maybe_unused]] const std::string &title,
const MPD::Song &song)
{
LyricsFetcher::Result result;
result.first = false;
std::string path;
if (song.isFromDatabase())
path += Config.mpd_music_dir;
path += song.getURI();
TagLib::FileRef f(path.c_str());
if (f.isNull())
{
result.second = "Could not open file";
return result;
}
TagLib::PropertyMap properties = f.file()->properties();
if (properties.contains("LYRICS"))
{
result.first = true;
result.second = properties["LYRICS"].toString("\n\n").to8Bit(true);
}
else if (properties.contains("UNSYNCEDLYRICS"))
{
result.first = true;
result.second = properties["UNSYNCEDLYRICS"].toString("\n\n").to8Bit(true);
}
else
result.second = "No lyrics in tags";
return result;
}
#endif // HAVE_TAGLIB_H

@ -26,8 +26,6 @@
#include <memory>
#include <string>
#include "song.h"
struct LyricsFetcher
{
typedef std::pair<bool, std::string> Result;
@ -35,7 +33,7 @@ struct LyricsFetcher
virtual ~LyricsFetcher() { }
virtual const char *name() const = 0;
virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song);
virtual Result fetch(const std::string &artist, const std::string &title);
protected:
virtual const char *urlTemplate() const = 0;
@ -59,7 +57,7 @@ std::istream &operator>>(std::istream &is, LyricsFetcher_ &fetcher);
struct GoogleLyricsFetcher : public LyricsFetcher
{
virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song);
virtual Result fetch(const std::string &artist, const std::string &title);
protected:
virtual const char *urlTemplate() const { return URL; }
@ -71,6 +69,34 @@ 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"; }
@ -79,12 +105,20 @@ 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"; }
protected:
virtual const char *regex() const override { return "<div data-lyrics-container.*?>(.*?)</div>"; }
virtual const char *regex() const override { return "<div class=\"[Ll]yrics.*?>(.*?)</div>"; }
};
struct JahLyricsFetcher : public GoogleLyricsFetcher
@ -108,7 +142,7 @@ struct TekstowoFetcher : public GoogleLyricsFetcher
virtual const char *name() const override { return "tekstowo.pl"; }
protected:
virtual const char *regex() const override { return "<div class=\"inner-text\">(.*?)</div>"; }
virtual const char *regex() const override { return "<div class=\"song-text\".*?>.*?</h2>(.*?)<a"; }
};
struct ZeneszovegFetcher : public GoogleLyricsFetcher
@ -116,13 +150,13 @@ 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 trans_original\">(.*?)</div>"; }
virtual const char *regex() const override { return "<div class=\"lyrics-plain-text.*?\">(.*?)</div>"; }
};
struct InternetLyricsFetcher : public GoogleLyricsFetcher
{
virtual const char *name() const override { return "the Internet"; }
virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song) override;
virtual Result fetch(const std::string &artist, const std::string &title) override;
protected:
virtual const char *siteKeyword() const override { return nullptr; }
@ -134,16 +168,4 @@ private:
std::string URL;
};
#ifdef HAVE_TAGLIB_H
struct TagsLyricsFetcher : public LyricsFetcher
{
virtual const char *name() const override { return "tags"; }
virtual Result fetch(const std::string &artist, const std::string &title, const MPD::Song &song) override;
protected:
virtual const char *urlTemplate() const override { return ""; }
virtual const char *regex() const override { return ""; }
};
#endif // HAVE_TAGLIB_H
#endif // NCMPCPP_LYRICS_FETCHER_H

@ -147,7 +147,7 @@ unsigned Connection::Version() const
void Connection::SetHostname(const std::string &host)
{
size_t at = host.find("@");
if (at != 0 && at != std::string::npos)
if (at != std::string::npos)
{
m_password = host.substr(0, at);
m_host = host.substr(at+1);

@ -353,14 +353,8 @@ private:
};
template <typename ObjectT>
struct Iterator
struct Iterator: std::iterator<std::input_iterator_tag, ObjectT>
{
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,10 +219,7 @@ int main(int argc, char **argv)
try
{
auto k = Bindings.get(input);
[[maybe_unused]] bool executed = std::any_of(
k.first,
k.second,
std::bind(&Binding::execute, ph::_1));
std::any_of(k.first, k.second, std::bind(&Binding::execute, ph::_1));
}
catch (ConversionError &e)
{

@ -151,7 +151,7 @@ boost::optional<std::string> downloadLyrics(
<< NC::Format::NoBold << "... ";
}
}
auto result_ = fetcher_->fetch(s_artist, s_title, s);
auto result_ = fetcher_->fetch(s_artist, s_title);
if (result_.first == false)
{
if (shared_buffer)
@ -361,25 +361,6 @@ 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] {
@ -396,10 +377,7 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
break;
}
lyrics_file = lyricsFilename(consumer->songs.front().song());
// 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))
if (!boost::filesystem::exists(lyrics_file))
{
cs = consumer->songs.front();
if (cs.notify())
@ -411,7 +389,7 @@ void Lyrics::fetchInBackground(const MPD::Song &s, bool notify_)
}
consumer->songs.pop();
}
if (!cs.song().empty() && !cs.song().isStream())
if (!cs.song().empty())
{
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, 4> GetFuns;
static const std::array<MPD::Song::GetFunction, 3> GetFuns;
LocaleStringComparison m_cmp;
@ -117,16 +117,26 @@ 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, 4> SortSongs::GetFuns = {{
const std::array<MPD::Song::GetFunction, 3> SortSongs::GetFuns = {{
&MPD::Song::getDate,
&MPD::Song::getAlbum,
&MPD::Song::getDisc,
&MPD::Song::getTrackNumber,
&MPD::Song::getDisc
}};
class SortAlbumEntries {
@ -336,9 +346,9 @@ void MediaLibrary::update()
for (const auto &album : albums)
{
auto entry = AlbumEntry(
Album(std::get<0>(album.first),
std::get<1>(album.first),
std::get<2>(album.first),
Album(std::move(std::get<0>(album.first)),
std::move(std::get<1>(album.first)),
std::move(std::get<2>(album.first)),
album.second));
if (idx < Albums.size())
Albums[idx].value() = std::move(entry);
@ -435,8 +445,8 @@ void MediaLibrary::update()
{
auto entry = AlbumEntry(
Album(primary_tag,
std::get<0>(album.first),
std::get<1>(album.first),
std::move(std::get<0>(album.first)),
std::move(std::get<1>(album.first)),
album.second));
if (idx < Albums.size())
{

@ -169,7 +169,10 @@ void PlaylistEditor::update()
}
catch (MPD::ServerError &e)
{
Status::handleServerError(e);
if (e.code() == MPD_SERVER_ERROR_SYSTEM) // no playlists directory
Statusbar::print(e.what());
else
throw;
}
if (idx < Playlists.size())
Playlists.resizeList(idx);

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

@ -83,8 +83,7 @@ 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_FLIPPED(ToWString(""))
SMOOTH_CHARS(ToWString("▁▂▃▄▅▆▇█"))
#endif
{
InitDataSource();
@ -96,7 +95,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_freqspace.reserve(500);
m_dft_logspace.reserve(500);
m_bar_heights.reserve(100);
# endif // HAVE_FFTW3_H
}
@ -108,7 +107,7 @@ void Visualizer::switchTo()
m_reset_output = true;
drawHeader();
# ifdef HAVE_FFTW3_H
GenFreqSpace();
GenLogspace();
m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H
}
@ -122,7 +121,7 @@ void Visualizer::resize()
hasToBeResized = 0;
InitVisualization();
# ifdef HAVE_FFTW3_H
GenFreqSpace();
GenLogspace();
m_bar_heights.reserve(w.getWidth());
# endif // HAVE_FFTW3_H
}
@ -421,7 +420,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[0]
<< Config.visualizer_chars[1]
<< NC::FormattedColor::End<>(c);
}
}
@ -450,7 +449,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_freqspace[0])
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[0])
++cur_bin;
for (size_t x = 0; x < win_width; ++x)
{
@ -459,10 +458,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_freqspace[x])
while (cur_bin < m_fftw_results && Bin2Hz(cur_bin) < m_dft_logspace[x])
{
// check left bound if not first index
if (x == 0 || Bin2Hz(cur_bin) >= m_dft_freqspace[x-1])
if (x == 0 || Bin2Hz(cur_bin) >= m_dft_logspace[x-1])
{
bar_height += m_freq_magnitudes[cur_bin];
++count;
@ -476,19 +475,8 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
// average bins
bar_height /= count;
// 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;
}
// log scale bar heights
bar_height = (20 * log10(bar_height) + DYNAMIC_RANGE + GAIN) / DYNAMIC_RANGE;
// Scale bar height between 0 and height
bar_height = bar_height > 0 ? bar_height * height : 0;
bar_height = bar_height > height ? height : bar_height;
@ -510,12 +498,7 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
++h_idx;
} else {
// data point does not exist, need to interpolate
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;
}
h = Interpolate(x, h_idx);
}
for (size_t j = 0; j < h; ++j)
@ -535,7 +518,8 @@ void Visualizer::DrawFrequencySpectrum(const int16_t *buf, ssize_t samples, size
} else {
// fractional height
if (flipped) {
ch = SMOOTH_CHARS_FLIPPED[idx];
ch = SMOOTH_CHARS[size-idx-2];
color = NC::FormattedColor(color.color(), {NC::Format::Reverse});
} else {
ch = SMOOTH_CHARS[idx];
}
@ -560,7 +544,7 @@ void Visualizer::DrawFrequencySpectrumStereo(const int16_t *buf_left, const int1
DrawFrequencySpectrum(buf_right, samples, height, w.getHeight() - height);
}
double Visualizer::InterpolateCubic(size_t x, size_t h_idx)
double Visualizer::Interpolate(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;
@ -605,33 +589,6 @@ double Visualizer::InterpolateCubic(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
@ -660,36 +617,10 @@ 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_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();
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);
}
}
#endif // HAVE_FFTW3_H

@ -76,11 +76,8 @@ 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 InterpolateCubic(size_t, size_t);
double InterpolateLinear(size_t, size_t);
double Interpolate(size_t, size_t);
# endif // HAVE_FFTW3_H
void InitDataSource();
@ -116,8 +113,7 @@ private:
const double HZ_MAX;
const double GAIN;
const std::wstring SMOOTH_CHARS;
const std::wstring SMOOTH_CHARS_FLIPPED;
std::vector<double> m_dft_freqspace;
std::vector<double> m_dft_logspace;
std::vector<std::pair<size_t, double>> m_bar_heights;
std::vector<double> m_freq_magnitudes;

@ -212,6 +212,28 @@ void deprecated(const char *option, const char *version_removal,
bool Configuration::read(const std::vector<std::string> &config_paths, bool ignore_errors)
{
option_parser p;
// Deprecated options.
p.add("visualizer_fifo_path", &visualizer_fifo_path, "", [](std::string v) {
if (!v.empty())
{
deprecated("visualizer_fifo_path",
"0.10",
"replaced by visualizer_data_source");
}
return adjust_path(v);
});
p.add<void>("visualizer_sync_interval", nullptr, "", [](std::string v) {
if (!v.empty())
{
deprecated("visualizer_sync_interval",
"0.10",
"set 'buffer_time' parameter of your MPD audio output to '100000' "
"(100ms) or lower if you experience synchronization issues "
"between audio and visualization");
}
});
// keep the same order of variables as in configuration file
p.add("ncmpcpp_directory", &ncmpcpp_directory, "~/.config/ncmpcpp/", adjust_directory);
p.add("lyrics_directory", &lyrics_directory, "~/.lyrics/", adjust_directory);
@ -248,12 +270,11 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
p.add("visualizer_fps", &visualizer_fps,
"60", [](std::string v) {
uint32_t result = verbose_lexical_cast<uint32_t>(v);
boundsCheck<uint32_t>(result, 30, 1000);
boundsCheck<uint32_t>(result, 30, 144);
return result;
});
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);
@ -278,8 +299,6 @@ 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) {
@ -356,16 +375,6 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
nullptr,
std::ref(now_playing_suffix_length),
ph::_1));
p.add("not_playing_prefix", &not_playing_prefix, "",
std::bind(buffer_wlength,
nullptr,
std::ref(not_playing_prefix_length),
ph::_1));
p.add("not_playing_suffix", &not_playing_suffix, "",
std::bind(buffer_wlength,
nullptr,
std::ref(not_playing_suffix_length),
ph::_1));
p.add("browser_playlist_prefix", &browser_playlist_prefix, "$2playlist$9 ", buffer);
p.add("selected_item_prefix", &selected_item_prefix, "$6",
std::bind(buffer_wlength,
@ -471,27 +480,9 @@ 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<void>("lyrics_fetchers", nullptr,
#ifdef HAVE_TAGLIB_H
"tags, "
#endif
"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("lyrics_fetchers", &lyrics_fetchers,
"azlyrics, genius, musixmatch, sing365, metrolyrics, justsomelyrics, jahlyrics, plyrics, tekstowo, zeneszoveg, internet",
list_of<LyricsFetcher_>);
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,13 +86,10 @@ 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;
@ -112,8 +109,6 @@ struct Configuration
NC::Buffer selected_item_suffix;
NC::Buffer now_playing_prefix;
NC::Buffer now_playing_suffix;
NC::Buffer not_playing_prefix;
NC::Buffer not_playing_suffix;
NC::Buffer modified_item_prefix;
NC::Buffer current_item_prefix;
NC::Buffer current_item_suffix;
@ -214,8 +209,6 @@ struct Configuration
size_t selected_item_suffix_length;
size_t now_playing_prefix_length;
size_t now_playing_suffix_length;
size_t not_playing_prefix_length;
size_t not_playing_suffix_length;
size_t current_item_prefix_length;
size_t current_item_suffix_length;
size_t current_item_inactive_column_prefix_length;

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

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

@ -92,7 +92,7 @@ void readID3v2Tags(mpd_song *s, TagLib::ID3v2::Tag *tag)
readFrame(frames["TRCK"], "Track");
readFrame(frames["TCON"], "Genre");
readFrame(frames["TCOM"], "Composer");
readFrame(frames["TPE4"], "Performer");
readFrame(frames["TPE3"], "Performer");
readFrame(frames["TPOS"], "Disc");
readFrame(frames["COMM"], "Comment");
}
@ -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<unsigned>(s.getDate()));
tag->setYear(boost::lexical_cast<TagLib::uint>(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<unsigned>(s.getTrack()));
tag->setTrack(boost::lexical_cast<TagLib::uint>(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";
}
@ -166,7 +166,7 @@ void writeID3v2Tags(const MPD::MutableSong &s, TagLib::ID3v2::Tag *tag)
writeID3v2("TRCK", tagList(s, &MPD::Song::getTrack));
writeID3v2("TCON", tagList(s, &MPD::Song::getGenre));
writeID3v2("TCOM", tagList(s, &MPD::Song::getComposer));
writeID3v2("TPE4", tagList(s, &MPD::Song::getPerformer));
writeID3v2("TPE3", tagList(s, &MPD::Song::getPerformer));
writeID3v2("TPOS", tagList(s, &MPD::Song::getDisc));
writeID3v2("COMM", tagList(s, &MPD::Song::getComment));
}
@ -174,7 +174,6 @@ 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());
};
@ -262,7 +261,7 @@ void read(mpd_song *s)
if (f.isNull())
return;
setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->lengthInSeconds()));
setAttribute(s, "Time", boost::lexical_cast<std::string>(f.audioProperties()->length()));
if (auto mpeg_file = dynamic_cast<TagLib::MPEG::File *>(f.file()))
{
@ -302,9 +301,15 @@ 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()))
{
@ -321,7 +326,7 @@ bool write(MPD::MutableSong &s)
else
writeCommonTags(s, f.tag());
if (!f.save())
if (!saved && !f.save())
return false;
// TODO: move this somewhere else

@ -37,7 +37,7 @@ void drawHeader()
using Global::myScreen;
using Global::wHeader;
using Global::VolumeState;
if (!Config.header_visibility)
return;
switch (Config.design)
@ -62,9 +62,9 @@ void drawHeader()
mvwhline(wHeader->raw(), 4, 0, 0, COLS);
*wHeader << NC::FormattedColor::End<>(Config.alternative_ui_separator_color)
<< NC::XY((COLS-wideLength(title))/2, 3)
// << NC::Format::Bold
<< title;
// << NC::Format::NoBold;
<< NC::Format::Bold
<< title
<< NC::Format::NoBold;
break;
}
wHeader->refresh();

@ -34,23 +34,10 @@ 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,6 +26,7 @@
#include <boost/type_traits/is_unsigned.hpp>
#include "config.h"
#include "gcc.h"
struct ConversionError
{
@ -43,21 +44,21 @@ struct OutOfBounds : std::exception
const std::string &errorMessage() { return m_error_message; }
template <typename Type>
[[noreturn]] static void raise(const Type &value, const Type &lbound, const Type &ubound)
GNUC_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>
[[noreturn]] static void raiseLower(const Type &value, const Type &lbound)
GNUC_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>
[[noreturn]] static void raiseUpper(const Type &value, const Type &ubound)
GNUC_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());

@ -20,29 +20,16 @@
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include <cstdlib>
#include "utility/html.h"
std::string unescapeHtmlUtf8(const std::string &data)
{
int base;
size_t offset;
std::string result;
for (size_t i = 0, j; i < data.length(); ++i)
{
if (data[i] == '&' && data[i+1] == '#' && (j = data.find(';', i)) != std::string::npos)
{
if (data[i+2] == 'x')
{
offset = 3;
base = 16;
}
else
{
offset = 2;
base = 10;
}
int n = strtol(&data.c_str()[i+offset], nullptr, base);
int n = atoi(&data.c_str()[i+2]);
if (n >= 0x800)
{
result += (0xe0 | ((n >> 12) & 0x0f));

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

@ -168,8 +168,6 @@ 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':

Loading…
Cancel
Save