Add tagging support & warn on unsupported bitrate
This commit is contained in:
parent
4cd1139b8b
commit
aa323ee11b
@ -34,7 +34,13 @@ Spotify version:
|
||||
* ~~1.1.25~~
|
||||
|
||||
## Building
|
||||
This project uses C++14 on Visual Studio 2019.
|
||||
This project uses C++14 on Visual Studio 2019 with vcpkg:
|
||||
1. [Install vcpkg](https://github.com/microsoft/vcpkg#quick-start-windows) if you haven't done so already
|
||||
2. Install TagLib dependency with vcpkg (`vcpkg install taglib:x86-windows-static-md`)
|
||||
3. Clone the repository (`git clone https://gitlab.com/fuck-capitalism/spotifykeydumper.git`)
|
||||
4. Open the solution 'SpotifyKeyDumper.sln' in Visual Studio 2019 and set the configuration to Release and the
|
||||
platform to x86 and then select Build -> Build Solution **or** run MSBuild (requires Visual Studio 2019 installation):
|
||||
`msbuild /p:Configuration="Release" /p:Platform="x86"`.
|
||||
|
||||
*If you want a specific version, create an issue.*
|
||||
|
||||
|
@ -37,10 +37,14 @@ uriHandler_v47 uriHandler_v47_hook = nullptr;
|
||||
typedef bool (__cdecl* isValidUrl_v25)(int* urlThingArg, int a2);
|
||||
isValidUrl_v25 isValidUrl_v25_hook = nullptr;
|
||||
|
||||
typedef int (__cdecl* getBitrate_v25)(int quality);
|
||||
getBitrate_v25 getBitrate_v25_hook = nullptr;
|
||||
|
||||
std::string authToken = std::string();
|
||||
std::string keyStr = std::string();
|
||||
std::string uriStr = std::string();
|
||||
std::string fileId = std::string();
|
||||
int quality = 4;
|
||||
|
||||
__int64 newPosition = 0;
|
||||
bool signalled = false;
|
||||
@ -54,7 +58,7 @@ void TryDownload()
|
||||
{
|
||||
signalled = false;
|
||||
lastUri = uriStr;
|
||||
std::thread t2(Utils::DownloadSong, std::string(fileId), uriStr, keyStr, authToken);
|
||||
std::thread t2(Utils::DownloadSong, std::string(fileId), uriStr, keyStr, authToken, quality);
|
||||
t2.detach();
|
||||
}
|
||||
}
|
||||
@ -63,7 +67,7 @@ int __cdecl keyToLE_hook_v25(unsigned int* dest, int* key, int bits)
|
||||
{
|
||||
if (bits == 128)
|
||||
{
|
||||
BYTE keyBuffer[16];
|
||||
BYTE keyBuffer[16] = {};
|
||||
BYTE* keyBufPtr = keyBuffer;
|
||||
memcpy(keyBufPtr, key, 16);
|
||||
|
||||
@ -88,7 +92,7 @@ int __cdecl keyToLE_hook_v28(unsigned int* dest, int* key, int bits, bool isEnco
|
||||
{
|
||||
// key is encoded with some sort of algorithm; decode it here
|
||||
|
||||
unsigned int keyDecoded[4];
|
||||
unsigned int keyDecoded[4] = {};
|
||||
unsigned int uVar1;
|
||||
unsigned int keyPtr;
|
||||
unsigned int uVar3;
|
||||
@ -118,7 +122,7 @@ int __cdecl keyToLE_hook_v28(unsigned int* dest, int* key, int bits, bool isEnco
|
||||
}
|
||||
|
||||
// Copy key bytes to new buffer
|
||||
char keyBuffer[16];
|
||||
char keyBuffer[16] = {};
|
||||
char* keyBufPtr = keyBuffer;
|
||||
memcpy(keyBufPtr, decodedKeyPtr, 16);
|
||||
|
||||
@ -331,6 +335,57 @@ bool __cdecl isValidUrl_hook_v25(int* urlThingArg, int arg_4)
|
||||
return isValidUrl_v25_hook(urlThingArg, arg_4);
|
||||
}
|
||||
|
||||
// TODO: Fix downloads on low quality and very high quality to get rid of this
|
||||
bool hasAlertedQuality = false;
|
||||
int __cdecl getBitrate_hook_v25(int quality)
|
||||
{
|
||||
// Force quality to be normal or high quality (since other modes do not reliably work)
|
||||
// (no, forcing it to very high on a free account does not work)
|
||||
|
||||
/*switch (quality)
|
||||
{
|
||||
case 0: // Automatic
|
||||
quality = 3;
|
||||
break;
|
||||
case 1: // Low
|
||||
quality = 2;
|
||||
break;
|
||||
case 4: // Very high
|
||||
quality = 3;
|
||||
break;
|
||||
}*/
|
||||
|
||||
std::string qualityStr = "?";
|
||||
switch (quality)
|
||||
{
|
||||
case 0:
|
||||
qualityStr = "Automatic";
|
||||
break;
|
||||
case 1:
|
||||
qualityStr = "Low";
|
||||
break;
|
||||
case 2:
|
||||
qualityStr = "Normal";
|
||||
break;
|
||||
case 3:
|
||||
qualityStr = "High";
|
||||
break;
|
||||
case 4:
|
||||
qualityStr = "Very High";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hasAlertedQuality && quality != 2 && quality != 3)
|
||||
{
|
||||
std::cout << "Warning! You have selected an unsupported quality (" + qualityStr + "); select Normal or High"
|
||||
" quality in the Spotify settings for proper compatibility." << std::endl;
|
||||
hasAlertedQuality = true;
|
||||
}
|
||||
|
||||
::quality = quality;
|
||||
|
||||
return getBitrate_v25_hook(quality);
|
||||
}
|
||||
|
||||
char* GetKeyFuncAddrV26()
|
||||
{
|
||||
@ -407,7 +462,8 @@ void Hooks::Init()
|
||||
fileIdWriter_v45_hook = (fileIdWriter_v45)Utils::TrampHook32((char*)0x00CBB560, (char*)fileIdWriter_hook_v45,
|
||||
5);
|
||||
signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00B095A0, (char*)signalEmitter_hook_v45,
|
||||
5);
|
||||
5); // 00E48410
|
||||
getBitrate_v25_hook = (getBitrate_v25)Utils::TrampHook32((char*)0x00E48410, (char*)getBitrate_hook_v25, 6);
|
||||
break;
|
||||
case 46:
|
||||
keyToLE_v28_hook = (keyToLE_v28)Utils::TrampHook32((char*)0x010C2FB0, (char*)keyToLE_hook_v28, 6);
|
||||
@ -417,6 +473,7 @@ void Hooks::Init()
|
||||
5);
|
||||
signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00B02270, (char*)signalEmitter_hook_v45,
|
||||
5);
|
||||
getBitrate_v25_hook = (getBitrate_v25)Utils::TrampHook32((char*)0x00E3C780, (char*)getBitrate_hook_v25, 6);
|
||||
break;
|
||||
case 47:
|
||||
keyBuffer_v47 = new char[16]; // 128 bits = 16 bytes
|
||||
@ -428,6 +485,7 @@ void Hooks::Init()
|
||||
5);
|
||||
signalEmitter_v45_hook = (signalEmitter_v45)Utils::TrampHook32((char*)0x00AFBB50, (char*)signalEmitter_hook_v45,
|
||||
5);
|
||||
getBitrate_v25_hook = (getBitrate_v25)Utils::TrampHook32((char*)0x00E3E460, (char*)getBitrate_hook_v25, 6);
|
||||
//startUpdate_v47_hook = (startUpdate_v47)Utils::TrampHook32((char*)0x009FB530, (char*)startUpdate_hook_v47, 5);
|
||||
//uriHandler_v47_hook = (uriHandler_v47)Utils::TrampHook32((char*)0x010A39E0, (char*)uriHandler_hook_v47, 5);
|
||||
//isValidUrl_v25_hook = (isValidUrl_v25)Utils::TrampHook32((char*)0x0105CD80, (char*)isValidUrl_hook_v25, 6);
|
||||
|
@ -16,6 +16,7 @@
|
||||
<ProjectGuid>{e6674e55-3cf4-45e4-a22f-7e257bcea583}</ProjectGuid>
|
||||
<RootNamespace>SpotifyKeyDumper</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows-static-md</VcpkgTriplet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
@ -78,6 +79,7 @@
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<LanguageStandard>stdcpp14</LanguageStandard>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@ -85,6 +87,7 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "tiny-AES-c/aes.h"
|
||||
|
||||
const uint8_t IV[] = { 0x72, 0xE0, 0x67, 0xFB, 0xDD, 0xCB, 0xCF, 0x77, 0xEB, 0xE8, 0xBC, 0x64, 0x3F, 0x63, 0x0D, 0x93 };
|
||||
const std::string urlRegex = "https?:\\/\\/(?:www\.)?([-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})"
|
||||
"\\b([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*)";
|
||||
|
||||
bool Utils::Detour32(char* src, char* dst, const intptr_t len)
|
||||
{
|
||||
@ -61,7 +63,7 @@ int Utils::GetSpotifyVersion()
|
||||
LPBYTE lpVersionInfo = new BYTE[dwFVISize];
|
||||
GetFileVersionInfo(lpszFilePath, 0, dwFVISize, lpVersionInfo);
|
||||
UINT uLen;
|
||||
VS_FIXEDFILEINFO* lpFfi;
|
||||
VS_FIXEDFILEINFO* lpFfi = {};
|
||||
VerQueryValue(lpVersionInfo, _T("\\"), (LPVOID*)&lpFfi, &uLen);
|
||||
DWORD dwFileVersionMS = lpFfi->dwFileVersionMS;
|
||||
DWORD dwFileVersionLS = lpFfi->dwFileVersionLS;
|
||||
@ -115,30 +117,158 @@ std::wstring Utils::Utf8ToUtf16(const std::string& str)
|
||||
return convertedString;
|
||||
}
|
||||
|
||||
enum class FileType
|
||||
{
|
||||
OGG,
|
||||
MP3
|
||||
};
|
||||
|
||||
struct SongInfo
|
||||
{
|
||||
std::string title, artist, album, cover;
|
||||
} songInfo;
|
||||
FileType fileType{ FileType::OGG };
|
||||
std::string title{ std::string() };
|
||||
std::string artist{ std::string() };
|
||||
std::string album{ std::string() };
|
||||
std::string coverUrl{ std::string() };
|
||||
std::string releaseType{ std::string() };
|
||||
std::string releaseDate{ std::string() };
|
||||
std::string isrc{ std::string() };
|
||||
unsigned int year{ 0 };
|
||||
unsigned int trackNum{ 1 };
|
||||
unsigned int totalTracks{ 1 };
|
||||
unsigned int discNum{ 0 };
|
||||
bool isExplicit{ false };
|
||||
};
|
||||
|
||||
void ClearSongInfo()
|
||||
void TagSong(std::wstring songPath, SongInfo* songInfo)
|
||||
{
|
||||
songInfo.title = "";
|
||||
songInfo.artist = "";
|
||||
songInfo.album = "";
|
||||
songInfo.cover = "";
|
||||
TagLib::String fileName = songPath.c_str();
|
||||
TagLib::String tTitle(songInfo->title, TagLib::String::Type::UTF8);
|
||||
TagLib::String tArtist(songInfo->artist, TagLib::String::Type::UTF8);
|
||||
TagLib::String tAlbum(songInfo->album, TagLib::String::Type::UTF8);
|
||||
TagLib::String tReleaseType(songInfo->releaseType, TagLib::String::Type::UTF8);
|
||||
TagLib::String tReleaseDate(songInfo->releaseDate, TagLib::String::Type::UTF8);
|
||||
TagLib::String tIsrc(songInfo->isrc, TagLib::String::Type::UTF8);
|
||||
TagLib::String tTotalTracks(std::to_string(songInfo->totalTracks), TagLib::String::Type::UTF8);
|
||||
TagLib::String tDiscNum(std::to_string(songInfo->discNum), TagLib::String::Type::UTF8);
|
||||
//TagLib::String tIsExplicit(std::to_string(songInfo->isExplicit), TagLib::String::Type::UTF8);
|
||||
|
||||
// Parse episode URL to separate host and path
|
||||
std::string coverUrlHost, coverUrlPath;
|
||||
try
|
||||
{
|
||||
std::regex re(urlRegex);
|
||||
std::smatch match;
|
||||
if (std::regex_search(songInfo->coverUrl, match, re) && match.size() > 1)
|
||||
{
|
||||
coverUrlHost = match.str(1);
|
||||
coverUrlPath = match.str(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Error: Cover art URL is not valid!" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (std::regex_error& e)
|
||||
{
|
||||
// Syntax error in the regular expression
|
||||
std::cout << "Error: Invalid regex!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string coverArtData = Utils::DownloadSpotifyUrl(coverUrlHost, coverUrlPath, "");
|
||||
TagLib::ByteVector tCoverArtData(reinterpret_cast<const char*>(&coverArtData[0]), coverArtData.length());
|
||||
|
||||
switch (songInfo->fileType)
|
||||
{
|
||||
case FileType::OGG:
|
||||
{
|
||||
TagLib::Ogg::Vorbis::File audioFile(songPath.c_str());
|
||||
TagLib::Ogg::XiphComment* tag = audioFile.tag();
|
||||
|
||||
TagLib::FLAC::Picture* coverArt = new TagLib::FLAC::Picture();
|
||||
coverArt->setType((TagLib::FLAC::Picture::Type)0x03); // Front Cover
|
||||
coverArt->setMimeType("image/jpeg");
|
||||
coverArt->setDescription("Front Cover");
|
||||
coverArt->setData(tCoverArtData);
|
||||
|
||||
tag->addPicture(coverArt);
|
||||
tag->setTitle(tTitle);
|
||||
tag->setArtist(tArtist);
|
||||
tag->setAlbum(tAlbum);
|
||||
tag->setTrack(songInfo->trackNum);
|
||||
tag->setYear(songInfo->year);
|
||||
|
||||
tag->addField("DATE", tReleaseDate);
|
||||
tag->addField("DISCNUMBER", tDiscNum);
|
||||
tag->addField("ISRC", tIsrc);
|
||||
tag->addField("SOURCEMEDIA", "Digital Media");
|
||||
tag->addField("RELEASETYPE", tReleaseType);
|
||||
tag->addField("TOTALTRACKS", tTotalTracks);
|
||||
|
||||
audioFile.save();
|
||||
break;
|
||||
}
|
||||
case FileType::MP3:
|
||||
{
|
||||
TagLib::MPEG::File audioFile(songPath.c_str());
|
||||
TagLib::ID3v2::Tag* tag = audioFile.ID3v2Tag(true);
|
||||
|
||||
tag->setTitle(tTitle);
|
||||
tag->setArtist(tArtist);
|
||||
tag->setAlbum(tAlbum);
|
||||
tag->setTrack(songInfo->trackNum);
|
||||
tag->setYear(songInfo->year);
|
||||
|
||||
TagLib::ID3v2::AttachedPictureFrame* frame = new TagLib::ID3v2::AttachedPictureFrame;
|
||||
if (frame->picture().size() < tCoverArtData.size())
|
||||
{
|
||||
frame->setMimeType("image/jpeg");
|
||||
frame->setPicture(tCoverArtData);
|
||||
tag->addFrame(frame);
|
||||
}
|
||||
|
||||
audioFile.save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetUrlNum(int quality)
|
||||
{
|
||||
switch (quality)
|
||||
{
|
||||
case 0: // Automatic
|
||||
return 1; // Set to high quality
|
||||
case 1: // Low
|
||||
return 8;
|
||||
case 2: // Normal
|
||||
return 0;
|
||||
case 3: // High
|
||||
return 1;
|
||||
case 4: // Very high
|
||||
return 2;
|
||||
default:
|
||||
return 1; // Shouldn't happen; set to high quality
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string songRegex =
|
||||
"https?:\\/\\/(?:www\.)?([-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6})"
|
||||
"\\b([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*)";
|
||||
static const std::string albumSearchPattern = "\x68\x65\x69\x67\x68\x74\x22\x20\x3A\x20\x36\x34\x30";
|
||||
static const std::wstring songDirRoot = L"Downloads";
|
||||
static std::wstring songDir = songDirRoot;
|
||||
void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, std::string authToken)
|
||||
void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, std::string authToken, int quality)
|
||||
{
|
||||
std::string downloadStr;
|
||||
std::wstring songExtension = L".ogg";
|
||||
|
||||
SongInfo* songInfo = new SongInfo();
|
||||
/*songInfo->title = "";
|
||||
songInfo->artist = "";
|
||||
songInfo->album = "";
|
||||
songInfo->coverUrl = "";
|
||||
songInfo->fileType = FileType::OGG;*/
|
||||
|
||||
if (fileId.empty() || uri.empty() || authToken.empty())
|
||||
{
|
||||
std::cout << "Could not download song or episode: missing fileId, trackUri, or authToken!" << std::endl;
|
||||
@ -157,8 +287,9 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
}
|
||||
|
||||
// Get storage resolve from Spotify
|
||||
std::string urlNum = std::to_string(GetUrlNum(quality));
|
||||
std::string srStr = DownloadSpotifyUrl("spclient.wg.spotify.com",
|
||||
"/storage-resolve/files/audio/interactive_prefetch/" + fileId + "?product=0", authToken);
|
||||
"/storage-resolve/v2/files/audio/interactive/" + urlNum + "/" + fileId + "?product=0", authToken);
|
||||
|
||||
if (srStr.length() <= 5)
|
||||
{
|
||||
@ -177,7 +308,7 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
std::string songPath;
|
||||
try
|
||||
{
|
||||
std::regex re(songRegex);
|
||||
std::regex re(urlRegex);
|
||||
std::smatch match;
|
||||
if (std::regex_search(srStr, match, re) && match.size() > 1)
|
||||
{
|
||||
@ -206,11 +337,16 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadStr.substr(0, 6).compare("<HTML>") == 0)
|
||||
if (downloadStr.compare(0, 6, "<HTML>") == 0)
|
||||
{
|
||||
std::cout << "Error: " + downloadStr << std::endl;
|
||||
return;
|
||||
}
|
||||
else if (downloadStr.compare(0, 5, "Error") == 0)
|
||||
{
|
||||
std::cout << downloadStr << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt encrypted song data with Tiny AES in C
|
||||
struct AES_ctx ctx;
|
||||
@ -231,10 +367,21 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
std::string metadata = DownloadSpotifyUrl("api.spotify.com", "/v1/tracks/"
|
||||
+ uri.substr(uri.find("spotify:track:") + 14), authToken);
|
||||
|
||||
songInfo.title = strtok((char*)(metadata.substr(metadata.find("is_local") + 55)).c_str(), "\"");
|
||||
songInfo.artist = strtok((char*)(metadata.substr(metadata.find("name") + 9)).c_str(), "\"");
|
||||
songInfo.album = strtok((char*)(metadata.substr(metadata.find(albumSearchPattern) + 404)).c_str(), "\"");
|
||||
songInfo.cover = strtok((char*)(metadata.substr(metadata.find("height") + 30)).c_str(), "\"");
|
||||
songInfo->title = strtok((char*)(metadata.substr(metadata.find("is_local\" :") + 55)).c_str(), "\"");
|
||||
songInfo->artist = strtok((char*)(metadata.substr(metadata.find("name\" :") + 9)).c_str(), "\"");
|
||||
songInfo->album = strtok((char*)(metadata.substr(metadata.find(albumSearchPattern) + 404)).c_str(), "\"");
|
||||
songInfo->coverUrl = strtok((char*)(metadata.substr(metadata.find("height\" :") + 30)).c_str(), "\"");
|
||||
songInfo->releaseType = strtok((char*)(metadata.substr(metadata.find("album_type\" :") + 15)).c_str(), "\"");
|
||||
songInfo->releaseDate = strtok((char*)(metadata.substr(metadata.find("release_date\" :") + 17)).c_str(), "\"");
|
||||
songInfo->isrc = strtok((char*)(metadata.substr(metadata.find("isrc\" :") + 9)).c_str(), "\"");
|
||||
songInfo->trackNum = std::stoi(strtok((char*)(metadata.substr(metadata.find("track_number\" :") + 16)).c_str(),
|
||||
","));
|
||||
songInfo->totalTracks = std::stoi(strtok((char*)(metadata.substr(metadata.find("total_tracks\" :")
|
||||
+ 16)).c_str(), ","));
|
||||
songInfo->discNum = std::stoi(strtok((char*)(metadata.substr(metadata.find("disc_number\" :") + 15)).c_str(),
|
||||
","));
|
||||
songInfo->isExplicit = strtok((char*)(metadata.substr(metadata.find("explicit\" :") + 12)).c_str(), ",");
|
||||
songInfo->fileType = FileType::OGG;
|
||||
|
||||
songExtension = L".ogg";
|
||||
}
|
||||
@ -249,7 +396,7 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
// Parse episode URL to separate host and path
|
||||
try
|
||||
{
|
||||
std::regex re(songRegex);
|
||||
std::regex re(urlRegex);
|
||||
std::smatch match;
|
||||
if (std::regex_search(episodeUrl, match, re) && match.size() > 1)
|
||||
{
|
||||
@ -284,46 +431,50 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
std::string metadata = DownloadSpotifyUrl("api.spotify.com", "/v1/episodes/"
|
||||
+ uri.substr(uri.find("spotify:episode:") + 16), authToken);
|
||||
|
||||
songInfo.title = strtok((char*)(metadata.substr(metadata.find("name") + 9)).c_str(), "\"");
|
||||
songInfo.artist = strtok((char*)(metadata.substr(metadata.find("publisher") + 14)).c_str(), "\"");
|
||||
songInfo.album = strtok((char*)(metadata.substr(metadata.find("media_type") + 37)).c_str(), "\"");
|
||||
songInfo.cover = strtok((char*)(metadata.substr(metadata.find("height") + 28)).c_str(), "\"");
|
||||
songInfo->title = strtok((char*)(metadata.substr(metadata.find("name") + 9)).c_str(), "\"");
|
||||
songInfo->artist = strtok((char*)(metadata.substr(metadata.find("publisher") + 14)).c_str(), "\"");
|
||||
songInfo->album = strtok((char*)(metadata.substr(metadata.find("media_type") + 37)).c_str(), "\"");
|
||||
songInfo->coverUrl = strtok((char*)(metadata.substr(metadata.find("height") + 28)).c_str(), "\"");
|
||||
songInfo->fileType = FileType::MP3;
|
||||
|
||||
songExtension = L".mp3";
|
||||
}
|
||||
|
||||
if (songInfo.title.empty() || songInfo.artist.empty() || songInfo.album.empty())
|
||||
if (songInfo->title.empty() || songInfo->artist.empty() || songInfo->album.empty())
|
||||
{
|
||||
std::cout << "Error: Invalid title/artist/album name!" << std::endl;
|
||||
delete songInfo;
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring tempDirArtist = FixPathStr(Utf8ToUtf16(songInfo.artist));
|
||||
std::wstring tempDirArtist = FixPathStr(Utf8ToUtf16(songInfo->artist));
|
||||
|
||||
songDir = songDirRoot;
|
||||
if (!CreateDirectoryW(songDir.c_str(), NULL) && ERROR_ALREADY_EXISTS != GetLastError())
|
||||
std::cout << "Couldn't create main downloads directory!" << std::endl;
|
||||
|
||||
if (CreateDirectoryW(std::wstring(songDir + L"\\" + tempDirArtist).c_str(), NULL)
|
||||
else if (CreateDirectoryW(std::wstring(songDir + L"\\" + tempDirArtist).c_str(), NULL)
|
||||
|| ERROR_ALREADY_EXISTS == GetLastError())
|
||||
{
|
||||
std::wstring tempDirAlbum = FixPathStr(Utf8ToUtf16(songInfo.album));
|
||||
std::wstring tempDirAlbum = FixPathStr(Utf8ToUtf16(songInfo->album));
|
||||
|
||||
if (CreateDirectoryW(std::wstring(songDir + L"\\" + tempDirArtist + std::wstring(L"\\")
|
||||
+ tempDirAlbum).c_str(), NULL) || ERROR_ALREADY_EXISTS == GetLastError())
|
||||
{
|
||||
songDir += L"\\" + tempDirArtist + std::wstring(L"\\") + tempDirAlbum;
|
||||
std::wstring tempDirSong = FixPathStr(Utf8ToUtf16(songInfo->title));
|
||||
|
||||
std::wstring tempDirSong = FixPathStr(Utf8ToUtf16(songInfo.title));
|
||||
songDir += L"\\" + tempDirArtist + std::wstring(L"\\") + tempDirAlbum + L".\\" + tempDirArtist + L" - "
|
||||
+ tempDirSong + songExtension;
|
||||
|
||||
std::ofstream songFileOut(songDir + L".\\" + tempDirArtist + L" - " + tempDirSong + songExtension,
|
||||
std::ios_base::binary);
|
||||
std::ofstream songFileOut(songDir, std::ios_base::binary);
|
||||
songFileOut.write(downloadStr.c_str(), downloadStr.size());
|
||||
songFileOut.close();
|
||||
|
||||
std::cout << "Finished downloading: " << songInfo.artist << " - \"" << songInfo.title << "\"!" << std::endl;
|
||||
TagSong(songDir, songInfo);
|
||||
|
||||
ClearSongInfo();
|
||||
std::cout << "Finished downloading: " << songInfo->artist << " - \"" << songInfo->title << "\"!"
|
||||
<< std::endl;
|
||||
|
||||
delete songInfo;
|
||||
return;
|
||||
}
|
||||
else
|
||||
@ -335,29 +486,6 @@ void Utils::DownloadSong(std::string fileId, std::string uri, std::string key, s
|
||||
{
|
||||
std::cout << "Couldn't create artist directory!" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Could not finish downloading song!" << std::endl;
|
||||
|
||||
ClearSongInfo();
|
||||
}
|
||||
|
||||
std::string GetLastErrorAsString()
|
||||
{
|
||||
//Get the error message, if any.
|
||||
DWORD errorMessageID = ::GetLastError();
|
||||
if (errorMessageID == 0)
|
||||
return std::string(); //No error message has been recorded
|
||||
|
||||
LPSTR messageBuffer = nullptr;
|
||||
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
|
||||
|
||||
std::string message(messageBuffer, size);
|
||||
|
||||
//Free the buffer.
|
||||
LocalFree(messageBuffer);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
std::string Utils::DownloadSpotifyUrl(std::string host, std::string path, std::string authToken)
|
||||
@ -373,26 +501,26 @@ std::string Utils::DownloadSpotifyUrl(std::string host, std::string path, std::s
|
||||
hSession = InternetOpenA(userAgent.c_str(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
|
||||
|
||||
if (hSession == NULL)
|
||||
return "Error: Could not initialize request!";
|
||||
return "Error: Could not initialize request: " + GetLastError();
|
||||
|
||||
hConnect = InternetConnectA(hSession, host.c_str(), 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0,
|
||||
NULL);
|
||||
|
||||
if (hConnect == NULL)
|
||||
return "Error: Could not create connect!";
|
||||
return "Error: Could not create connect: " + GetLastError();
|
||||
|
||||
hRequest = HttpOpenRequestA(hConnect, "GET", path.c_str(), NULL, NULL, NULL, INTERNET_FLAG_NO_AUTH, 0);
|
||||
|
||||
if (hRequest == NULL)
|
||||
return "Error: Could not create open request!";
|
||||
return "Error: Could not create open request: " + GetLastError();
|
||||
|
||||
HttpAddRequestHeadersA(hRequest, authHeader.c_str(), -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
|
||||
bRequestSent = HttpSendRequestA(hRequest, NULL, 0, NULL, 0);
|
||||
|
||||
if (!bRequestSent)
|
||||
return "Error: Could not send request!";
|
||||
return "Error: Could not send request: " + GetLastError();
|
||||
|
||||
char tmpBuffer[bufferSize];
|
||||
char tmpBuffer[bufferSize] = {};
|
||||
BOOL canRead = true;
|
||||
DWORD bytesRead = -1;
|
||||
|
||||
|
@ -9,7 +9,7 @@ public:
|
||||
static std::string HexString(BYTE* data, int len);
|
||||
static std::wstring FixPathStr(std::wstring str);
|
||||
static std::wstring Utf8ToUtf16(const std::string& str);
|
||||
static void DownloadSong(std::string fileId, std::string fileUri, std::string key, std::string authToken);
|
||||
static void DownloadSong(std::string fileId, std::string uri, std::string key, std::string authToken, int quality);
|
||||
static std::string DownloadSpotifyUrl(std::string host, std::string path, std::string authToken);
|
||||
static bool BadPtr(void* ptr);
|
||||
};
|
||||
|
@ -25,4 +25,11 @@
|
||||
#include <vector>
|
||||
#include <WinInet.h>
|
||||
|
||||
// TagLib
|
||||
#define TAGLIB_STATIC
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/attachedpictureframe.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/vorbisfile.h>
|
||||
|
||||
#endif //PCH_H
|
||||
|
Loading…
Reference in New Issue
Block a user