Files
ncmpcpp/src/lyrics.cpp

438 lines
10 KiB
C++

/***************************************************************************
* Copyright (C) 2008-2011 by Andrzej Rybczak *
* electricityispower@gmail.com *
* *
* 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. *
***************************************************************************/
#include <cassert>
#include <cerrno>
#include <fstream>
#include "browser.h"
#include "charset.h"
#include "curl_handle.h"
#include "global.h"
#include "helpers.h"
#include "lyrics.h"
#include "playlist.h"
#include "settings.h"
#include "song.h"
using Global::MainHeight;
using Global::MainStartY;
using Global::myScreen;
using Global::myOldScreen;
#ifdef HAVE_CURL_CURL_H
LyricsFetcher **Lyrics::itsFetcher = 0;
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 = new Lyrics;
void Lyrics::Init()
{
w = new Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, brNone);
isInitialized = 1;
}
void Lyrics::Resize()
{
size_t x_offset, width;
GetWindowResizeParams(x_offset, width);
w->Resize(width, MainHeight);
w->MoveTo(x_offset, MainStartY);
hasToBeResized = 0;
}
void Lyrics::Update()
{
# ifdef HAVE_CURL_CURL_H
if (isReadyToTake)
Take();
if (isDownloadInProgress)
{
w->Flush();
w->Refresh();
}
# endif // HAVE_CURL_CURL_H
if (ReloadNP)
{
const MPD::Song *s = myPlaylist->NowPlayingSong();
if (s && !s->GetArtist().empty() && !s->GetTitle().empty())
{
Global::RedrawHeader = 1;
itsScrollBegin = 0;
itsSong = *s;
Load();
}
ReloadNP = 0;
}
}
void Lyrics::SwitchTo()
{
using Global::myLockedScreen;
using Global::myInactiveScreen;
if (myScreen == this)
return myOldScreen->SwitchTo();
if (!isInitialized)
Init();
if (hasToBeResized)
Resize();
itsScrollBegin = 0;
# ifdef HAVE_CURL_CURL_H
// take lyrics if they were downloaded
if (isReadyToTake)
Take();
if (isDownloadInProgress || itsWorkersNumber > 0)
{
ShowMessage("Lyrics are being downloaded...");
return;
}
# endif // HAVE_CURL_CURL_H
if (const MPD::Song *s = myScreen->CurrentSong())
{
if (!s->GetArtist().empty() && !s->GetTitle().empty())
{
myOldScreen = myScreen;
myScreen = this;
itsSong = *s;
Load();
Global::RedrawHeader = 1;
}
else
{
ShowMessage("Song must have both artist and title tag set!");
return;
}
}
// if we resize for locked screen, we have to do that in the end since
// fetching lyrics may fail (eg. if tags are missing) and we don't want
// to adjust screen size then.
if (myLockedScreen)
{
UpdateInactiveScreen(this);
Resize();
}
}
std::basic_string<my_char_t> Lyrics::Title()
{
std::basic_string<my_char_t> result = U("Lyrics: ");
result += Scroller(TO_WSTRING(itsSong.toString("{%a - %t}")), itsScrollBegin, COLS-result.length()-(Config.new_design ? 2 : Global::VolumeState.length()));
return result;
}
void Lyrics::SpacePressed()
{
Config.now_playing_lyrics = !Config.now_playing_lyrics;
ShowMessage("Reload lyrics if song changes: %s", Config.now_playing_lyrics ? "On" : "Off");
}
#ifdef HAVE_CURL_CURL_H
void Lyrics::DownloadInBackground(const MPD::Song *s)
{
if (!s || s->GetArtist().empty() || s->GetTitle().empty())
return;
if (!s->Localized())
const_cast<MPD::Song *>(s)->Localize();
std::string filename = GenerateFilename(*s);
std::ifstream f(filename.c_str());
if (f.is_open())
{
f.close();
return;
}
ShowMessage("Fetching lyrics for %s...", s->toString(Config.song_status_format_no_colors).c_str());
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(MPD::Song *s)
{
std::string artist = Curl::escape(locale_to_utf_cpy(s->GetArtist()));
std::string title = Curl::escape(locale_to_utf_cpy(s->GetTitle()));
LyricsFetcher::Result result;
bool fetcher_defined = itsFetcher && *itsFetcher;
for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
{
result = (*plugin)->fetch(artist, title);
if (result.first)
break;
if (fetcher_defined)
break;
}
if (result.first == true)
Save(GenerateFilename(*s), result.second);
}
void *Lyrics::Download()
{
std::string artist = Curl::escape(locale_to_utf_cpy(itsSong.GetArtist()));
std::string title = Curl::escape(locale_to_utf_cpy(itsSong.GetTitle()));
LyricsFetcher::Result result;
// if one of plugins is selected, try only this one,
// otherwise try all of them until one of them succeeds
bool fetcher_defined = itsFetcher && *itsFetcher;
for (LyricsFetcher **plugin = fetcher_defined ? itsFetcher : lyricsPlugins; *plugin != 0; ++plugin)
{
*w << "Fetching lyrics from " << fmtBold << (*plugin)->name() << fmtBoldEnd << "... ";
result = (*plugin)->fetch(artist, title);
if (result.first == false)
*w << clRed << result.second << clEnd << "\n";
else
break;
if (fetcher_defined)
break;
}
if (result.first == true)
{
Save(itsFilename, result.second);
utf_to_locale(result.second);
w->Clear();
*w << result.second;
}
else
*w << "\nLyrics 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.isFromDB())
{
filename = Config.mpd_music_dir;
filename += "/";
filename += s.GetFile();
}
else
filename = s.GetFile();
// 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 = locale_to_utf_cpy(s.GetArtist());
file += " - ";
file += locale_to_utf_cpy(s.GetTitle());
file += ".txt";
EscapeUnallowedChars(file);
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());
itsSong.Localize();
itsFilename = GenerateFilename(itsSong);
CreateDir(Config.lyrics_directory);
w->Clear();
w->Reset();
std::ifstream input(itsFilename.c_str());
if (input.is_open())
{
bool first = 1;
std::string line;
while (getline(input, line))
{
if (!first)
*w << "\n";
utf_to_locale(line);
*w << line;
first = 0;
}
w->Flush();
if (ReloadNP)
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 " << itsFolder << " directory (file syntax is \"$ARTIST - $TITLE.txt\") or recompile ncmpcpp with curl support.";
w->Flush();
# endif
}
}
void Lyrics::Edit()
{
if (myScreen != this)
return;
if (Config.external_editor.empty())
{
ShowMessage("External editor is not set!");
return;
}
ShowMessage("Opening lyrics in external editor...");
if (Config.use_console_editor)
{
system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
// below is needed as screen gets cleared, but apparently
// ncurses doesn't know about it, so we need to reload main screen
endwin();
initscr();
curs_set(0);
}
else
system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
}
#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();
}
}
#endif // HAVE_CURL_CURL_H
void Lyrics::Refetch()
{
if (remove(itsFilename.c_str()) && errno != ENOENT)
{
static const char msg[] = "Couldn't remove \"%s\": %s";
ShowMessage(msg, Shorten(TO_WSTRING(itsFilename), COLS-static_strlen(msg)-25).c_str(), strerror(errno));
return;
}
Load();
}
#ifdef HAVE_CURL_CURL_H
void Lyrics::ToggleFetcher()
{
if (itsFetcher && *itsFetcher)
++itsFetcher;
else
itsFetcher = &lyricsPlugins[0];
if (*itsFetcher)
ShowMessage("Using lyrics database: %s", (*itsFetcher)->name());
else
ShowMessage("Using all lyrics databases");
}
void Lyrics::Take()
{
assert(isReadyToTake);
pthread_join(itsDownloader, 0);
w->Flush();
w->Refresh();
isDownloadInProgress = 0;
isReadyToTake = 0;
}
#endif // HAVE_CURL_CURL_H