Added cddb lookup (using freedb) to CddaDataModel!

This commit is contained in:
casey langen 2017-04-23 00:44:54 -07:00
parent 60ce7d1b81
commit ed4d2fcb4c
5 changed files with 251 additions and 33 deletions

View File

@ -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;
}

View File

@ -77,6 +77,7 @@ class CddaDataModel {
AudioDisc(char driveLetter);
std::string GetCddbId();
std::string GetCddbQueryString();
void SetLeadout(DiscTrackPtr leadout);
void AddTrack(DiscTrackPtr track);

View File

@ -38,20 +38,37 @@
#include <core/sdk/IIndexerNotifier.h>
#include <curl/curl.h>
#include <string>
#include <sstream>
#include <set>
#include <map>
#include <boost/algorithm/string.hpp>
using namespace musik::core::sdk;
struct CddbMetadata {
std::string album;
std::string artist;
std::string year;
std::string genre;
std::vector<std::string> titles;
};
using DiscList = std::vector<CddaDataModel::AudioDiscPtr>;
using DiscIdList = std::set<std::string>;
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<std::string, std::shared_ptr<CddbMetadata>> discIdToMetadata;
extern "C" __declspec(dllexport) void SetIndexerNotifier(musik::core::sdk::IIndexerNotifier* notifier) {
std::unique_lock<std::mutex> lock(globalSinkMutex);
std::unique_lock<std::mutex> 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<std::string*>(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<void*>(&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<std::string> 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<std::string> 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<std::string> 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<void*>(&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<std::string> lines;
boost::algorithm::split(lines, discResponse, boost::is_any_of("\n"));
std::shared_ptr<CddbMetadata> 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<std::mutex> lock(globalSinkMutex);
std::unique_lock<std::mutex> 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<CddbMetadata> metadata = nullptr;
{
std::unique_lock<std::mutex> 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();

View File

@ -55,8 +55,8 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>../../;../../3rdparty/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../;../../3rdparty/include;../../3rdparty/win32_include;../../../../boost_1_64_0_b2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -69,16 +69,16 @@
<ObjectFileName>$(IntDir)</ObjectFileName>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>shlwapi.lib;libcurl.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../../3rdparty/win32_lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>../../;../../3rdparty/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>../../;../../3rdparty/include;../../3rdparty/win32_include;../../../../boost_1_64_0_b2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
@ -93,8 +93,8 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>shlwapi.lib;libcurl.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../../3rdparty/win32_lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>false</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>

View File

@ -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<TrackListEntry> 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<TrackListEntry> 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);