diff --git a/src/contrib/cddadecoder/CddaDataModel.cpp b/src/contrib/cddadecoder/CddaDataModel.cpp index 8accaa860..8215c4176 100644 --- a/src/contrib/cddadecoder/CddaDataModel.cpp +++ b/src/contrib/cddadecoder/CddaDataModel.cpp @@ -343,6 +343,30 @@ std::string CddaDataModel::AudioDisc::GetCddbId() { return std::string(buffer); } +std::string CddaDataModel::AudioDisc::GetCddbQueryString() { + std::string query = "cmd=cddb+query+" + this->GetCddbId() + "+"; + query += std::to_string(this->GetTrackCount()); + + /* tracks */ + for (auto track : this->tracks) { + size_t frames = + track->GetFrames() + + (track->GetSeconds() * FRAMES_PER_SECOND) + + (track->GetMinutes()) * FRAMES_PER_MINUTE; + + query += "+" + std::to_string(frames); + } + + size_t leadoutOffset = + leadout->GetFrames() + + (leadout->GetSeconds() * FRAMES_PER_SECOND) + + (leadout->GetMinutes()) * FRAMES_PER_MINUTE; + + query += "+" + std::to_string(leadoutOffset / FRAMES_PER_SECOND); + + return query; +} + void CddaDataModel::AudioDisc::SetLeadout(DiscTrackPtr leadout) { this->leadout = leadout; } diff --git a/src/contrib/cddadecoder/CddaDataModel.h b/src/contrib/cddadecoder/CddaDataModel.h index 58af12998..6d71baf77 100644 --- a/src/contrib/cddadecoder/CddaDataModel.h +++ b/src/contrib/cddadecoder/CddaDataModel.h @@ -77,6 +77,7 @@ class CddaDataModel { AudioDisc(char driveLetter); std::string GetCddbId(); + std::string GetCddbQueryString(); void SetLeadout(DiscTrackPtr leadout); void AddTrack(DiscTrackPtr track); diff --git a/src/contrib/cddadecoder/CddaIndexerSource.cpp b/src/contrib/cddadecoder/CddaIndexerSource.cpp index 9e25acc9a..9860552c5 100644 --- a/src/contrib/cddadecoder/CddaIndexerSource.cpp +++ b/src/contrib/cddadecoder/CddaIndexerSource.cpp @@ -38,20 +38,37 @@ #include +#include + #include #include #include +#include + +#include using namespace musik::core::sdk; +struct CddbMetadata { + std::string album; + std::string artist; + std::string year; + std::string genre; + std::vector titles; +}; + using DiscList = std::vector; using DiscIdList = std::set; -static std::mutex globalSinkMutex; +static std::mutex globalStateMutex; static musik::core::sdk::IIndexerNotifier* notifier; +static const std::string FREEDB_URL = "http://freedb.freedb.org/~cddb/cddb.cgi"; +static const std::string FREEDB_HELLO = "&hello=user+musikcube+cddadecoder+0.5.0&proto=6"; +static std::map> discIdToMetadata; + extern "C" __declspec(dllexport) void SetIndexerNotifier(musik::core::sdk::IIndexerNotifier* notifier) { - std::unique_lock lock(globalSinkMutex); + std::unique_lock lock(globalStateMutex); ::notifier = notifier; } @@ -92,6 +109,152 @@ static std::string labelForDrive(const char driveLetter) { return std::string(buffer); } +static size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) { + if (ptr && userdata) { + std::string& str = *(reinterpret_cast(userdata)); + str += std::string(ptr, size * nmemb); + } + return size * nmemb; +} + +static CURL* newCurlEasy(const std::string& url, void* userdata) { + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HEADER, 0); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "musikcube CddaIndexerSource"); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, userdata); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlWriteCallback); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3000); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 7500); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 500); + + //curl_easy_setopt (curl, CURLOPT_PROXY, "localhost"); + //curl_easy_setopt (curl, CURLOPT_PROXYPORT, 8080); + + // if (useproxy) { + // curl_easy_setopt (this->curlEasy, CURLOPT_PROXY, proxyaddress); + // curl_easy_setopt (this->curlEasy, CURLOPT_PROXYUSERPWD, proxyuserpass); + // } + + return curl; +} + +static void cddbLookup(const std::string& discId, std::string listQueryParams) { + listQueryParams = listQueryParams + FREEDB_HELLO; + std::string listResponse; + + /* get a listing of all entries for the specified disc */ + CURL* listing = newCurlEasy(FREEDB_URL, static_cast(&listResponse)); + curl_easy_setopt(listing, CURLOPT_POSTFIELDSIZE, listQueryParams.size()); + curl_easy_setopt(listing, CURLOPT_POSTFIELDS, listQueryParams.c_str()); + + CURLcode result = curl_easy_perform(listing); + curl_easy_cleanup(listing); + + std::string discQueryParams; + + if (result == CURLE_OK) { /* well... we got something back */ + listResponse = boost::replace_all_copy(listResponse, "\r\n", "\n"); + + std::vector lines; + boost::algorithm::split(lines, listResponse, boost::is_any_of("\n")); + + /* just choose the first disc for now. we don't have a way to present a + UI to the user, so this is really all we can do. */ + if (lines.size() >= 1) { + if (lines.at(0).find("200") == 0) { + std::vector parts; + boost::algorithm::split(parts, lines.at(0), boost::is_any_of(" ")); + + if (parts.size() >= 3) { + discQueryParams = "cmd=cddb+read+" + parts[1] + "+" + parts[2] + FREEDB_HELLO; + } + } + /* the first line of the response has a status code. anything + in the 200 range is fine. */ + else if (lines.at(0).find("21") == 0) { + std::vector parts; + boost::algorithm::split(parts, lines.at(1), boost::is_any_of(" ")); + + if (parts.size() >= 2) { + discQueryParams = "cmd=cddb+read+" + parts[0] + "+" + parts[1] + FREEDB_HELLO; + } + } + } + } + + /* we resolved at least one disc. let's look it up. */ + if (discQueryParams.size()) { + std::string discResponse; + CURL* details = newCurlEasy(FREEDB_URL, static_cast(&discResponse)); + curl_easy_setopt(details, CURLOPT_POSTFIELDSIZE, discQueryParams.size()); + curl_easy_setopt(details, CURLOPT_POSTFIELDS, discQueryParams.c_str()); + + CURLcode result = curl_easy_perform(details); + curl_easy_cleanup(details); + + if (result == CURLE_OK) { + discResponse = boost::replace_all_copy(discResponse, "\r\n", "\n"); + + std::vector lines; + boost::algorithm::split(lines, discResponse, boost::is_any_of("\n")); + + std::shared_ptr metadata(new CddbMetadata()); + + for (auto line : lines) { + auto len = line.size(); + if (len) { + auto eq = line.find_first_of('='); + if (eq != std::string::npos) { + std::string key = boost::trim_copy(line.substr(0, eq)); + std::string value = boost::trim_copy(line.substr(eq + 1)); + + if (key == "DTITLE") { + auto slash = value.find_first_of('/'); + std::string artist, album; + + if (slash == std::string::npos) { + artist = album = value; + } + else { + artist = boost::trim_copy(value.substr(0, slash)); + album = boost::trim_copy(value.substr(slash + 1)); + } + + metadata->artist = artist; + metadata->album = album; + } + else if (key == "DYEAR") { + metadata->year = value; + } + else if (key == "DGENRE") { + metadata->genre = value; + } + else if (key.find("TTITLE") == 0) { + metadata->titles.push_back(value); + } + } + } + } + + /* done parsing... */ + if (discId.size()) { + discIdToMetadata[discId] = metadata; + } + } + } +} + CddaIndexerSource::CddaIndexerSource() : model(CddaDataModel::Instance()) { model.AddEventListener(this); @@ -114,7 +277,7 @@ void CddaIndexerSource::RefreshModel() { } void CddaIndexerSource::OnAudioDiscInsertedOrRemoved() { - std::unique_lock lock(globalSinkMutex); + std::unique_lock lock(globalStateMutex); if (::notifier) { ::notifier->ScheduleRescan(this); } @@ -129,28 +292,56 @@ void CddaIndexerSource::OnAfterScan() { } ScanResult CddaIndexerSource::Scan(IIndexerWriter* indexer) { + using namespace std::placeholders; + for (auto disc : this->discs) { char driveLetter = disc->GetDriveLetter(); std::string cddbId = disc->GetCddbId(); + std::shared_ptr metadata = nullptr; + + { + std::unique_lock lock(globalStateMutex); + auto it = discIdToMetadata.find(cddbId); + + if (it == discIdToMetadata.end()) { + try { + cddbLookup(cddbId, disc->GetCddbQueryString()); /* it'll time out in a few seconds */ + it = discIdToMetadata.find(cddbId); + } + catch (...) { + /* this should never happen. */ + } + } + + metadata = (it != discIdToMetadata.end()) ? it->second : nullptr; + } + + std::string label = labelForDrive(driveLetter); + std::string album = metadata ? "[CD] " + metadata->album : label; + std::string artist = metadata ? "[CD] " + metadata->artist : label; + std::string genre = metadata ? "[CD] " + metadata->genre : label; for (int i = 0; i < disc->GetTrackCount(); i++) { auto discTrack = disc->GetTrackAt(i); - - std::string externalId = createExternalId(driveLetter, cddbId, i); - std::string label = labelForDrive(driveLetter); - std::string title = "track #" + std::to_string(i + 1); - + auto externalId = createExternalId(driveLetter, cddbId, i); auto track = indexer->CreateWriter(); - track->SetValue("album", label.c_str()); - track->SetValue("artist", label.c_str()); - track->SetValue("album_artist", label.c_str()); - track->SetValue("genre", label.c_str()); - track->SetValue("title", title.c_str()); + track->SetValue("album", album.c_str()); + track->SetValue("artist", artist.c_str()); + track->SetValue("album_artist", artist.c_str()); + track->SetValue("genre", genre.c_str()); track->SetValue("filename", discTrack->GetFilePath().c_str()); track->SetValue("duration", std::to_string((int) round(discTrack->GetDuration())).c_str()); track->SetValue("track", std::to_string(i + 1).c_str()); + if (metadata) { + track->SetValue("title", metadata->titles.at(i).c_str()); + } + else { + std::string title = "track #" + std::to_string(i + 1); + track->SetValue("title", title.c_str()); + } + indexer->Save(this, track, externalId.c_str()); track->Release(); diff --git a/src/contrib/cddadecoder/cddadecoder.vcxproj b/src/contrib/cddadecoder/cddadecoder.vcxproj index bc4c4cbc1..1aa8153cc 100755 --- a/src/contrib/cddadecoder/cddadecoder.vcxproj +++ b/src/contrib/cddadecoder/cddadecoder.vcxproj @@ -55,8 +55,8 @@ Disabled - ../../;../../3rdparty/include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + ../../;../../3rdparty/include;../../3rdparty/win32_include;../../../../boost_1_64_0_b2;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug @@ -69,16 +69,16 @@ $(IntDir) - shlwapi.lib;%(AdditionalDependencies) - %(AdditionalLibraryDirectories) + shlwapi.lib;libcurl.lib;%(AdditionalDependencies) + ../../3rdparty/win32_lib;%(AdditionalLibraryDirectories) true false - ../../;../../3rdparty/include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + ../../;../../3rdparty/include;../../3rdparty/win32_include;../../../../boost_1_64_0_b2;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded @@ -93,8 +93,8 @@ true - shlwapi.lib;%(AdditionalDependencies) - %(AdditionalLibraryDirectories) + shlwapi.lib;libcurl.lib;%(AdditionalDependencies) + ../../3rdparty/win32_lib;%(AdditionalLibraryDirectories) false diff --git a/src/musikbox/app/window/TrackListView.cpp b/src/musikbox/app/window/TrackListView.cpp index 8ae525bc6..71068a301 100755 --- a/src/musikbox/app/window/TrackListView.cpp +++ b/src/musikbox/app/window/TrackListView.cpp @@ -364,20 +364,22 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi auto trackIndex = this->parent.headers.AdapterToTrackListIndex(rawIndex + 1); TrackPtr track = parent.tracks->Get(trackIndex); - std::string album = track->GetValue(constants::Track::ALBUM); + if (track) { + std::string album = track->GetValue(constants::Track::ALBUM); - if (!album.size()) { - album = _TSTR("tracklist_unknown_album"); + if (!album.size()) { + album = _TSTR("tracklist_unknown_album"); + } + + std::shared_ptr entry(new + TrackListEntry(album, trackIndex, RowType::Separator)); + + entry->SetAttrs(selected + ? COLOR_PAIR(CURSESPP_LIST_ITEM_HIGHLIGHTED_HEADER) + : COLOR_PAIR(CURSESPP_LIST_ITEM_HEADER)); + + return entry; } - - std::shared_ptr entry(new - TrackListEntry(album, trackIndex, RowType::Separator)); - - entry->SetAttrs(selected - ? COLOR_PAIR(CURSESPP_LIST_ITEM_HIGHLIGHTED_HEADER) - : COLOR_PAIR(CURSESPP_LIST_ITEM_HEADER)); - - return entry; } size_t trackIndex = this->parent.headers.AdapterToTrackListIndex(rawIndex);