Modernize lyrics screen
This commit is contained in:
1
NEWS
1
NEWS
@@ -7,6 +7,7 @@ ncmpcpp-0.8 (????-??-??)
|
|||||||
* Added support for fetching lyrics from genius.com.
|
* Added support for fetching lyrics from genius.com.
|
||||||
* Added support for fetching lyrics from tekstowo.pl.
|
* Added support for fetching lyrics from tekstowo.pl.
|
||||||
* The list of lyrics fetchers can now be set via configuration file.
|
* The list of lyrics fetchers can now be set via configuration file.
|
||||||
|
* Lyrics can now be fetched for songs with no tags.
|
||||||
|
|
||||||
ncmpcpp-0.7.7 (2016-10-31)
|
ncmpcpp-0.7.7 (2016-10-31)
|
||||||
* Fixed compilation on 32bit platforms.
|
* Fixed compilation on 32bit platforms.
|
||||||
|
|||||||
21
configure.ac
21
configure.ac
@@ -145,17 +145,6 @@ PKG_CHECK_MODULES([ICU], [icu-uc], [
|
|||||||
)
|
)
|
||||||
], [[]])
|
], [[]])
|
||||||
|
|
||||||
dnl =============================
|
|
||||||
dnl = checking for boost.thread =
|
|
||||||
dnl =============================
|
|
||||||
AC_DEFINE([BOOST_THREAD_VERSION], [3], [require boost.thread v3])
|
|
||||||
AC_CHECK_HEADERS([boost/thread.hpp], ,
|
|
||||||
AC_MSG_ERROR([boost/thread.hpp is missing or your boost version is too old (boost.thread v3 is required)])
|
|
||||||
)
|
|
||||||
AC_CHECK_LIB(boost_thread$BOOST_LIB_SUFFIX, main, LIBS="$LIBS -lboost_thread$BOOST_LIB_SUFFIX",
|
|
||||||
AC_MSG_ERROR([no boost.thread library found])
|
|
||||||
)
|
|
||||||
|
|
||||||
dnl ================================
|
dnl ================================
|
||||||
dnl = checking for various headers =
|
dnl = checking for various headers =
|
||||||
dnl ================================
|
dnl ================================
|
||||||
@@ -189,16 +178,6 @@ if test "$ax_cv_lib_readline_history" = "no"; then
|
|||||||
AC_MSG_WARN([readline library has no history functionality])
|
AC_MSG_WARN([readline library has no history functionality])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dnl ========================
|
|
||||||
dnl = checking for pthread =
|
|
||||||
dnl ========================
|
|
||||||
AC_CHECK_HEADERS([pthread.h],
|
|
||||||
AC_CHECK_LIB(pthread, pthread_create, LIBS="$LIBS -lpthread",
|
|
||||||
AC_MSG_ERROR([pthread.h found but there is no pthread library to make use of])
|
|
||||||
),
|
|
||||||
AC_MSG_ERROR([no pthread.h header header file found])
|
|
||||||
)
|
|
||||||
|
|
||||||
dnl ========================
|
dnl ========================
|
||||||
dnl = checking for ncurses =
|
dnl = checking for ncurses =
|
||||||
dnl ========================
|
dnl ========================
|
||||||
|
|||||||
@@ -1148,7 +1148,7 @@ bool ToggleLyricsFetcher::canBeRun()
|
|||||||
void ToggleLyricsFetcher::run()
|
void ToggleLyricsFetcher::run()
|
||||||
{
|
{
|
||||||
# ifdef HAVE_CURL_CURL_H
|
# ifdef HAVE_CURL_CURL_H
|
||||||
myLyrics->ToggleFetcher();
|
myLyrics->toggleFetcher();
|
||||||
# endif // HAVE_CURL_CURL_H
|
# endif // HAVE_CURL_CURL_H
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1601,7 +1601,7 @@ bool EditLyrics::canBeRun()
|
|||||||
|
|
||||||
void EditLyrics::run()
|
void EditLyrics::run()
|
||||||
{
|
{
|
||||||
myLyrics->Edit();
|
myLyrics->edit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JumpToBrowser::canBeRun()
|
bool JumpToBrowser::canBeRun()
|
||||||
@@ -2309,7 +2309,7 @@ bool RefetchLyrics::canBeRun()
|
|||||||
void RefetchLyrics::run()
|
void RefetchLyrics::run()
|
||||||
{
|
{
|
||||||
# ifdef HAVE_CURL_CURL_H
|
# ifdef HAVE_CURL_CURL_H
|
||||||
myLyrics->Refetch();
|
myLyrics->refetchCurrent();
|
||||||
# endif // HAVE_CURL_CURL_H
|
# endif // HAVE_CURL_CURL_H
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2444,8 +2444,24 @@ void ShowArtistInfo::run()
|
|||||||
# endif // HAVE_CURL_CURL_H
|
# endif // HAVE_CURL_CURL_H
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ShowLyrics::canBeRun()
|
||||||
|
{
|
||||||
|
if (myScreen == myLyrics)
|
||||||
|
{
|
||||||
|
m_song = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_song = currentSong(myScreen);
|
||||||
|
return m_song != nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ShowLyrics::run()
|
void ShowLyrics::run()
|
||||||
{
|
{
|
||||||
|
if (m_song != nullptr)
|
||||||
|
myLyrics->fetch(*m_song);
|
||||||
myLyrics->switchTo();
|
myLyrics->switchTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1240,7 +1240,10 @@ struct ShowLyrics: BaseAction
|
|||||||
ShowLyrics(): BaseAction(Type::ShowLyrics, "show_lyrics") { }
|
ShowLyrics(): BaseAction(Type::ShowLyrics, "show_lyrics") { }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
virtual bool canBeRun() override;
|
||||||
virtual void run() override;
|
virtual void run() override;
|
||||||
|
|
||||||
|
const MPD::Song *m_song;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Quit: BaseAction
|
struct Quit: BaseAction
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#ifdef HAVE_CURL_CURL_H
|
#ifdef HAVE_CURL_CURL_H
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -65,4 +64,3 @@ std::string Curl::escape(const std::string &s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_CURL_CURL_H
|
#endif // HAVE_CURL_CURL_H
|
||||||
|
|
||||||
|
|||||||
595
src/lyrics.cpp
595
src/lyrics.cpp
@@ -18,6 +18,9 @@
|
|||||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
#include <boost/range/algorithm_ext/erase.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -42,22 +45,152 @@
|
|||||||
using Global::MainHeight;
|
using Global::MainHeight;
|
||||||
using Global::MainStartY;
|
using Global::MainStartY;
|
||||||
|
|
||||||
#ifdef HAVE_CURL_CURL_H
|
|
||||||
LyricsFetcher *Lyrics::itsFetcher = nullptr;
|
|
||||||
std::queue<MPD::Song *> Lyrics::itsToDownload;
|
|
||||||
pthread_mutex_t Lyrics::itsDIBLock = PTHREAD_MUTEX_INITIALIZER;
|
|
||||||
size_t Lyrics::itsWorkersNumber = 0;
|
|
||||||
#endif // HAVE_CURL_CURL_H
|
|
||||||
|
|
||||||
Lyrics *myLyrics;
|
Lyrics *myLyrics;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string removeExtension(std::string filename)
|
||||||
|
{
|
||||||
|
size_t dot = filename.rfind('.');
|
||||||
|
if (dot != std::string::npos)
|
||||||
|
filename.resize(dot);
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string lyricsFilename(const MPD::Song &s)
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
if (Config.store_lyrics_in_song_dir && !s.isStream())
|
||||||
|
{
|
||||||
|
if (s.isFromDatabase())
|
||||||
|
filename = Config.mpd_music_dir + "/";
|
||||||
|
filename += removeExtension(s.getURI());
|
||||||
|
removeExtension(filename);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string artist = s.getArtist();
|
||||||
|
std::string title = s.getTitle();
|
||||||
|
if (artist.empty() || title.empty())
|
||||||
|
filename = removeExtension(s.getName());
|
||||||
|
else
|
||||||
|
filename = artist + " - " + title;
|
||||||
|
removeInvalidCharsFromFilename(filename, Config.generate_win32_compatible_filenames);
|
||||||
|
filename = Config.lyrics_directory + "/" + filename;
|
||||||
|
}
|
||||||
|
filename += ".txt";
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadLyrics(NC::Scrollpad &w, const std::string &filename)
|
||||||
|
{
|
||||||
|
std::ifstream input(filename);
|
||||||
|
if (input.is_open())
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
bool first_line = true;
|
||||||
|
while (std::getline(input, line))
|
||||||
|
{
|
||||||
|
// Remove carriage returns as they mess up the display.
|
||||||
|
boost::remove_erase(line, '\r');
|
||||||
|
if (!first_line)
|
||||||
|
w << '\n';
|
||||||
|
w << Charset::utf8ToLocale(line);
|
||||||
|
first_line = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveLyrics(const std::string &filename, const std::string &lyrics)
|
||||||
|
{
|
||||||
|
std::ofstream output(filename);
|
||||||
|
if (output.is_open())
|
||||||
|
{
|
||||||
|
output << lyrics;
|
||||||
|
output.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string> downloadLyrics(
|
||||||
|
const MPD::Song &s,
|
||||||
|
std::shared_ptr<Shared<NC::Buffer>> shared_buffer,
|
||||||
|
LyricsFetcher *current_fetcher)
|
||||||
|
{
|
||||||
|
std::string s_artist = Curl::escape(s.getArtist());
|
||||||
|
std::string s_title = Curl::escape(s.getTitle());
|
||||||
|
// If artist or title is empty, use filename. This should give reasonable
|
||||||
|
// results for google search based lyrics fetchers.
|
||||||
|
if (s_artist.empty() || s_title.empty())
|
||||||
|
{
|
||||||
|
s_artist.clear();
|
||||||
|
s_title = s.getName();
|
||||||
|
// Get rid of underscores to improve search results.
|
||||||
|
std::replace_if(s_title.begin(), s_title.end(), boost::is_any_of("-_"), ' ');
|
||||||
|
size_t dot = s_title.rfind('.');
|
||||||
|
if (dot != std::string::npos)
|
||||||
|
s_title.resize(dot);
|
||||||
|
s_title = Curl::escape(s_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fetch_lyrics = [&](auto &fetcher_) {
|
||||||
|
{
|
||||||
|
if (shared_buffer)
|
||||||
|
{
|
||||||
|
auto buf = shared_buffer->acquire();
|
||||||
|
*buf << "Fetching lyrics from "
|
||||||
|
<< NC::Format::Bold
|
||||||
|
<< fetcher_->name()
|
||||||
|
<< NC::Format::NoBold << "... ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto result_ = fetcher_->fetch(s_artist, s_title);
|
||||||
|
if (result_.first == false)
|
||||||
|
{
|
||||||
|
if (shared_buffer)
|
||||||
|
{
|
||||||
|
auto buf = shared_buffer->acquire();
|
||||||
|
*buf << NC::Color::Red
|
||||||
|
<< result_.second
|
||||||
|
<< NC::Color::End
|
||||||
|
<< '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result_;
|
||||||
|
};
|
||||||
|
|
||||||
|
LyricsFetcher::Result fetcher_result;
|
||||||
|
if (current_fetcher == nullptr)
|
||||||
|
{
|
||||||
|
for (auto &fetcher : Config.lyrics_fetchers)
|
||||||
|
{
|
||||||
|
fetcher_result = fetch_lyrics(fetcher);
|
||||||
|
if (fetcher_result.first)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fetcher_result = fetch_lyrics(current_fetcher);
|
||||||
|
|
||||||
|
boost::optional<std::string> result;
|
||||||
|
if (fetcher_result.first)
|
||||||
|
result = std::move(fetcher_result.second);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Lyrics::Lyrics()
|
Lyrics::Lyrics()
|
||||||
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
: Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
|
||||||
, Reload(0),
|
, m_refresh_window(false)
|
||||||
#ifdef HAVE_CURL_CURL_H
|
, m_scroll_begin(0)
|
||||||
isReadyToTake(0), isDownloadInProgress(0),
|
, m_fetcher(nullptr)
|
||||||
#endif // HAVE_CURL_CURL_H
|
, m_shared_queue(std::make_pair(false, std::queue<MPD::Song>{}))
|
||||||
itsScrollBegin(0)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void Lyrics::resize()
|
void Lyrics::resize()
|
||||||
@@ -71,23 +204,42 @@ void Lyrics::resize()
|
|||||||
|
|
||||||
void Lyrics::update()
|
void Lyrics::update()
|
||||||
{
|
{
|
||||||
# ifdef HAVE_CURL_CURL_H
|
if (m_worker.valid())
|
||||||
if (isReadyToTake)
|
|
||||||
Take();
|
|
||||||
|
|
||||||
if (isDownloadInProgress)
|
|
||||||
{
|
{
|
||||||
|
auto buffer = m_shared_buffer->acquire();
|
||||||
|
if (!buffer->empty())
|
||||||
|
{
|
||||||
|
w << *buffer;
|
||||||
|
buffer->clear();
|
||||||
|
m_refresh_window = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_worker.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
|
||||||
|
{
|
||||||
|
auto lyrics = m_worker.get();
|
||||||
|
if (lyrics)
|
||||||
|
{
|
||||||
|
w.clear();
|
||||||
|
w << Charset::utf8ToLocale(*lyrics);
|
||||||
|
std::string filename = lyricsFilename(m_song);
|
||||||
|
if (!saveLyrics(filename, *lyrics))
|
||||||
|
Statusbar::printf("Couldn't save lyrics as \"%1%\": %2%",
|
||||||
|
filename, strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
w << "\nLyrics were not found.\n";
|
||||||
|
m_refresh_window = true;
|
||||||
|
// Reset worker so it's no longer valid.
|
||||||
|
m_worker = std::future<boost::optional<std::string>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_refresh_window)
|
||||||
|
{
|
||||||
|
m_refresh_window = false;
|
||||||
w.flush();
|
w.flush();
|
||||||
w.refresh();
|
w.refresh();
|
||||||
}
|
}
|
||||||
# endif // HAVE_CURL_CURL_H
|
|
||||||
if (Reload)
|
|
||||||
{
|
|
||||||
drawHeader();
|
|
||||||
itsScrollBegin = 0;
|
|
||||||
Load();
|
|
||||||
Reload = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lyrics::switchTo()
|
void Lyrics::switchTo()
|
||||||
@@ -95,31 +247,9 @@ void Lyrics::switchTo()
|
|||||||
using Global::myScreen;
|
using Global::myScreen;
|
||||||
if (myScreen != this)
|
if (myScreen != this)
|
||||||
{
|
{
|
||||||
# ifdef HAVE_CURL_CURL_H
|
SwitchTo::execute(this);
|
||||||
// take lyrics if they were downloaded
|
m_scroll_begin = 0;
|
||||||
if (isReadyToTake)
|
drawHeader();
|
||||||
Take();
|
|
||||||
|
|
||||||
if (isDownloadInProgress || itsWorkersNumber > 0)
|
|
||||||
{
|
|
||||||
Statusbar::print("Lyrics are being downloaded...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
# endif // HAVE_CURL_CURL_H
|
|
||||||
|
|
||||||
auto s = currentSong(myScreen);
|
|
||||||
if (!s)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (SetSong(*s))
|
|
||||||
{
|
|
||||||
SwitchTo::execute(this);
|
|
||||||
itsScrollBegin = 0;
|
|
||||||
Load();
|
|
||||||
drawHeader();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Statusbar::print("Song must have both artist and title tag set");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
switchToPreviousScreen();
|
switchToPreviousScreen();
|
||||||
@@ -128,328 +258,143 @@ void Lyrics::switchTo()
|
|||||||
std::wstring Lyrics::title()
|
std::wstring Lyrics::title()
|
||||||
{
|
{
|
||||||
std::wstring result = L"Lyrics: ";
|
std::wstring result = L"Lyrics: ";
|
||||||
|
|
||||||
result += Scroller(
|
result += Scroller(
|
||||||
Format::stringify<wchar_t>(Format::parse(L"%a - %t"), &itsSong),
|
Format::stringify<wchar_t>(Format::parse(L"{%a - %t}|{%f}"), &m_song),
|
||||||
itsScrollBegin,
|
m_scroll_begin,
|
||||||
COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length())
|
COLS - result.length() - (Config.design == Design::Alternative
|
||||||
);
|
? 2
|
||||||
|
: Global::VolumeState.length()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_CURL_CURL_H
|
void Lyrics::fetch(const MPD::Song &s)
|
||||||
void Lyrics::DownloadInBackground(const MPD::Song &s)
|
|
||||||
{
|
{
|
||||||
if (s.empty() || s.getArtist().empty() || s.getTitle().empty())
|
if (!m_worker.valid() || s != m_song)
|
||||||
return;
|
|
||||||
|
|
||||||
std::string filename = GenerateFilename(s);
|
|
||||||
std::ifstream f(filename.c_str());
|
|
||||||
if (f.is_open())
|
|
||||||
{
|
{
|
||||||
f.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Statusbar::printf("Fetching lyrics for \"%1%\"...",
|
|
||||||
Format::stringify<char>(Config.song_status_format, &s)
|
|
||||||
);
|
|
||||||
|
|
||||||
MPD::Song *s_copy = new MPD::Song(s);
|
|
||||||
pthread_mutex_lock(&itsDIBLock);
|
|
||||||
if (itsWorkersNumber == itsMaxWorkersNumber)
|
|
||||||
itsToDownload.push(s_copy);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++itsWorkersNumber;
|
|
||||||
pthread_t t;
|
|
||||||
pthread_attr_t attr;
|
|
||||||
pthread_attr_init(&attr);
|
|
||||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
||||||
pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&itsDIBLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
|
|
||||||
{
|
|
||||||
MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
|
|
||||||
DownloadInBackgroundImplHelper(*s);
|
|
||||||
delete s;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock(&itsDIBLock);
|
|
||||||
if (itsToDownload.empty())
|
|
||||||
{
|
|
||||||
pthread_mutex_unlock(&itsDIBLock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s = itsToDownload.front();
|
|
||||||
itsToDownload.pop();
|
|
||||||
pthread_mutex_unlock(&itsDIBLock);
|
|
||||||
}
|
|
||||||
DownloadInBackgroundImplHelper(*s);
|
|
||||||
delete s;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_lock(&itsDIBLock);
|
|
||||||
--itsWorkersNumber;
|
|
||||||
pthread_mutex_unlock(&itsDIBLock);
|
|
||||||
|
|
||||||
pthread_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
|
|
||||||
{
|
|
||||||
std::string artist = Curl::escape(s.getArtist());
|
|
||||||
std::string title = Curl::escape(s.getTitle());
|
|
||||||
|
|
||||||
LyricsFetcher::Result result;
|
|
||||||
|
|
||||||
if (itsFetcher == nullptr)
|
|
||||||
{
|
|
||||||
for (auto &fetcher : Config.lyrics_fetchers)
|
|
||||||
{
|
|
||||||
result = fetcher->fetch(artist, title);
|
|
||||||
if (result.first)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
itsFetcher->fetch(artist, title);
|
|
||||||
|
|
||||||
if (result.first)
|
|
||||||
Save(GenerateFilename(s), result.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *Lyrics::Download()
|
|
||||||
{
|
|
||||||
std::string artist = Curl::escape(itsSong.getArtist());
|
|
||||||
std::string title_ = Curl::escape(itsSong.getTitle());
|
|
||||||
|
|
||||||
auto fetch_lyrics = [&](auto &fetcher_) {
|
|
||||||
w << "Fetching lyrics from "
|
|
||||||
<< NC::Format::Bold
|
|
||||||
<< fetcher_->name()
|
|
||||||
<< NC::Format::NoBold << "... ";
|
|
||||||
auto result_ = fetcher_->fetch(artist, title_);
|
|
||||||
if (result_.first == false)
|
|
||||||
{
|
|
||||||
w << NC::Color::Red
|
|
||||||
<< result_.second
|
|
||||||
<< NC::Color::End
|
|
||||||
<< '\n';
|
|
||||||
}
|
|
||||||
return result_;
|
|
||||||
};
|
|
||||||
|
|
||||||
LyricsFetcher::Result result;
|
|
||||||
|
|
||||||
if (itsFetcher == nullptr)
|
|
||||||
{
|
|
||||||
for (auto &fetcher : Config.lyrics_fetchers)
|
|
||||||
{
|
|
||||||
result = fetch_lyrics(fetcher);
|
|
||||||
if (result.first)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result = fetch_lyrics(itsFetcher);
|
|
||||||
|
|
||||||
if (result.first)
|
|
||||||
{
|
|
||||||
Save(itsFilename, result.second);
|
|
||||||
w.clear();
|
w.clear();
|
||||||
w << Charset::utf8ToLocale(result.second);
|
m_song = s;
|
||||||
}
|
if (loadLyrics(w, lyricsFilename(m_song)))
|
||||||
else
|
m_refresh_window = true;
|
||||||
w << '\n' << "Lyrics weren't found.";
|
|
||||||
|
|
||||||
isReadyToTake = 1;
|
|
||||||
pthread_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *Lyrics::DownloadWrapper(void *this_ptr)
|
|
||||||
{
|
|
||||||
return static_cast<Lyrics *>(this_ptr)->Download();
|
|
||||||
}
|
|
||||||
#endif // HAVE_CURL_CURL_H
|
|
||||||
|
|
||||||
std::string Lyrics::GenerateFilename(const MPD::Song &s)
|
|
||||||
{
|
|
||||||
std::string filename;
|
|
||||||
if (Config.store_lyrics_in_song_dir)
|
|
||||||
{
|
|
||||||
if (s.isFromDatabase())
|
|
||||||
{
|
|
||||||
filename = Config.mpd_music_dir;
|
|
||||||
filename += "/";
|
|
||||||
filename += s.getURI();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
filename = s.getURI();
|
|
||||||
// replace song's extension with .txt
|
|
||||||
size_t dot = filename.rfind('.');
|
|
||||||
assert(dot != std::string::npos);
|
|
||||||
filename.resize(dot);
|
|
||||||
filename += ".txt";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string file = s.getArtist();
|
|
||||||
file += " - ";
|
|
||||||
file += s.getTitle();
|
|
||||||
file += ".txt";
|
|
||||||
removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
|
|
||||||
filename = Config.lyrics_directory;
|
|
||||||
filename += "/";
|
|
||||||
filename += file;
|
|
||||||
}
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lyrics::Load()
|
|
||||||
{
|
|
||||||
# ifdef HAVE_CURL_CURL_H
|
|
||||||
if (isDownloadInProgress)
|
|
||||||
return;
|
|
||||||
# endif // HAVE_CURL_CURL_H
|
|
||||||
|
|
||||||
assert(!itsSong.getArtist().empty());
|
|
||||||
assert(!itsSong.getTitle().empty());
|
|
||||||
|
|
||||||
itsFilename = GenerateFilename(itsSong);
|
|
||||||
|
|
||||||
w.clear();
|
|
||||||
w.reset();
|
|
||||||
|
|
||||||
std::ifstream input(itsFilename.c_str());
|
|
||||||
if (input.is_open())
|
|
||||||
{
|
|
||||||
bool first = 1;
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(input, line))
|
|
||||||
{
|
{
|
||||||
// Remove carriage returns as they mess up the display.
|
m_shared_buffer = std::make_shared<Shared<NC::Buffer>>();
|
||||||
line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
|
m_worker = std::async(
|
||||||
if (!first)
|
std::launch::async,
|
||||||
w << '\n';
|
std::bind(downloadLyrics, m_song, m_shared_buffer, m_fetcher));
|
||||||
w << Charset::utf8ToLocale(line);
|
|
||||||
first = 0;
|
|
||||||
}
|
}
|
||||||
w.flush();
|
|
||||||
if (Reload)
|
|
||||||
w.refresh();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
# ifdef HAVE_CURL_CURL_H
|
|
||||||
pthread_create(&itsDownloader, 0, DownloadWrapper, this);
|
|
||||||
isDownloadInProgress = 1;
|
|
||||||
# else
|
|
||||||
w << "Local lyrics not found. As ncmpcpp has been compiled without curl support, you can put appropriate lyrics into " << Config.lyrics_directory << " directory (file syntax is \"$ARTIST - $TITLE.txt\") or recompile ncmpcpp with curl support.";
|
|
||||||
w.flush();
|
|
||||||
# endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lyrics::Edit()
|
void Lyrics::refetchCurrent()
|
||||||
|
{
|
||||||
|
std::string filename = lyricsFilename(m_song);
|
||||||
|
if (std::remove(filename.c_str()) == -1 && errno != ENOENT)
|
||||||
|
{
|
||||||
|
const char msg[] = "Couldn't remove \"%1%\": %2%";
|
||||||
|
Statusbar::printf(msg, wideShorten(filename, COLS - const_strlen(msg) - 25),
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get rid of current worker so fetch can restart the process.
|
||||||
|
m_worker = std::future<boost::optional<std::string>>();
|
||||||
|
fetch(m_song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lyrics::edit()
|
||||||
{
|
{
|
||||||
assert(Global::myScreen == this);
|
|
||||||
|
|
||||||
if (Config.external_editor.empty())
|
if (Config.external_editor.empty())
|
||||||
{
|
{
|
||||||
Statusbar::print("Proper external_editor variable has to be set in configuration file");
|
Statusbar::print("external_editor variable has to be set in configuration file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Statusbar::print("Opening lyrics in external editor...");
|
Statusbar::print("Opening lyrics in external editor...");
|
||||||
|
|
||||||
GNUC_UNUSED int res;
|
GNUC_UNUSED int res;
|
||||||
|
std::string command;
|
||||||
|
std::string filename = lyricsFilename(m_song);
|
||||||
if (Config.use_console_editor)
|
if (Config.use_console_editor)
|
||||||
{
|
{
|
||||||
res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
|
command = "/bin/sh -c \"" + Config.external_editor + " \\\"" + filename + "\\\"\"";
|
||||||
Load();
|
res = system(command.c_str());
|
||||||
// below is needed as screen gets cleared, but apparently
|
fetch(m_song);
|
||||||
// ncurses doesn't know about it, so we need to reload main screen
|
// Reset ncurses state to refresh the screen.
|
||||||
endwin();
|
endwin();
|
||||||
initscr();
|
initscr();
|
||||||
curs_set(0);
|
curs_set(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lyrics::SetSong(const MPD::Song &s)
|
|
||||||
{
|
|
||||||
if (!s.getArtist().empty() && !s.getTitle().empty())
|
|
||||||
{
|
{
|
||||||
itsSong = s;
|
command = "nohup " + Config.external_editor
|
||||||
return true;
|
+ " \"" + filename + "\" > /dev/null 2>&1 &";
|
||||||
}
|
res = system(command.c_str());
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAVE_CURL_CURL_H
|
|
||||||
void Lyrics::Save(const std::string &filename, const std::string &lyrics)
|
|
||||||
{
|
|
||||||
std::ofstream output(filename.c_str());
|
|
||||||
if (output.is_open())
|
|
||||||
{
|
|
||||||
output << lyrics;
|
|
||||||
output.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lyrics::Refetch()
|
void Lyrics::toggleFetcher()
|
||||||
{
|
{
|
||||||
if (remove(itsFilename.c_str()) && errno != ENOENT)
|
if (m_fetcher != nullptr)
|
||||||
{
|
|
||||||
const char msg[] = "Couldn't remove \"%1%\": %2%";
|
|
||||||
Statusbar::printf(msg, wideShorten(itsFilename, COLS-const_strlen(msg)-25), strerror(errno));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Lyrics::ToggleFetcher()
|
|
||||||
{
|
|
||||||
if (itsFetcher != nullptr)
|
|
||||||
{
|
{
|
||||||
auto fetcher = std::find_if(Config.lyrics_fetchers.begin(),
|
auto fetcher = std::find_if(Config.lyrics_fetchers.begin(),
|
||||||
Config.lyrics_fetchers.end(),
|
Config.lyrics_fetchers.end(),
|
||||||
[](auto &f) { return f.get() == itsFetcher; });
|
[this](auto &f) { return f.get() == m_fetcher; });
|
||||||
assert(fetcher != Config.lyrics_fetchers.end());
|
assert(fetcher != Config.lyrics_fetchers.end());
|
||||||
++fetcher;
|
++fetcher;
|
||||||
if (fetcher != Config.lyrics_fetchers.end())
|
if (fetcher != Config.lyrics_fetchers.end())
|
||||||
itsFetcher = fetcher->get();
|
m_fetcher = fetcher->get();
|
||||||
else
|
else
|
||||||
itsFetcher = nullptr;
|
m_fetcher = nullptr;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
assert(!Config.lyrics_fetchers.empty());
|
assert(!Config.lyrics_fetchers.empty());
|
||||||
itsFetcher = Config.lyrics_fetchers[0].get();
|
m_fetcher = Config.lyrics_fetchers[0].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itsFetcher != nullptr)
|
if (m_fetcher != nullptr)
|
||||||
Statusbar::printf("Using lyrics fetcher: %s", itsFetcher->name());
|
Statusbar::printf("Using lyrics fetcher: %s", m_fetcher->name());
|
||||||
else
|
else
|
||||||
Statusbar::print("Using all lyrics fetchers");
|
Statusbar::print("Using all lyrics fetchers");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lyrics::Take()
|
void Lyrics::fetchInBackground(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
assert(isReadyToTake);
|
auto consumer = [this] {
|
||||||
pthread_join(itsDownloader, 0);
|
std::string lyrics_file;
|
||||||
w.flush();
|
while (true)
|
||||||
w.refresh();
|
{
|
||||||
isDownloadInProgress = 0;
|
MPD::Song qs;
|
||||||
isReadyToTake = 0;
|
{
|
||||||
}
|
auto queue = m_shared_queue.acquire();
|
||||||
#endif // HAVE_CURL_CURL_H
|
assert(queue->first);
|
||||||
|
if (queue->second.empty())
|
||||||
|
{
|
||||||
|
queue->first = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lyrics_file = lyricsFilename(queue->second.front());
|
||||||
|
if (!boost::filesystem::exists(lyrics_file))
|
||||||
|
qs = queue->second.front();
|
||||||
|
queue->second.pop();
|
||||||
|
}
|
||||||
|
if (!qs.empty())
|
||||||
|
{
|
||||||
|
auto lyrics = downloadLyrics(qs, nullptr, m_fetcher);
|
||||||
|
if (lyrics)
|
||||||
|
saveLyrics(lyrics_file, *lyrics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto queue = m_shared_queue.acquire();
|
||||||
|
queue->second.push(s);
|
||||||
|
// Start the consumer if it's not running.
|
||||||
|
if (!queue->first)
|
||||||
|
{
|
||||||
|
std::thread t(consumer);
|
||||||
|
t.detach();
|
||||||
|
queue->first = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
101
src/lyrics.h
101
src/lyrics.h
@@ -21,7 +21,9 @@
|
|||||||
#ifndef NCMPCPP_LYRICS_H
|
#ifndef NCMPCPP_LYRICS_H
|
||||||
#define NCMPCPP_LYRICS_H
|
#define NCMPCPP_LYRICS_H
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <boost/optional.hpp>
|
||||||
|
#include <future>
|
||||||
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
@@ -29,6 +31,40 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
|
|
||||||
|
template <typename ResourceT>
|
||||||
|
struct Shared
|
||||||
|
{
|
||||||
|
struct Resource
|
||||||
|
{
|
||||||
|
Resource(std::mutex &mutex, ResourceT &resource)
|
||||||
|
: m_lock(std::unique_lock<std::mutex>(mutex)), m_resource(resource)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
ResourceT &operator*() { return m_resource; }
|
||||||
|
const ResourceT &operator*() const { return m_resource; }
|
||||||
|
|
||||||
|
ResourceT *operator->() { return &m_resource; }
|
||||||
|
const ResourceT *operator->() const { return &m_resource; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_lock<std::mutex> m_lock;
|
||||||
|
ResourceT &m_resource;
|
||||||
|
};
|
||||||
|
|
||||||
|
Shared(){ }
|
||||||
|
|
||||||
|
template <typename ValueT>
|
||||||
|
Shared(ValueT &&value)
|
||||||
|
: m_resource(std::forward<ValueT>(value))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Resource acquire() { return Resource(m_mutex, m_resource); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex m_mutex;
|
||||||
|
ResourceT m_resource;
|
||||||
|
};
|
||||||
|
|
||||||
struct Lyrics: Screen<NC::Scrollpad>, Tabbable
|
struct Lyrics: Screen<NC::Scrollpad>, Tabbable
|
||||||
{
|
{
|
||||||
Lyrics();
|
Lyrics();
|
||||||
@@ -44,53 +80,26 @@ struct Lyrics: Screen<NC::Scrollpad>, Tabbable
|
|||||||
|
|
||||||
virtual bool isLockable() override { return false; }
|
virtual bool isLockable() override { return false; }
|
||||||
virtual bool isMergable() override { return true; }
|
virtual bool isMergable() override { return true; }
|
||||||
|
|
||||||
// private members
|
|
||||||
bool SetSong(const MPD::Song &s);
|
|
||||||
void Edit();
|
|
||||||
|
|
||||||
# ifdef HAVE_CURL_CURL_H
|
|
||||||
void Refetch();
|
|
||||||
|
|
||||||
static void ToggleFetcher();
|
// other members
|
||||||
static void DownloadInBackground(const MPD::Song &s);
|
void fetch(const MPD::Song &s);
|
||||||
# endif // HAVE_CURL_CURL_H
|
void refetchCurrent();
|
||||||
|
void edit();
|
||||||
bool Reload;
|
void toggleFetcher();
|
||||||
|
|
||||||
|
void fetchInBackground(const MPD::Song &s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Load();
|
bool m_refresh_window;
|
||||||
|
size_t m_scroll_begin;
|
||||||
# ifdef HAVE_CURL_CURL_H
|
|
||||||
static void *DownloadInBackgroundImpl(void *song_ptr);
|
std::shared_ptr<Shared<NC::Buffer>> m_shared_buffer;
|
||||||
static void DownloadInBackgroundImplHelper(const MPD::Song &s);
|
|
||||||
// lock for allowing exclusive access to itsToDownload and itsWorkersNumber
|
MPD::Song m_song;
|
||||||
static pthread_mutex_t itsDIBLock;
|
LyricsFetcher *m_fetcher;
|
||||||
// storage for songs for which lyrics are scheduled to be downloaded
|
std::future<boost::optional<std::string>> m_worker;
|
||||||
static std::queue<MPD::Song *> itsToDownload;
|
|
||||||
// current worker threads (ie. downloading lyrics)
|
Shared<std::pair<bool, std::queue<MPD::Song>>> m_shared_queue;
|
||||||
static size_t itsWorkersNumber;
|
|
||||||
// maximum number of worker threads. if it's reached, next lyrics requests
|
|
||||||
// are put into itsToDownload queue.
|
|
||||||
static const size_t itsMaxWorkersNumber = 1;
|
|
||||||
|
|
||||||
void *Download();
|
|
||||||
static void *DownloadWrapper(void *);
|
|
||||||
static void Save(const std::string &filename, const std::string &lyrics);
|
|
||||||
|
|
||||||
void Take();
|
|
||||||
bool isReadyToTake;
|
|
||||||
bool isDownloadInProgress;
|
|
||||||
pthread_t itsDownloader;
|
|
||||||
|
|
||||||
static LyricsFetcher *itsFetcher;
|
|
||||||
# endif // HAVE_CURL_CURL_H
|
|
||||||
|
|
||||||
size_t itsScrollBegin;
|
|
||||||
MPD::Song itsSong;
|
|
||||||
std::string itsFilename;
|
|
||||||
|
|
||||||
static std::string GenerateFilename(const MPD::Song &s);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Lyrics *myLyrics;
|
extern Lyrics *myLyrics;
|
||||||
|
|||||||
@@ -46,18 +46,30 @@ struct Scrollpad: public Window
|
|||||||
void flush();
|
void flush();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
bool setProperties(Color begin, const std::string &s, Color end, size_t flags, size_t id = -2);
|
bool setProperties(Color begin, const std::string &s, Color end,
|
||||||
bool setProperties(Format begin, const std::string &s, Format end, size_t flags, size_t id = -2);
|
size_t flags, size_t id = -2);
|
||||||
|
bool setProperties(Format begin, const std::string &s, Format end,
|
||||||
|
size_t flags, size_t id = -2);
|
||||||
void removeProperties(size_t id = -2);
|
void removeProperties(size_t id = -2);
|
||||||
|
|
||||||
|
Scrollpad &operator<<(int n) { return write(n); }
|
||||||
|
Scrollpad &operator<<(long int n) { return write(n); }
|
||||||
|
Scrollpad &operator<<(unsigned int n) { return write(n); }
|
||||||
|
Scrollpad &operator<<(unsigned long int n) { return write(n); }
|
||||||
|
Scrollpad &operator<<(char c) { return write(c); }
|
||||||
|
Scrollpad &operator<<(const char *s) { return write(s); }
|
||||||
|
Scrollpad &operator<<(const std::string &s) { return write(s); }
|
||||||
|
Scrollpad &operator<<(Color color) { return write(color); }
|
||||||
|
Scrollpad &operator<<(Format format) { return write(format); }
|
||||||
|
|
||||||
|
private:
|
||||||
template <typename ItemT>
|
template <typename ItemT>
|
||||||
Scrollpad &operator<<(const ItemT &item)
|
Scrollpad &write(ItemT &&item)
|
||||||
{
|
{
|
||||||
m_buffer << item;
|
m_buffer << std::forward<ItemT>(item);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
Buffer m_buffer;
|
Buffer m_buffer;
|
||||||
|
|
||||||
size_t m_beginning;
|
size_t m_beginning;
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ void Status::Changes::songID(int song_id)
|
|||||||
|
|
||||||
# ifdef HAVE_CURL_CURL_H
|
# ifdef HAVE_CURL_CURL_H
|
||||||
if (Config.fetch_lyrics_in_background)
|
if (Config.fetch_lyrics_in_background)
|
||||||
Lyrics::DownloadInBackground(s);
|
myLyrics->fetchInBackground(s);
|
||||||
# endif // HAVE_CURL_CURL_H
|
# endif // HAVE_CURL_CURL_H
|
||||||
|
|
||||||
drawTitle(s);
|
drawTitle(s);
|
||||||
@@ -582,11 +582,10 @@ void Status::Changes::songID(int song_id)
|
|||||||
if (Config.autocenter_mode)
|
if (Config.autocenter_mode)
|
||||||
myPlaylist->locateSong(s);
|
myPlaylist->locateSong(s);
|
||||||
|
|
||||||
if (Config.now_playing_lyrics && isVisible(myLyrics) && myLyrics->previousScreen() == myPlaylist)
|
if (Config.now_playing_lyrics
|
||||||
{
|
&& isVisible(myLyrics)
|
||||||
if (myLyrics->SetSong(s))
|
&& myLyrics->previousScreen() == myPlaylist)
|
||||||
myLyrics->Reload = 1;
|
myLyrics->fetch(s);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elapsedTime(false);
|
elapsedTime(false);
|
||||||
|
|||||||
@@ -89,7 +89,12 @@ public:
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return m_string.empty() && m_properties.empty();
|
||||||
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
m_string.clear();
|
m_string.clear();
|
||||||
|
|||||||
Reference in New Issue
Block a user