mirror of
https://github.com/clangen/musikcube.git
synced 2024-10-02 04:52:32 +00:00
Added cddb lookup (using freedb) to CddaDataModel!
This commit is contained in:
parent
60ce7d1b81
commit
ed4d2fcb4c
@ -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;
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ class CddaDataModel {
|
||||
AudioDisc(char driveLetter);
|
||||
|
||||
std::string GetCddbId();
|
||||
std::string GetCddbQueryString();
|
||||
|
||||
void SetLeadout(DiscTrackPtr leadout);
|
||||
void AddTrack(DiscTrackPtr track);
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user