Make the list of lyrics fetchers customizable
[ncmpcpp.git] / src / lyrics.cpp
blob46d4ed71dcba238808fb29bc86d27995b44d624d
1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <cassert>
22 #include <cerrno>
23 #include <cstring>
24 #include <fstream>
26 #include "browser.h"
27 #include "charset.h"
28 #include "curl_handle.h"
29 #include "format_impl.h"
30 #include "global.h"
31 #include "helpers.h"
32 #include "lyrics.h"
33 #include "playlist.h"
34 #include "scrollpad.h"
35 #include "settings.h"
36 #include "song.h"
37 #include "statusbar.h"
38 #include "title.h"
39 #include "screen_switcher.h"
40 #include "utility/string.h"
42 using Global::MainHeight;
43 using Global::MainStartY;
45 #ifdef HAVE_CURL_CURL_H
46 LyricsFetcher *Lyrics::itsFetcher = nullptr;
47 std::queue<MPD::Song *> Lyrics::itsToDownload;
48 pthread_mutex_t Lyrics::itsDIBLock = PTHREAD_MUTEX_INITIALIZER;
49 size_t Lyrics::itsWorkersNumber = 0;
50 #endif // HAVE_CURL_CURL_H
52 Lyrics *myLyrics;
54 Lyrics::Lyrics()
55 : Screen(NC::Scrollpad(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
56 , Reload(0),
57 #ifdef HAVE_CURL_CURL_H
58 isReadyToTake(0), isDownloadInProgress(0),
59 #endif // HAVE_CURL_CURL_H
60 itsScrollBegin(0)
61 { }
63 void Lyrics::resize()
65 size_t x_offset, width;
66 getWindowResizeParams(x_offset, width);
67 w.resize(width, MainHeight);
68 w.moveTo(x_offset, MainStartY);
69 hasToBeResized = 0;
72 void Lyrics::update()
74 # ifdef HAVE_CURL_CURL_H
75 if (isReadyToTake)
76 Take();
78 if (isDownloadInProgress)
80 w.flush();
81 w.refresh();
83 # endif // HAVE_CURL_CURL_H
84 if (Reload)
86 drawHeader();
87 itsScrollBegin = 0;
88 Load();
89 Reload = 0;
93 void Lyrics::switchTo()
95 using Global::myScreen;
96 if (myScreen != this)
98 # ifdef HAVE_CURL_CURL_H
99 // take lyrics if they were downloaded
100 if (isReadyToTake)
101 Take();
103 if (isDownloadInProgress || itsWorkersNumber > 0)
105 Statusbar::print("Lyrics are being downloaded...");
106 return;
108 # endif // HAVE_CURL_CURL_H
110 auto s = currentSong(myScreen);
111 if (!s)
112 return;
114 if (SetSong(*s))
116 SwitchTo::execute(this);
117 itsScrollBegin = 0;
118 Load();
119 drawHeader();
121 else
122 Statusbar::print("Song must have both artist and title tag set");
124 else
125 switchToPreviousScreen();
128 std::wstring Lyrics::title()
130 std::wstring result = L"Lyrics: ";
132 result += Scroller(
133 Format::stringify<wchar_t>(Format::parse(L"%a - %t"), &itsSong),
134 itsScrollBegin,
135 COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length())
137 return result;
140 #ifdef HAVE_CURL_CURL_H
141 void Lyrics::DownloadInBackground(const MPD::Song &s)
143 if (s.empty() || s.getArtist().empty() || s.getTitle().empty())
144 return;
146 std::string filename = GenerateFilename(s);
147 std::ifstream f(filename.c_str());
148 if (f.is_open())
150 f.close();
151 return;
153 Statusbar::printf("Fetching lyrics for \"%1%\"...",
154 Format::stringify<char>(Config.song_status_format, &s)
157 MPD::Song *s_copy = new MPD::Song(s);
158 pthread_mutex_lock(&itsDIBLock);
159 if (itsWorkersNumber == itsMaxWorkersNumber)
160 itsToDownload.push(s_copy);
161 else
163 ++itsWorkersNumber;
164 pthread_t t;
165 pthread_attr_t attr;
166 pthread_attr_init(&attr);
167 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
168 pthread_create(&t, &attr, DownloadInBackgroundImpl, s_copy);
170 pthread_mutex_unlock(&itsDIBLock);
173 void *Lyrics::DownloadInBackgroundImpl(void *void_ptr)
175 MPD::Song *s = static_cast<MPD::Song *>(void_ptr);
176 DownloadInBackgroundImplHelper(*s);
177 delete s;
179 while (true)
181 pthread_mutex_lock(&itsDIBLock);
182 if (itsToDownload.empty())
184 pthread_mutex_unlock(&itsDIBLock);
185 break;
187 else
189 s = itsToDownload.front();
190 itsToDownload.pop();
191 pthread_mutex_unlock(&itsDIBLock);
193 DownloadInBackgroundImplHelper(*s);
194 delete s;
197 pthread_mutex_lock(&itsDIBLock);
198 --itsWorkersNumber;
199 pthread_mutex_unlock(&itsDIBLock);
201 pthread_exit(0);
204 void Lyrics::DownloadInBackgroundImplHelper(const MPD::Song &s)
206 std::string artist = Curl::escape(s.getArtist());
207 std::string title = Curl::escape(s.getTitle());
209 LyricsFetcher::Result result;
211 if (itsFetcher == nullptr)
213 for (auto &fetcher : Config.lyrics_fetchers)
215 result = fetcher->fetch(artist, title);
216 if (result.first)
217 break;
220 else
221 itsFetcher->fetch(artist, title);
223 if (result.first)
224 Save(GenerateFilename(s), result.second);
227 void *Lyrics::Download()
229 std::string artist = Curl::escape(itsSong.getArtist());
230 std::string title_ = Curl::escape(itsSong.getTitle());
232 auto fetch_lyrics = [&](auto &fetcher) {
233 w << "Fetching lyrics from "
234 << NC::Format::Bold
235 << fetcher->name()
236 << NC::Format::NoBold << "... ";
237 auto result = fetcher->fetch(artist, title_);
238 if (result.first == false)
240 w << NC::Color::Red
241 << result.second
242 << NC::Color::End
243 << '\n';
245 return result;
248 LyricsFetcher::Result result;
250 if (itsFetcher == nullptr)
252 for (auto &fetcher : Config.lyrics_fetchers)
254 result = fetch_lyrics(fetcher);
255 if (result.first)
256 break;
259 else
260 result = fetch_lyrics(itsFetcher);
262 if (result.first)
264 Save(itsFilename, result.second);
265 w.clear();
266 w << Charset::utf8ToLocale(result.second);
268 else
269 w << '\n' << "Lyrics weren't found.";
271 isReadyToTake = 1;
272 pthread_exit(0);
275 void *Lyrics::DownloadWrapper(void *this_ptr)
277 return static_cast<Lyrics *>(this_ptr)->Download();
279 #endif // HAVE_CURL_CURL_H
281 std::string Lyrics::GenerateFilename(const MPD::Song &s)
283 std::string filename;
284 if (Config.store_lyrics_in_song_dir)
286 if (s.isFromDatabase())
288 filename = Config.mpd_music_dir;
289 filename += "/";
290 filename += s.getURI();
292 else
293 filename = s.getURI();
294 // replace song's extension with .txt
295 size_t dot = filename.rfind('.');
296 assert(dot != std::string::npos);
297 filename.resize(dot);
298 filename += ".txt";
300 else
302 std::string file = s.getArtist();
303 file += " - ";
304 file += s.getTitle();
305 file += ".txt";
306 removeInvalidCharsFromFilename(file, Config.generate_win32_compatible_filenames);
307 filename = Config.lyrics_directory;
308 filename += "/";
309 filename += file;
311 return filename;
314 void Lyrics::Load()
316 # ifdef HAVE_CURL_CURL_H
317 if (isDownloadInProgress)
318 return;
319 # endif // HAVE_CURL_CURL_H
321 assert(!itsSong.getArtist().empty());
322 assert(!itsSong.getTitle().empty());
324 itsFilename = GenerateFilename(itsSong);
326 w.clear();
327 w.reset();
329 std::ifstream input(itsFilename.c_str());
330 if (input.is_open())
332 bool first = 1;
333 std::string line;
334 while (std::getline(input, line))
336 // Remove carriage returns as they mess up the display.
337 line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
338 if (!first)
339 w << '\n';
340 w << Charset::utf8ToLocale(line);
341 first = 0;
343 w.flush();
344 if (Reload)
345 w.refresh();
347 else
349 # ifdef HAVE_CURL_CURL_H
350 pthread_create(&itsDownloader, 0, DownloadWrapper, this);
351 isDownloadInProgress = 1;
352 # else
353 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.";
354 w.flush();
355 # endif
359 void Lyrics::Edit()
361 assert(Global::myScreen == this);
363 if (Config.external_editor.empty())
365 Statusbar::print("Proper external_editor variable has to be set in configuration file");
366 return;
369 Statusbar::print("Opening lyrics in external editor...");
371 GNUC_UNUSED int res;
372 if (Config.use_console_editor)
374 res = system(("/bin/sh -c \"" + Config.external_editor + " \\\"" + itsFilename + "\\\"\"").c_str());
375 Load();
376 // below is needed as screen gets cleared, but apparently
377 // ncurses doesn't know about it, so we need to reload main screen
378 endwin();
379 initscr();
380 curs_set(0);
382 else
383 res = system(("nohup " + Config.external_editor + " \"" + itsFilename + "\" > /dev/null 2>&1 &").c_str());
386 bool Lyrics::SetSong(const MPD::Song &s)
388 if (!s.getArtist().empty() && !s.getTitle().empty())
390 itsSong = s;
391 return true;
393 else
394 return false;
397 #ifdef HAVE_CURL_CURL_H
398 void Lyrics::Save(const std::string &filename, const std::string &lyrics)
400 std::ofstream output(filename.c_str());
401 if (output.is_open())
403 output << lyrics;
404 output.close();
408 void Lyrics::Refetch()
410 if (remove(itsFilename.c_str()) && errno != ENOENT)
412 const char msg[] = "Couldn't remove \"%1%\": %2%";
413 Statusbar::printf(msg, wideShorten(itsFilename, COLS-const_strlen(msg)-25), strerror(errno));
414 return;
416 Load();
419 void Lyrics::ToggleFetcher()
421 if (itsFetcher != nullptr)
423 auto fetcher = std::find_if(Config.lyrics_fetchers.begin(),
424 Config.lyrics_fetchers.end(),
425 [](auto &f) { return f.get() == itsFetcher; });
426 assert(fetcher != Config.lyrics_fetchers.end());
427 ++fetcher;
428 if (fetcher != Config.lyrics_fetchers.end())
429 itsFetcher = fetcher->get();
430 else
431 itsFetcher = nullptr;
433 else
435 assert(!Config.lyrics_fetchers.empty());
436 itsFetcher = Config.lyrics_fetchers[0].get();
439 if (itsFetcher != nullptr)
440 Statusbar::printf("Using lyrics fetcher: %s", itsFetcher->name());
441 else
442 Statusbar::print("Using all lyrics fetchers");
445 void Lyrics::Take()
447 assert(isReadyToTake);
448 pthread_join(itsDownloader, 0);
449 w.flush();
450 w.refresh();
451 isDownloadInProgress = 0;
452 isReadyToTake = 0;
454 #endif // HAVE_CURL_CURL_H