Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpc-hc/mpc-hc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorioannis <ioannis.e@gmail.com>2017-05-17 05:08:25 +0300
committerXhmikosR <xhmikosr@gmail.com>2017-06-03 15:12:56 +0300
commitac90b41d75e8d2ed8abb2c9567e66ab2787dd345 (patch)
tree548d612ea6c2c83a98da7a4d83f20157d1a67222
parentdcd539083535a6635a3909006a32587781695ad0 (diff)
Temporarily disable titlovi and ysubs subtitles providers until we decide to permanently remove them.
-rw-r--r--src/mpc-hc/SubtitlesProvider.cpp2266
-rw-r--r--src/mpc-hc/SubtitlesProvider.h282
2 files changed, 1282 insertions, 1266 deletions
diff --git a/src/mpc-hc/SubtitlesProvider.cpp b/src/mpc-hc/SubtitlesProvider.cpp
index d5075f788..e4ece81db 100644
--- a/src/mpc-hc/SubtitlesProvider.cpp
+++ b/src/mpc-hc/SubtitlesProvider.cpp
@@ -1,1127 +1,1139 @@
-/*
- * (C) 2016-2017 see Authors.txt
- *
- * This file is part of MPC-HC.
- *
- * MPC-HC is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * MPC-HC is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "stdafx.h"
-#include "SubtitlesProvider.h"
-#include "SubtitlesProvidersUtils.h"
-#include "mplayerc.h"
-#include "ISOLang.h"
-#include "Logger.h"
-#include "MediaInfo/library/Source/ThirdParty/base64/base64.h"
-#include "tinyxml2/library/tinyxml2.h"
-#include "rapidjson/include/rapidjson/document.h"
-
-#define LOG if (AfxGetAppSettings().bEnableLogging) SUBTITLES_LOG
-#define LOG_NONE _T("()")
-#define LOG_INPUT _T("(\"%S\")")
-#define LOG_OUTPUT _T("()=%S")
-#define LOG_BOTH _T("(\"%S\")=%S")
-#define LOG_ERROR _T("() ERROR: %S")
-
-#define GUESSED_NAME_POSTFIX " (*)"
-#define CheckAbortAndReturn() { if (IsAborting()) return SR_ABORTED; }
-
-using namespace SubtitlesProvidersUtils;
-
-class LanguageDownloadException : public std::exception
-{
- using exception::exception;
-};
-
-/******************************************************************************
-** Register providers
-******************************************************************************/
-void SubtitlesProviders::RegisterProviders()
-{
- Register<OpenSubtitles>(this);
- Register<podnapisi>(this);
- Register<titlovi>(this);
- Register<SubDB>(this);
- Register<ysubs>(this);
- Register<Napisy24>(this);
-}
-
-/******************************************************************************
-** OpenSubtitles
-******************************************************************************/
-
-void OpenSubtitles::Initialize()
-{
- xmlrpc = std::make_unique<XmlRpcClient>("http://api.opensubtitles.org/xml-rpc");
- xmlrpc->setIgnoreCertificateAuthority();
-}
-
-SRESULT OpenSubtitles::Login(const std::string& sUserName, const std::string& sPassword)
-{
- if (xmlrpc) {
- XmlRpcValue args, result;
- args[0] = sUserName;
- args[1] = sPassword;
- args[2] = "en";
- const auto& strUA = UserAgent();
- args[3] = strUA.c_str(); // Test with "OSTestUserAgent"
- if (!xmlrpc->execute("LogIn", args, result)) {
- return SR_FAILED;
- }
-
- if (result["status"].getType() == XmlRpcValue::Type::TypeString) {
- if (result["status"] == std::string("200 OK")) {
- token = result["token"];
- } else if (result["status"] == std::string("401 Unauthorized")) {
- // Notify user that User/Pass provided are invalid.
- CString msg;
- msg.Format(IDS_SUB_CREDENTIALS_ERROR, Name().c_str(), UserName().c_str());
- AfxMessageBox(msg, MB_ICONERROR | MB_OK);
- }
- }
- }
-
- LOG(LOG_BOTH, sUserName.c_str(), token.valid() ? (LPCSTR)token : "failed");
- return token.valid() ? SR_SUCCEEDED : SR_FAILED;
-}
-
-SRESULT OpenSubtitles::LogOut()
-{
- if (xmlrpc && token.valid()) {
- XmlRpcValue args, result;
- args[0] = token;
- VERIFY(xmlrpc->execute("LogOut", args, result));
- token.clear();
- }
- m_nLoggedIn = SPL_UNDEFINED;
-
- LOG(LOG_NONE);
- return SR_SUCCEEDED;
-}
-
-SRESULT OpenSubtitles::Hash(SubtitlesInfo& pFileInfo)
-{
- pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
- LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
- return SR_SUCCEEDED;
-}
-
-SRESULT OpenSubtitles::Search(const SubtitlesInfo& pFileInfo)
-{
- const auto languages = LanguagesISO6392();
- XmlRpcValue args, result;
- args[0] = token;
- auto& movieInfo = args[1][0];
- movieInfo["sublanguageid"] = !languages.empty() ? JoinContainer(languages, ",") : "all";
- movieInfo["moviehash"] = pFileInfo.fileHash;
- movieInfo["moviebytesize"] = std::to_string(pFileInfo.fileSize);
- //args[1][1]["sublanguageid"] = !languages.empty() ? languages : "all";
- //args[1][1]["tag"] = pFileInfo.fileName + "." + pFileInfo.fileExtension;
- args[2]["limit"] = 500;
-
- LOG(LOG_INPUT,
- StringFormat("{ sublanguageid=\"%s\", moviehash=\"%s\", moviebytesize=\"%s\", limit=%d }",
- (LPCSTR)movieInfo["sublanguageid"],
- (LPCSTR)movieInfo["moviehash"],
- (LPCSTR)movieInfo["moviebytesize"],
- (int)args[2]["limit"]).c_str());
-
- if (!xmlrpc->execute("SearchSubtitles", args, result)) {
- return SR_FAILED;
- }
-
- if (result["data"].getType() != XmlRpcValue::Type::TypeArray) {
- return SR_FAILED;
- }
-
- int nCount = result["data"].size();
- for (int i = 0; i < nCount; ++i) {
- CheckAbortAndReturn();
- XmlRpcValue& data(result["data"][i]);
- SubtitlesInfo pSubtitlesInfo;
- pSubtitlesInfo.id = (const char*)data["IDSubtitleFile"];
- pSubtitlesInfo.discNumber = data["SubActualCD"];
- pSubtitlesInfo.discCount = data["SubSumCD"];
- pSubtitlesInfo.fileExtension = (const char*)data["SubFormat"];
- pSubtitlesInfo.languageCode = (const char*)data["ISO639"]; //"SubLanguageID"
- pSubtitlesInfo.languageName = (const char*)data["LanguageName"];
- pSubtitlesInfo.downloadCount = data["SubDownloadsCnt"];
-
- pSubtitlesInfo.fileName = (const char*)data["SubFileName"];
- regexResult results;
- stringMatch("\"([^\"]+)\" (.+)", (const char*)data["MovieName"], results);
- if (!results.empty()) {
- pSubtitlesInfo.title = results[0];
- pSubtitlesInfo.title2 = results[1];
- } else {
- pSubtitlesInfo.title = (const char*)data["MovieName"];
- }
- pSubtitlesInfo.year = (int)data["MovieYear"] == 0 ? -1 : (int)data["MovieYear"];
- pSubtitlesInfo.seasonNumber = (int)data["SeriesSeason"] == 0 ? -1 : (int)data["SeriesSeason"];
- pSubtitlesInfo.episodeNumber = (int)data["SeriesEpisode"] == 0 ? -1 : (int)data["SeriesEpisode"];
- pSubtitlesInfo.hearingImpaired = data["SubHearingImpaired"];
- pSubtitlesInfo.url = (const char*)data["SubtitlesLink"];
- pSubtitlesInfo.releaseNames.emplace_back((const char*)data["MovieReleaseName"]);
- pSubtitlesInfo.imdbid = (const char*)data["IDMovieImdb"];
- pSubtitlesInfo.corrected = (int)data["SubBad"] ? -1 : 0;
- Set(pSubtitlesInfo);
- }
- return SR_SUCCEEDED;
-}
-
-SRESULT OpenSubtitles::Download(SubtitlesInfo& pSubtitlesInfo)
-{
- XmlRpcValue args, result;
- args[0] = token;
- args[1][0] = pSubtitlesInfo.id;
- if (!xmlrpc->execute("DownloadSubtitles", args, result)) {
- return SR_FAILED;
- }
-
- LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
-
- if (result["data"].getType() != XmlRpcValue::Type::TypeArray) {
- return SR_FAILED;
- }
-
- pSubtitlesInfo.fileContents = Base64::decode(std::string(result["data"][0]["data"]));
- return SR_SUCCEEDED;
-}
-
-SRESULT OpenSubtitles::Upload(const SubtitlesInfo& pSubtitlesInfo)
-{
- XmlRpcValue args, result;
- args[0] = token;
-
- //TODO: Ask how to obtain commented values !!!
- args[1]["cd1"]["subhash"] = StringToHash(pSubtitlesInfo.fileContents, CALG_MD5);
- args[1]["cd1"]["subfilename"] = pSubtitlesInfo.fileName + ".srt";
- args[1]["cd1"]["moviehash"] = pSubtitlesInfo.fileHash;
- args[1]["cd1"]["moviebytesize"] = std::to_string(pSubtitlesInfo.fileSize);
- //args[1]["cd1"]["movietimems"];
- //args[1]["cd1"]["movieframes"];
- //args[1]["cd1"]["moviefps"];
- args[1]["cd1"]["moviefilename"] = pSubtitlesInfo.fileName + "." + pSubtitlesInfo.fileExtension;
-
- CheckAbortAndReturn();
- if (!xmlrpc->execute("TryUploadSubtitles", args, result)) {
- return SR_FAILED;
- }
- CheckAbortAndReturn();
-
- if ((int)result["alreadyindb"] == 1) {
- return SR_EXISTS;
- } else if ((int)result["alreadyindb"] == 0) {
- // We need imdbid to proceed
- if (result["data"].getType() == XmlRpcValue::Type::TypeArray) {
- args[1]["baseinfo"]["idmovieimdb"] = result["data"][0]["IDMovieImdb"];
- } else if (!pSubtitlesInfo.imdbid.empty()) {
- args[1]["baseinfo"]["idmovieimdb"] = pSubtitlesInfo.imdbid;
- } else {
- std::string title(StringReplace(pSubtitlesInfo.title, "and", "&"));
- if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
- XmlRpcValue _args, _result;
- _args[0] = token;
- _args[1][0] = pSubtitlesInfo.fileHash;
- if (!xmlrpc->execute("CheckMovieHash", _args, _result)) {
- return SR_FAILED;
- }
-
- if (_result["data"].getType() == XmlRpcValue::Type::TypeStruct) {
- //regexResults results;
- //stringMatch("\"(.+)\" (.+)", (const char*)data["MovieName"], results);
- //if (!results.empty()) {
- // pSubtitlesInfo.title = results[0][0];
- // pSubtitlesInfo.title2 = results[0][1];
- //} else {
- // pSubtitlesInfo.title = (const char*)data["MovieName"];
- //}
- regexResults results;
- stringMatch("\"(.+)\" .+|(.+)", StringReplace((const char*)_result["data"][pSubtitlesInfo.fileHash]["MovieName"], "and", "&"), results);
- std::string _title(results[0][0] + results[0][1]);
-
- if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(_result["data"][pSubtitlesInfo.fileHash]["MovieYear"])))*/) {
- args[1]["baseinfo"]["idmovieimdb"] = _result["data"][pSubtitlesInfo.fileHash]["MovieImdbID"]; //imdbid
- }
- }
- }
-
- if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
- XmlRpcValue _args, _result;
- _args[0] = token;
- _args[1][0] = pSubtitlesInfo.fileHash;
- if (!xmlrpc->execute("CheckMovieHash2", _args, _result)) {
- return SR_FAILED;
- }
-
- if (_result["data"].getType() == XmlRpcValue::Type::TypeArray) {
- int nCount = _result["data"][pSubtitlesInfo.fileHash].size();
- for (int i = 0; i < nCount; ++i) {
- regexResults results;
- stringMatch("\"(.+)\" .+|(.+)", StringReplace((const char*)_result["data"][pSubtitlesInfo.fileHash][i]["MovieName"], "and", "&"), results);
- std::string _title(results[0][0] + results[0][1]);
-
- if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(_result["data"][pSubtitlesInfo.fileHash][i]["MovieYear"])))*/) {
- args[1]["baseinfo"]["idmovieimdb"] = _result["data"][pSubtitlesInfo.fileHash][i]["MovieImdbID"]; //imdbid
- break;
- }
- }
- }
- }
-
- if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
- XmlRpcValue _args, _result;
- _args[0] = token;
- _args[1] = title;
- if (!xmlrpc->execute("SearchMoviesOnIMDB", _args, _result)) {
- return SR_FAILED;
- }
- if (_result["data"].getType() == XmlRpcValue::Type::TypeArray) {
- int nCount = _result["data"].size();
- for (int i = 0; i < nCount; ++i) {
- regexResults results;
- stringMatch("(.+) [(](\\d{4})[)]", StringReplace((const char*)_result["data"][i]["title"], "and", "&"), results);
- if (results.size() == 1) {
- std::string _title(results[0][0]);
-
- if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(results[0][1].c_str())))*/) {
- args[1]["baseinfo"]["idmovieimdb"] = _result["data"][i]["id"]; //imdbid
- break;
- }
- }
- }
- }
- }
- }
- if (args[1]["baseinfo"]["idmovieimdb"].valid()) {
- XmlRpcValue _args, _result;
- _args[0] = token;
- _args[1][0]["moviehash"] = pSubtitlesInfo.fileHash;
- _args[1][0]["moviebytesize"] = (int)pSubtitlesInfo.fileSize;
- _args[1][0]["imdbid"] = args[1]["baseinfo"]["idmovieimdb"];
- //_args[1][0]["movietimems"];
- //_args[1][0]["moviefps"];
- _args[1][0]["moviefilename"] = pSubtitlesInfo.fileName + "." + pSubtitlesInfo.fileExtension;
- if (!xmlrpc->execute("InsertMovieHash", _args, _result)) {
- return SR_FAILED;
- }
- // REsult value is irrelevant
- _result["data"]["accepted_moviehashes"];
-
-
- //args[1]["baseinfo"]["moviereleasename"];
- //args[1]["baseinfo"]["movieaka"];
- //args[1]["baseinfo"]["sublanguageid"];
- //args[1]["baseinfo"]["subauthorcomment"];
- if (pSubtitlesInfo.hearingImpaired != -1) {
- args[1]["baseinfo"]["hearingimpaired"] = pSubtitlesInfo.hearingImpaired;
- }
- //args[1]["baseinfo"]["highdefinition"];
- //args[1]["baseinfo"]["automatictranslation"];
-
- args[1]["cd1"]["subcontent"] = Base64::encode(StringGzipCompress(pSubtitlesInfo.fileContents));
-
- if (!xmlrpc->execute("UploadSubtitles", args, result)) {
- return SR_FAILED;
- }
- LOG(LOG_OUTPUT, (LPCSTR)result["data"]);
-
- return SR_SUCCEEDED;
- }
- }
- return SR_FAILED;
-}
-
-const std::set<std::string>& OpenSubtitles::Languages() const
-{
- static std::once_flag initialized;
- static std::set<std::string> result;
-
- try {
- std::call_once(initialized, [this]() {
- if (!CheckInternetConnection()) {
- throw LanguageDownloadException("No internet connection.");
- }
- XmlRpcValue args, res;
- args = "en";
- if (!xmlrpc->execute("GetSubLanguages", args, res)) {
- throw LanguageDownloadException("Failed to execute xmlrpc command.");
- }
- if (res["data"].getType() != XmlRpcValue::Type::TypeArray) {
- throw LanguageDownloadException("Response is not an array.");
- }
-
- auto& data = res["data"];
- int count = data.size();
- for (int i = 0; i < count; ++i) {
-#ifdef _DEBUG
- // Validate if language code conversion is in sync with OpenSubtitles database.
- std::string subLanguageID = data[i]["SubLanguageID"];
- std::string ISO6391 = data[i]["ISO639"];
- ASSERT(!ISO6391.empty());
- ASSERT(!subLanguageID.empty());
- ASSERT(ISOLang::ISO6391To6392(ISO6391.c_str()) == subLanguageID.c_str());
- ASSERT(ISOLang::ISO6392To6391(subLanguageID.c_str()) == ISO6391.c_str());
- //std::string languageName = data[i]["LanguageName"];
- //ASSERT(ISO639XToLanguage(ISO6391.c_str()) == languageName.c_str());
- //ASSERT(ISO639XToLanguage(subLanguageID.c_str()) == languageName.c_str());
-#endif
- result.emplace(data[i]["ISO639"]);
- }
- });
- } catch (const LanguageDownloadException& e) {
- UNREFERENCED_PARAMETER(e);
- LOG(LOG_ERROR, e.what());
- }
- return result;
-}
-
-bool OpenSubtitles::NeedLogin()
-{
- // return true to call Login() or false to skip Login()
- if (!token.valid()) {
- return true;
- }
-
- XmlRpcValue args, result;
- args[0] = token;
- if (!xmlrpc->execute("NoOperation", args, result)) {
- return false;
- }
-
- if ((result["status"].getType() == XmlRpcValue::Type::TypeString) && (result["status"] == std::string("200 OK"))) {
- return false;
- }
-
- return true;
-}
-
-
-/******************************************************************************
-** SubDB
-******************************************************************************/
-
-SRESULT SubDB::Hash(SubtitlesInfo& pFileInfo)
-{
- std::vector<BYTE> buffer(2 * PROBE_SIZE);
- if (pFileInfo.pAsyncReader) {
- UINT64 position = 0;
- pFileInfo.pAsyncReader->SyncRead(position, PROBE_SIZE, (BYTE*)&buffer[0]);
- position = std::max((UINT64)0, (UINT64)(pFileInfo.fileSize - PROBE_SIZE));
- pFileInfo.pAsyncReader->SyncRead(position, PROBE_SIZE, (BYTE*)&buffer[PROBE_SIZE]);
- } else {
- CFile file;
- CFileException fileException;
- if (file.Open(CString(pFileInfo.filePath.c_str()),
- CFile::modeRead | CFile::osSequentialScan | CFile::shareDenyNone | CFile::typeBinary,
- &fileException)) {
- file.Read(&buffer[0], PROBE_SIZE);
- file.Seek(std::max((UINT64)0, (UINT64)(pFileInfo.fileSize - PROBE_SIZE)), CFile::begin);
- file.Read(&buffer[PROBE_SIZE], PROBE_SIZE);
- }
- }
- pFileInfo.fileHash = StringToHash(std::string((char*)&buffer[0], buffer.size()), CALG_MD5);
- LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
- return SR_SUCCEEDED;
-}
-
-SRESULT SubDB::Search(const SubtitlesInfo& pFileInfo)
-{
- SRESULT searchResult = SR_UNDEFINED;
- std::string url(StringFormat("http://api.thesubdb.com/?action=search&hash=%s", pFileInfo.fileHash.c_str()));
- LOG(LOG_INPUT, url.c_str());
-
- std::string data;
- searchResult = DownloadInternal(url, "", data);
-
- if (!data.empty()) {
- for (const auto& iter : StringTokenize(data, ",")) {
- CheckAbortAndReturn();
- if (CheckLanguage(iter)) {
- SubtitlesInfo pSubtitlesInfo;
- pSubtitlesInfo.id = pFileInfo.fileHash;
- pSubtitlesInfo.fileExtension = "srt";
- pSubtitlesInfo.fileName = pFileInfo.fileName + GUESSED_NAME_POSTFIX;
- pSubtitlesInfo.languageCode = iter;
- pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(iter.c_str()));
- pSubtitlesInfo.discNumber = 1;
- pSubtitlesInfo.discCount = 1;
- pSubtitlesInfo.title = pFileInfo.title;
- Set(pSubtitlesInfo);
- }
- }
- }
-
- return searchResult;
-}
-
-SRESULT SubDB::Download(SubtitlesInfo& pSubtitlesInfo)
-{
- std::string url(StringFormat("http://api.thesubdb.com/?action=download&hash=%s&language=%s", pSubtitlesInfo.id.c_str(), pSubtitlesInfo.languageCode.c_str()));
- LOG(LOG_INPUT, url.c_str());
- return DownloadInternal(url, "", pSubtitlesInfo.fileContents);
-}
-
-SRESULT SubDB::Upload(const SubtitlesInfo& pSubtitlesInfo)
-{
-#define MULTIPART_BOUNDARY "xYzZY"
- std::string url(StringFormat("http://api.thesubdb.com/?action=upload&hash=%s", pSubtitlesInfo.fileHash.c_str()));
- stringMap headers({
- { "User-Agent", UserAgent() },
- { "Content-Type", "multipart/form-data; boundary=" MULTIPART_BOUNDARY },
- });
-
- std::string content, data;
- content += StringFormat("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", MULTIPART_BOUNDARY, "hash", pSubtitlesInfo.fileHash.c_str());
- content += StringFormat("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s.%s\"\r\nContent-Type: application/octet-stream\r\nContent-Transfer-Encoding: binary\r\n\r\n",
- MULTIPART_BOUNDARY, "file", pSubtitlesInfo.fileHash.c_str(), "srt");
- content += pSubtitlesInfo.fileContents;
- content += StringFormat("\r\n--%s--\r\n", MULTIPART_BOUNDARY);
-
- CheckAbortAndReturn();
-
- DWORD dwStatusCode = NULL;
- StringUpload(url, headers, content, data, FALSE, &dwStatusCode);
-
- LOG(LOG_BOTH, url.c_str(), std::to_string(dwStatusCode).c_str());
-
- switch (dwStatusCode) {
- case 201:
- return SR_SUCCEEDED; //'Uploaded': (HTTP/1.1 201 Created): If everything was OK, the HTTP status code 201 will be returned.
- case 403:
- return SR_EXISTS; //'Duplicated': (HTTP/1.1 403 Forbidden): If the subtitle file already exists in our database, the HTTP status code 403 will be returned.
- case 400:
- return SR_FAILED; //'Malformed': (HTTP/1.1 400 Bad Request): If the request was malformed, the HTTP status code 400 will be returned.
- case 415:
- return SR_FAILED; //'Invalid': (HTTP/1.1 415 Unsupported Media Type): If the subtitle file is not supported by our database, the HTTP status code 415 will be returned.
- default:
- return SR_UNDEFINED;
- }
-}
-
-const std::set<std::string>& SubDB::Languages() const
-{
- static std::once_flag initialized;
- static std::set<std::string> result;
- try {
- std::call_once(initialized, [this]() {
- if (!CheckInternetConnection()) {
- throw LanguageDownloadException("No internet connection.");
- }
- std::string data;
- if (DownloadInternal("http://api.thesubdb.com/?action=languages", "", data) != SR_SUCCEEDED) {
- throw LanguageDownloadException("Failed to download language list.");
- }
- for (const auto& str : StringTokenize(data, ",")) {
- result.emplace(str);
- }
- });
- } catch (const LanguageDownloadException& e) {
- UNREFERENCED_PARAMETER(e);
- LOG(LOG_ERROR, e.what());
- }
- return result;
-}
-
-/******************************************************************************
-** podnapisi
-******************************************************************************/
-
-SRESULT podnapisi::Login(const std::string& sUserName, const std::string& sPassword)
-{
- //TODO: implement
- return SR_UNDEFINED;
-}
-
-/*
-UPDATED
-https://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164#p212652
-RESULTS ------------------------------------------------
-"/sXML/1/" //Reply in XML format
-"/page//" //Return nth page of results
-SEARCH -------------------------------------------------
-"/sT/1/" //Type: -1=all, 0=movies, 1=series, don't specify for auto detection
-"/sAKA/1/" //Include movie title aliases
-"/sM//" //Movie id from www.omdb.si
-"/sK//" //Title url encoded text
-"/sY//" //Year number
-"/sTS//" //Season number
-"/sTE//" //Episode number
-"/sR//" //Release name url encoded text
-"/sJ/0/" //Languages (old integer IDs), comma delimited, 0=all
-"/sL/en/" //Languages in ISO ISO codes (exception are sr-latn and pt-br), comma delimited
-"/sEH//" //Exact hash match (OSH)
-"/sMH//" //Movie hash (OSH)
-SEARCH ADDITIONAL --------------------------------------
-"/sFT/0/" //Subtitles Format: 0=all, 1=MicroDVD, 2=SAMI, 3=SSA, 4=SubRip, 5=SubViewer 2.0, 6=SubViewer, 7=MPSub, 8=Advanced SSA, 9=DVDSubtitle, 10=TMPlayer, 11=MPlayer2
-"/sA/0/" //Search subtitles by user id, 0=all
-"/sI//" //Search subtitles by subtitle id
-SORTING ------------------------------------------------
-"/sS//" //Sorting field: movie, year, fps, language, downloads, cds, username, time, rating
-"/sO//" //Soring order: asc, desc
-FILTERS ------------------------------------------------
-"/sOE/1/" //Subtitles for extended edition only
-"/sOD/1/" //Subtitles suitable for DVD only
-"/sOH/1/" //Subtitles for high-definition video only
-"/sOI/1/" //Subtitles for hearing impaired only
-"/sOT/1/" //Technically correct only
-"/sOL/1/" //Grammatically correct only
-"/sOA/1/" //Author subtitles only
-"/sOCS/1/" //Only subtitles for a complete season
-UNKNOWN ------------------------------------------------
-"/sH//" //Search subtitles by video file hash ??? (not working for me)
-*/
-
-SRESULT podnapisi::Search(const SubtitlesInfo& pFileInfo)
-{
- SRESULT searchResult = SR_UNDEFINED;
- int page = 1, pages = 1, results = 0;
- do {
- CheckAbortAndReturn();
-
- const auto languages = LanguagesISO6391();
- std::string search(pFileInfo.title);
- if (!pFileInfo.country.empty()) {
- search += " " + pFileInfo.country;
- }
- search = std::regex_replace(search, std::regex(" and | *[!?&':] *", RegexFlags), " ");
-
- std::string url("https://www.podnapisi.net/ppodnapisi/search");
- url += "?sXML=1";
- url += "&sAKA=1";
- url += (!search.empty() ? "&sK=" + UrlEncode(search.c_str()) : "");
- url += (pFileInfo.year != -1 ? "&sY=" + std::to_string(pFileInfo.year) : "");
- url += (pFileInfo.seasonNumber != -1 ? "&sTS=" + std::to_string(pFileInfo.seasonNumber) : "");
- url += (pFileInfo.episodeNumber != -1 ? "&sTE=" + std::to_string(pFileInfo.episodeNumber) : "");
- url += "&sMH=" + pFileInfo.fileHash;
- //url += "&sR=" + UrlEncode(pFileInfo.fileName.c_str());
- url += (!languages.empty() ? "&sL=" + JoinContainer(languages, ",") : "");
- url += "&page=" + std::to_string(page);
- LOG(LOG_INPUT, url.c_str());
-
- std::string data;
- searchResult = DownloadInternal(url, "", data);
-
- using namespace tinyxml2;
-
- tinyxml2::XMLDocument dxml;
- if (dxml.Parse(data.c_str()) == XML_SUCCESS) {
-
- auto GetChildElementText = [&](XMLElement * pElement, const char* value) -> std::string {
- std::string str;
- XMLElement* pChildElement = pElement->FirstChildElement(value);
- if (pChildElement != nullptr)
- {
- auto pText = pChildElement->GetText();
- if (pText != nullptr) {
- str = pText;
- }
- }
- return str;
- };
-
- XMLElement* pRootElmt = dxml.FirstChildElement("results");
- if (pRootElmt) {
- XMLElement* pPaginationElmt = pRootElmt->FirstChildElement("pagination");
- if (pPaginationElmt) {
- page = atoi(GetChildElementText(pPaginationElmt, "current").c_str());
- pages = atoi(GetChildElementText(pPaginationElmt, "count").c_str());
- results = atoi(GetChildElementText(pPaginationElmt, "results").c_str());
- }
- // 30 results per page
- if (page > 1) {
- return SR_TOOMANY;
- }
-
- if (results > 0) {
- XMLElement* pSubtitleElmt = pRootElmt->FirstChildElement("subtitle");
-
- while (pSubtitleElmt) {
- CheckAbortAndReturn();
-
- SubtitlesInfo pSubtitlesInfo;
-
- pSubtitlesInfo.id = GetChildElementText(pSubtitleElmt, "pid");
- pSubtitlesInfo.title = HtmlSpecialCharsDecode(GetChildElementText(pSubtitleElmt, "title").c_str());
-
- std::string year = GetChildElementText(pSubtitleElmt, "year");
- pSubtitlesInfo.year = year.empty() ? -1 : atoi(year.c_str());
-
- pSubtitlesInfo.url = GetChildElementText(pSubtitleElmt, "url");
- std::string format = GetChildElementText(pSubtitleElmt, "format");
- pSubtitlesInfo.fileExtension = (format == "SubRip" || format == "N/A") ? "srt" : format;
-
- pSubtitlesInfo.languageCode = podnapisi_languages[atoi(GetChildElementText(pSubtitleElmt, "languageId").c_str())].code;
- pSubtitlesInfo.languageName = GetChildElementText(pSubtitleElmt, "languageName");
- pSubtitlesInfo.seasonNumber = atoi(GetChildElementText(pSubtitleElmt, "tvSeason").c_str());
- pSubtitlesInfo.episodeNumber = atoi(GetChildElementText(pSubtitleElmt, "tvEpisode").c_str());
- pSubtitlesInfo.discCount = atoi(GetChildElementText(pSubtitleElmt, "cds").c_str());
- pSubtitlesInfo.discNumber = pSubtitlesInfo.discCount;
-
- std::string flags = GetChildElementText(pSubtitleElmt, "flags");
- pSubtitlesInfo.hearingImpaired = (flags.find("n") != std::string::npos) ? TRUE : FALSE;
- pSubtitlesInfo.corrected = (flags.find("r") != std::string::npos) ? -1 : 0;
- pSubtitlesInfo.downloadCount = atoi(GetChildElementText(pSubtitleElmt, "downloads").c_str());
- pSubtitlesInfo.imdbid = GetChildElementText(pSubtitleElmt, "movieId");
- pSubtitlesInfo.frameRate = atof(GetChildElementText(pSubtitleElmt, "fps").c_str());
-
- XMLElement* pReleasesElem = pSubtitleElmt->FirstChildElement("releases");
- if (pReleasesElem) {
- XMLElement* pReleaseElem = pReleasesElem->FirstChildElement("release");
-
- while (pReleaseElem) {
- auto pText = pReleaseElem->GetText();
-
- if (!pText) {
- continue;
- }
-
- pSubtitlesInfo.releaseNames.emplace_back(pText);
-
- if (pSubtitlesInfo.fileName.empty() || pFileInfo.fileName.find(pText) != std::string::npos) {
- pSubtitlesInfo.fileName = pText;
- pSubtitlesInfo.fileName += "." + pSubtitlesInfo.fileExtension;
- }
- pReleaseElem = pReleaseElem->NextSiblingElement();
- }
- }
-
- if (pSubtitlesInfo.fileName.empty()) {
- std::string str = pSubtitlesInfo.title;
- if (!year.empty()) {
- str += " " + year;
- }
- if (pSubtitlesInfo.seasonNumber > 0) {
- str += StringFormat(" S%02d", pSubtitlesInfo.seasonNumber);
- }
- if (pSubtitlesInfo.episodeNumber > 0) {
- str += StringFormat("%sE%02d", (pSubtitlesInfo.seasonNumber > 0) ? "" : " ", pSubtitlesInfo.episodeNumber);
- }
- str += GUESSED_NAME_POSTFIX;
- pSubtitlesInfo.fileName = str;
- }
-
- Set(pSubtitlesInfo);
- pSubtitleElmt = pSubtitleElmt->NextSiblingElement();
- }
- }
- }
- }
- } while (page++ < pages);
-
- return searchResult;
-}
-
-SRESULT podnapisi::Hash(SubtitlesInfo& pFileInfo)
-{
- pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
- LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
- return SR_SUCCEEDED;
-}
-
-SRESULT podnapisi::Download(SubtitlesInfo& pSubtitlesInfo)
-{
- std::string url = StringFormat("https://www.podnapisi.net/subtitles/%s/download", pSubtitlesInfo.id.c_str());
- LOG(LOG_INPUT, url.c_str());
- return DownloadInternal(url, "", pSubtitlesInfo.fileContents);
-}
-
-const std::set<std::string>& podnapisi::Languages() const
-{
- static std::once_flag initialized;
- static std::set<std::string> result;
-
- std::call_once(initialized, [this]() {
- for (const auto& iter : podnapisi_languages) {
- if (strlen(iter.code)) {
- result.emplace(iter.code);
- }
- }
- });
- return result;
-}
-
-/******************************************************************************
-** titlovi
-******************************************************************************/
-
-/*
- x-dev_api_id=
- uiculture=hr,rs,si,ba,en,mk
- language=hr,rs,sr,si,ba,en,mk
- keyword=
- year=
- mt=numeric value representing type of subtitle (Movie / TV show / documentary 1, 2, 3)
- season=numeric value representing season
- episode=numeric value representing season episode
- forcefilename=true (default is false) return direct download link
-*/
-
-SRESULT titlovi::Search(const SubtitlesInfo& pFileInfo)
-{
- // Need to filter not supported languages, because their API returns .hr language otherwise.
- auto selectedLanguages = LanguagesISO6391();
- bool userSelectedLanguage = !selectedLanguages.empty();
- const auto languagesIntersection = GetLanguagesIntersection(std::move(selectedLanguages));
- std::string KEY = "WC1ERVYtREVTS1RPUF9maWUyYS1hMVJzYS1hSHc0UA==";
- std::string url(StringFormat("http://api.titlovi.com/xml_get_api.ashx?x-dev_api_id=%s&uiculture=en&forcefilename=true", Base64::decode(KEY).c_str()));
- url += "&mt=" + (pFileInfo.seasonNumber != -1 ? std::to_string(2) : std::to_string(1));
- url += "&keyword=" + UrlEncode(pFileInfo.title.c_str());
- url += (pFileInfo.seasonNumber != -1 ? "&season=" + std::to_string(pFileInfo.seasonNumber) : "");
- url += (pFileInfo.episodeNumber != -1 ? "&episode=" + std::to_string(pFileInfo.episodeNumber) : "");
- url += (pFileInfo.year != -1 ? "&year=" + std::to_string(pFileInfo.year) : "");
- url += (userSelectedLanguage ? "&language=" + JoinContainer(languagesIntersection, ",") : "");
- LOG(LOG_INPUT, url.c_str());
-
- std::string data;
- SRESULT searchResult = DownloadInternal(url, "", data);
-
- tinyxml2::XMLDocument dxml;
- if (dxml.Parse(data.c_str()) == tinyxml2::XMLError::XML_SUCCESS) {
-
- auto GetChildElementText = [&](tinyxml2::XMLElement * pElement, const char* value) -> std::string {
- std::string str;
- auto pChildElement = pElement->FirstChildElement(value);
-
- if (pChildElement != nullptr)
- {
- auto pText = pChildElement->GetText();
- if (pText != nullptr) {
- str = pText;
- }
- }
- return str;
- };
-
- auto pRootElmt = dxml.FirstChildElement("subtitles");
-
- if (pRootElmt) {
- int num = pRootElmt->IntAttribute("resultsCount");
-
- if (num > 0/* && num < 50*/) {
- auto pSubtitleElmt = pRootElmt->FirstChildElement();
-
- while (pSubtitleElmt) {
- SubtitlesInfo pSubtitlesInfo;
-
- pSubtitlesInfo.title = GetChildElementText(pSubtitleElmt, "title");
- pSubtitlesInfo.languageCode = GetChildElementText(pSubtitleElmt, "language");
-
- for (const auto& language : titlovi_languages) {
- if (pSubtitlesInfo.languageCode == language.code) {
- pSubtitlesInfo.languageCode = language.name;
- }
- }
-
- pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(pSubtitlesInfo.languageCode.c_str()));
- auto releaseNames = StringTokenize(GetChildElementText(pSubtitleElmt, "release"), "/");
- pSubtitlesInfo.releaseNames = { releaseNames.begin(), releaseNames.end() };
- pSubtitlesInfo.imdbid = GetChildElementText(pSubtitleElmt, "imdbId");
- pSubtitlesInfo.frameRate = atof(GetChildElementText(pSubtitleElmt, "fps").c_str());
- pSubtitlesInfo.year = atoi(GetChildElementText(pSubtitleElmt, "year").c_str());
- pSubtitlesInfo.discNumber = atoi(GetChildElementText(pSubtitleElmt, "cd").c_str());
- pSubtitlesInfo.discCount = pSubtitlesInfo.discNumber;
- pSubtitlesInfo.downloadCount = atoi(GetChildElementText(pSubtitleElmt, "downloads").c_str());
-
- auto pSubtitleChildElmt = pSubtitleElmt->FirstChildElement("urls");
- if (pSubtitleChildElmt) {
- auto pURLElement = pSubtitleChildElmt->FirstChildElement("url");
- while (pURLElement) {
- if (pURLElement->Attribute("what", "download")) {
- pSubtitlesInfo.url = pURLElement->GetText();
- }
- if (pURLElement->Attribute("what", "direct")) {
- pSubtitlesInfo.id = pURLElement->GetText();
- }
- pURLElement = pURLElement->NextSiblingElement();
- }
- }
-
- if ((pSubtitleChildElmt = pSubtitleElmt->FirstChildElement("TVShow")) != nullptr) {
- pSubtitlesInfo.seasonNumber = atoi(GetChildElementText(pSubtitleChildElmt, "season").c_str());
- pSubtitlesInfo.episodeNumber = atoi(GetChildElementText(pSubtitleChildElmt, "episode").c_str());
- }
-
- pSubtitlesInfo.fileName = pSubtitlesInfo.title + " " + std::to_string(pSubtitlesInfo.year);
- if (pSubtitlesInfo.seasonNumber > 0) {
- pSubtitlesInfo.fileName += StringFormat(" S%02d", pSubtitlesInfo.seasonNumber);
- }
- if (pSubtitlesInfo.episodeNumber > 0) {
- pSubtitlesInfo.fileName += StringFormat("%sE%02d", (pSubtitlesInfo.seasonNumber > 0) ? "" : " ", pSubtitlesInfo.episodeNumber);
- }
-
- bool found = false;
- for (const auto& str : pSubtitlesInfo.releaseNames) {
- if (pFileInfo.fileName.find(str) != std::string::npos) {
- pSubtitlesInfo.fileName += " " + str;
- found = true;
- break;
- }
- }
-
- if (!found && !pSubtitlesInfo.releaseNames.empty()) {
- pSubtitlesInfo.fileName += " " + pSubtitlesInfo.releaseNames.front();
- }
- pSubtitlesInfo.fileName += GUESSED_NAME_POSTFIX;
-
- Set(pSubtitlesInfo);
- pSubtitleElmt = pSubtitleElmt->NextSiblingElement();
- }
- }
- }
- }
- return searchResult;
-}
-
-SRESULT titlovi::Download(SubtitlesInfo& pSubtitlesInfo)
-{
- LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
- return DownloadInternal(pSubtitlesInfo.id, "", pSubtitlesInfo.fileContents);
-}
-
-const std::set<std::string>& titlovi::Languages() const
-{
- static std::once_flag initialized;
- static std::set<std::string> result;
-
- std::call_once(initialized, [this]() {
- for (const auto& iter : titlovi_languages) {
- if (strlen(iter.name)) {
- result.emplace(iter.name);
- }
- }
- });
- return result;
-}
-
-/******************************************************************************
-** ysubs
-******************************************************************************/
-
-SRESULT ysubs::Search(const SubtitlesInfo& pFileInfo)
-{
- SRESULT searchResult = SR_UNDEFINED;
- using namespace rapidjson;
-
- if (pFileInfo.year && pFileInfo.seasonNumber == -1 && pFileInfo.episodeNumber == -1) {
- std::string urlApi(StringFormat("https://yts.ag/api/v2/list_movies.json?query_term=%s", UrlEncode(pFileInfo.title.c_str())));
- LOG(LOG_INPUT, urlApi.c_str());
-
- std::string data;
- searchResult = DownloadInternal(urlApi, "", data);
-
- Document d;
- if (d.ParseInsitu(&data[0]).HasParseError()) {
- return SR_FAILED;
- }
-
- auto root = d.FindMember("data");
- if (root != d.MemberEnd()) {
- auto iter = root->value.FindMember("movies");
- if ((iter != root->value.MemberEnd()) && (iter->value.IsArray())) {
- std::set<std::string> imdb_ids;
- for (auto elem = iter->value.Begin(); elem != iter->value.End(); ++elem) {
- std::string imdb = elem->FindMember("imdb_code")->value.GetString();
- if (imdb_ids.find(imdb) == imdb_ids.end()) {
- imdb_ids.insert(imdb);
-
- std::string urlSubs(StringFormat("http://api.ysubs.com/subs/%s", imdb.c_str()));
- LOG(LOG_INPUT, urlSubs.c_str());
-
- std::string data1;
- searchResult = DownloadInternal(urlSubs, "", data1);
- Document d1;
- if (d1.ParseInsitu(&data1[0]).HasParseError()) {
- return SR_FAILED;
- }
-
- auto iter1 = d1.FindMember("subs");
- if (iter1 != d1.MemberEnd()) {
- iter1 = iter1->value.FindMember(imdb.c_str());
- if (iter1 != d1.MemberEnd()) {
- for (auto elem1 = iter1->value.MemberBegin(); elem1 != iter1->value.MemberEnd(); ++elem1) {
- std::string lang = elem1->name.GetString();
- std::string lang_code;
- for (const auto& language : ysubs_languages) {
- if (lang == language.name) {
- lang_code = language.code;
- }
- }
- if (CheckLanguage(lang_code)) {
- for (auto elem2 = elem1->value.Begin(); elem2 != elem1->value.End(); ++elem2) {
- SubtitlesInfo pSubtitlesInfo;
-
- pSubtitlesInfo.title = elem->FindMember("title")->value.GetString();
- pSubtitlesInfo.languageCode = lang_code;
- pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(pSubtitlesInfo.languageCode.c_str()));
- pSubtitlesInfo.releaseNames.emplace_back("YIFY");
- pSubtitlesInfo.imdbid = imdb;
- pSubtitlesInfo.year = elem->FindMember("year")->value.GetInt();
- pSubtitlesInfo.discNumber = 1;
- pSubtitlesInfo.discCount = 1;
-
- pSubtitlesInfo.url = "http://www.yifysubtitles.com/movie-imdb/" + imdb;
- std::string str = elem2->FindMember("url")->value.GetString();
- pSubtitlesInfo.id = "http://www.yifysubtitles.com" + str;
- pSubtitlesInfo.hearingImpaired = elem2->FindMember("hi")->value.GetInt();
- pSubtitlesInfo.corrected = elem2->FindMember("rating")->value.GetInt();
-
- pSubtitlesInfo.fileName = pFileInfo.fileName;
- pSubtitlesInfo.fileName += GUESSED_NAME_POSTFIX;
-
- Set(pSubtitlesInfo);
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- return searchResult;
-}
-
-SRESULT ysubs::Download(SubtitlesInfo& pSubtitlesInfo)
-{
- LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
- return DownloadInternal(pSubtitlesInfo.id, "", pSubtitlesInfo.fileContents);
-}
-
-const std::set<std::string>& ysubs::Languages() const
-{
- static std::once_flag initialized;
- static std::set<std::string> result;
-
- std::call_once(initialized, [this]() {
- for (const auto& iter : ysubs_languages) {
- if (strlen(iter.code)) {
- result.emplace(iter.code);
- }
- }
- });
- return result;
-}
-
-/******************************************************************************
-** Napisy24
-******************************************************************************/
-
-SRESULT Napisy24::Search(const SubtitlesInfo& pFileInfo)
-{
- stringMap headers({
- { "User-Agent", UserAgent() },
- { "Content-Type", "application/x-www-form-urlencoded" }
- });
- std::string data;
- std::string url = "http://napisy24.pl/run/CheckSubAgent.php";
- std::string content = "postAction=CheckSub";
- content += "&ua=mpc-hc";
- content += "&ap=mpc-hc";
- content += "&fh=" + pFileInfo.fileHash;
- content += "&fs=" + std::to_string(pFileInfo.fileSize);
- content += "&fn=" + pFileInfo.fileName;
-
- LOG(LOG_INPUT, std::string(url + "?" + content).c_str());
- StringUpload(url, headers, content, data);
-
- if (data.length() < 4) {
- return SR_FAILED;
- }
-
- // Get status
- std::string status = data.substr(0, 4);
- if (status != "OK-2" && status != "OK-3") {
- return SR_FAILED;
- }
- data.erase(0, 5);
-
- size_t infoEnd = data.find("||");
- if (infoEnd == std::string::npos) {
- return SR_FAILED;
- }
-
- // Search already returns whole file
- SubtitlesInfo subtitleInfo;
- subtitleInfo.fileContents = data.substr(infoEnd + 2);
- subtitleInfo.languageCode = "pl"; // API doesn't support other languages yet.
-
- // Remove subtitle data
- data.erase(infoEnd);
-
- std::unordered_map<std::string, std::string> subtitleInfoMap;
- std::istringstream stringStream(data);
- std::string entry;
- while (std::getline(stringStream, entry, '|')) {
- auto delimPos = entry.find(':');
- if (delimPos == std::string::npos) {
- continue;
- }
- std::string key = entry.substr(0, delimPos);
- if (entry.length() <= delimPos + 1) {
- continue;
- }
- std::string value = entry.substr(delimPos + 1);
- subtitleInfoMap[key] = value;
- }
-
- subtitleInfo.url = "http://napisy24.pl/komentarze?napisId=" + subtitleInfoMap["napisId"];
- subtitleInfo.title = subtitleInfoMap["ftitle"];
- subtitleInfo.imdbid = subtitleInfoMap["fimdb"];
-
- auto it = subtitleInfoMap.find("fyear");
- if (it != subtitleInfoMap.end()) {
- subtitleInfo.year = std::stoi(it->second);
- }
-
- it = subtitleInfoMap.find("fps");
- if (it != subtitleInfoMap.end()) {
- subtitleInfo.frameRate = std::stod(it->second);
- }
-
- int hour, minute, second;
- if (sscanf_s(subtitleInfoMap["time"].c_str(), "%02d:%02d:%02d", &hour, &minute, &second) == 3) {
- subtitleInfo.lengthMs = ((hour * 60 + minute) * 60 + second) * 1000;
- }
-
- subtitleInfo.fileName = pFileInfo.fileName + "." + pFileInfo.fileExtension;
- subtitleInfo.discNumber = 1;
- subtitleInfo.discCount = 1;
-
- Set(subtitleInfo);
-
- return SR_SUCCEEDED;
-}
-
-SRESULT Napisy24::Hash(SubtitlesInfo& pFileInfo)
-{
- pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
- LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
- return SR_SUCCEEDED;
-}
-
-SRESULT Napisy24::Download(SubtitlesInfo& subtitlesInfo)
-{
- LOG(LOG_INPUT, subtitlesInfo.url.c_str());
- return subtitlesInfo.fileContents.empty() ? SR_FAILED : SR_SUCCEEDED;
-}
-
-const std::set<std::string>& Napisy24::Languages() const
-{
- static std::set<std::string> result = {"pl"};
- return result;
-}
+/*
+ * (C) 2016-2017 see Authors.txt
+ *
+ * This file is part of MPC-HC.
+ *
+ * MPC-HC is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MPC-HC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "stdafx.h"
+#include "SubtitlesProvider.h"
+#include "SubtitlesProvidersUtils.h"
+#include "mplayerc.h"
+#include "ISOLang.h"
+#include "Logger.h"
+#include "MediaInfo/library/Source/ThirdParty/base64/base64.h"
+#include "tinyxml2/library/tinyxml2.h"
+#include "rapidjson/include/rapidjson/document.h"
+
+#define LOG if (AfxGetAppSettings().bEnableLogging) SUBTITLES_LOG
+#define LOG_NONE _T("()")
+#define LOG_INPUT _T("(\"%S\")")
+#define LOG_OUTPUT _T("()=%S")
+#define LOG_BOTH _T("(\"%S\")=%S")
+#define LOG_ERROR _T("() ERROR: %S")
+
+#define GUESSED_NAME_POSTFIX " (*)"
+#define CheckAbortAndReturn() { if (IsAborting()) return SR_ABORTED; }
+
+// Uncomment define to compile the temporarily disabled subtitles providers.
+// Subtitles providers are disabled in case their API ceases to work.
+// In case the API is not restored in due time, the provider will eventually
+// be removed from mpc-hc. Upon removal, also remove icon resources.
+//#define MPCHC_DISABLED_SUBTITLES_PROVIDER
+
+using namespace SubtitlesProvidersUtils;
+
+class LanguageDownloadException : public std::exception
+{
+ using exception::exception;
+};
+
+/******************************************************************************
+** Register providers
+******************************************************************************/
+void SubtitlesProviders::RegisterProviders()
+{
+ Register<OpenSubtitles>(this);
+ Register<podnapisi>(this);
+#ifdef MPCHC_DISABLED_SUBTITLES_PROVIDER
+ Register<titlovi>(this);
+#endif // MPCHC_DISABLED_SUBTITLES_PROVIDER
+ Register<SubDB>(this);
+#ifdef MPCHC_DISABLED_SUBTITLES_PROVIDER
+ Register<ysubs>(this);
+#endif // MPCHC_DISABLED_SUBTITLES_PROVIDER
+ Register<Napisy24>(this);
+}
+
+/******************************************************************************
+** OpenSubtitles
+******************************************************************************/
+
+void OpenSubtitles::Initialize()
+{
+ xmlrpc = std::make_unique<XmlRpcClient>("http://api.opensubtitles.org/xml-rpc");
+ xmlrpc->setIgnoreCertificateAuthority();
+}
+
+SRESULT OpenSubtitles::Login(const std::string& sUserName, const std::string& sPassword)
+{
+ if (xmlrpc) {
+ XmlRpcValue args, result;
+ args[0] = sUserName;
+ args[1] = sPassword;
+ args[2] = "en";
+ const auto& strUA = UserAgent();
+ args[3] = strUA.c_str(); // Test with "OSTestUserAgent"
+ if (!xmlrpc->execute("LogIn", args, result)) {
+ return SR_FAILED;
+ }
+
+ if (result["status"].getType() == XmlRpcValue::Type::TypeString) {
+ if (result["status"] == std::string("200 OK")) {
+ token = result["token"];
+ } else if (result["status"] == std::string("401 Unauthorized")) {
+ // Notify user that User/Pass provided are invalid.
+ CString msg;
+ msg.Format(IDS_SUB_CREDENTIALS_ERROR, Name().c_str(), UserName().c_str());
+ AfxMessageBox(msg, MB_ICONERROR | MB_OK);
+ }
+ }
+ }
+
+ LOG(LOG_BOTH, sUserName.c_str(), token.valid() ? (LPCSTR)token : "failed");
+ return token.valid() ? SR_SUCCEEDED : SR_FAILED;
+}
+
+SRESULT OpenSubtitles::LogOut()
+{
+ if (xmlrpc && token.valid()) {
+ XmlRpcValue args, result;
+ args[0] = token;
+ VERIFY(xmlrpc->execute("LogOut", args, result));
+ token.clear();
+ }
+ m_nLoggedIn = SPL_UNDEFINED;
+
+ LOG(LOG_NONE);
+ return SR_SUCCEEDED;
+}
+
+SRESULT OpenSubtitles::Hash(SubtitlesInfo& pFileInfo)
+{
+ pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
+ LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
+ return SR_SUCCEEDED;
+}
+
+SRESULT OpenSubtitles::Search(const SubtitlesInfo& pFileInfo)
+{
+ const auto languages = LanguagesISO6392();
+ XmlRpcValue args, result;
+ args[0] = token;
+ auto& movieInfo = args[1][0];
+ movieInfo["sublanguageid"] = !languages.empty() ? JoinContainer(languages, ",") : "all";
+ movieInfo["moviehash"] = pFileInfo.fileHash;
+ movieInfo["moviebytesize"] = std::to_string(pFileInfo.fileSize);
+ //args[1][1]["sublanguageid"] = !languages.empty() ? languages : "all";
+ //args[1][1]["tag"] = pFileInfo.fileName + "." + pFileInfo.fileExtension;
+ args[2]["limit"] = 500;
+
+ LOG(LOG_INPUT,
+ StringFormat("{ sublanguageid=\"%s\", moviehash=\"%s\", moviebytesize=\"%s\", limit=%d }",
+ (LPCSTR)movieInfo["sublanguageid"],
+ (LPCSTR)movieInfo["moviehash"],
+ (LPCSTR)movieInfo["moviebytesize"],
+ (int)args[2]["limit"]).c_str());
+
+ if (!xmlrpc->execute("SearchSubtitles", args, result)) {
+ return SR_FAILED;
+ }
+
+ if (result["data"].getType() != XmlRpcValue::Type::TypeArray) {
+ return SR_FAILED;
+ }
+
+ int nCount = result["data"].size();
+ for (int i = 0; i < nCount; ++i) {
+ CheckAbortAndReturn();
+ XmlRpcValue& data(result["data"][i]);
+ SubtitlesInfo pSubtitlesInfo;
+ pSubtitlesInfo.id = (const char*)data["IDSubtitleFile"];
+ pSubtitlesInfo.discNumber = data["SubActualCD"];
+ pSubtitlesInfo.discCount = data["SubSumCD"];
+ pSubtitlesInfo.fileExtension = (const char*)data["SubFormat"];
+ pSubtitlesInfo.languageCode = (const char*)data["ISO639"]; //"SubLanguageID"
+ pSubtitlesInfo.languageName = (const char*)data["LanguageName"];
+ pSubtitlesInfo.downloadCount = data["SubDownloadsCnt"];
+
+ pSubtitlesInfo.fileName = (const char*)data["SubFileName"];
+ regexResult results;
+ stringMatch("\"([^\"]+)\" (.+)", (const char*)data["MovieName"], results);
+ if (!results.empty()) {
+ pSubtitlesInfo.title = results[0];
+ pSubtitlesInfo.title2 = results[1];
+ } else {
+ pSubtitlesInfo.title = (const char*)data["MovieName"];
+ }
+ pSubtitlesInfo.year = (int)data["MovieYear"] == 0 ? -1 : (int)data["MovieYear"];
+ pSubtitlesInfo.seasonNumber = (int)data["SeriesSeason"] == 0 ? -1 : (int)data["SeriesSeason"];
+ pSubtitlesInfo.episodeNumber = (int)data["SeriesEpisode"] == 0 ? -1 : (int)data["SeriesEpisode"];
+ pSubtitlesInfo.hearingImpaired = data["SubHearingImpaired"];
+ pSubtitlesInfo.url = (const char*)data["SubtitlesLink"];
+ pSubtitlesInfo.releaseNames.emplace_back((const char*)data["MovieReleaseName"]);
+ pSubtitlesInfo.imdbid = (const char*)data["IDMovieImdb"];
+ pSubtitlesInfo.corrected = (int)data["SubBad"] ? -1 : 0;
+ Set(pSubtitlesInfo);
+ }
+ return SR_SUCCEEDED;
+}
+
+SRESULT OpenSubtitles::Download(SubtitlesInfo& pSubtitlesInfo)
+{
+ XmlRpcValue args, result;
+ args[0] = token;
+ args[1][0] = pSubtitlesInfo.id;
+ if (!xmlrpc->execute("DownloadSubtitles", args, result)) {
+ return SR_FAILED;
+ }
+
+ LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
+
+ if (result["data"].getType() != XmlRpcValue::Type::TypeArray) {
+ return SR_FAILED;
+ }
+
+ pSubtitlesInfo.fileContents = Base64::decode(std::string(result["data"][0]["data"]));
+ return SR_SUCCEEDED;
+}
+
+SRESULT OpenSubtitles::Upload(const SubtitlesInfo& pSubtitlesInfo)
+{
+ XmlRpcValue args, result;
+ args[0] = token;
+
+ //TODO: Ask how to obtain commented values !!!
+ args[1]["cd1"]["subhash"] = StringToHash(pSubtitlesInfo.fileContents, CALG_MD5);
+ args[1]["cd1"]["subfilename"] = pSubtitlesInfo.fileName + ".srt";
+ args[1]["cd1"]["moviehash"] = pSubtitlesInfo.fileHash;
+ args[1]["cd1"]["moviebytesize"] = std::to_string(pSubtitlesInfo.fileSize);
+ //args[1]["cd1"]["movietimems"];
+ //args[1]["cd1"]["movieframes"];
+ //args[1]["cd1"]["moviefps"];
+ args[1]["cd1"]["moviefilename"] = pSubtitlesInfo.fileName + "." + pSubtitlesInfo.fileExtension;
+
+ CheckAbortAndReturn();
+ if (!xmlrpc->execute("TryUploadSubtitles", args, result)) {
+ return SR_FAILED;
+ }
+ CheckAbortAndReturn();
+
+ if ((int)result["alreadyindb"] == 1) {
+ return SR_EXISTS;
+ } else if ((int)result["alreadyindb"] == 0) {
+ // We need imdbid to proceed
+ if (result["data"].getType() == XmlRpcValue::Type::TypeArray) {
+ args[1]["baseinfo"]["idmovieimdb"] = result["data"][0]["IDMovieImdb"];
+ } else if (!pSubtitlesInfo.imdbid.empty()) {
+ args[1]["baseinfo"]["idmovieimdb"] = pSubtitlesInfo.imdbid;
+ } else {
+ std::string title(StringReplace(pSubtitlesInfo.title, "and", "&"));
+ if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
+ XmlRpcValue _args, _result;
+ _args[0] = token;
+ _args[1][0] = pSubtitlesInfo.fileHash;
+ if (!xmlrpc->execute("CheckMovieHash", _args, _result)) {
+ return SR_FAILED;
+ }
+
+ if (_result["data"].getType() == XmlRpcValue::Type::TypeStruct) {
+ //regexResults results;
+ //stringMatch("\"(.+)\" (.+)", (const char*)data["MovieName"], results);
+ //if (!results.empty()) {
+ // pSubtitlesInfo.title = results[0][0];
+ // pSubtitlesInfo.title2 = results[0][1];
+ //} else {
+ // pSubtitlesInfo.title = (const char*)data["MovieName"];
+ //}
+ regexResults results;
+ stringMatch("\"(.+)\" .+|(.+)", StringReplace((const char*)_result["data"][pSubtitlesInfo.fileHash]["MovieName"], "and", "&"), results);
+ std::string _title(results[0][0] + results[0][1]);
+
+ if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(_result["data"][pSubtitlesInfo.fileHash]["MovieYear"])))*/) {
+ args[1]["baseinfo"]["idmovieimdb"] = _result["data"][pSubtitlesInfo.fileHash]["MovieImdbID"]; //imdbid
+ }
+ }
+ }
+
+ if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
+ XmlRpcValue _args, _result;
+ _args[0] = token;
+ _args[1][0] = pSubtitlesInfo.fileHash;
+ if (!xmlrpc->execute("CheckMovieHash2", _args, _result)) {
+ return SR_FAILED;
+ }
+
+ if (_result["data"].getType() == XmlRpcValue::Type::TypeArray) {
+ int nCount = _result["data"][pSubtitlesInfo.fileHash].size();
+ for (int i = 0; i < nCount; ++i) {
+ regexResults results;
+ stringMatch("\"(.+)\" .+|(.+)", StringReplace((const char*)_result["data"][pSubtitlesInfo.fileHash][i]["MovieName"], "and", "&"), results);
+ std::string _title(results[0][0] + results[0][1]);
+
+ if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(_result["data"][pSubtitlesInfo.fileHash][i]["MovieYear"])))*/) {
+ args[1]["baseinfo"]["idmovieimdb"] = _result["data"][pSubtitlesInfo.fileHash][i]["MovieImdbID"]; //imdbid
+ break;
+ }
+ }
+ }
+ }
+
+ if (!args[1]["baseinfo"]["idmovieimdb"].valid()) {
+ XmlRpcValue _args, _result;
+ _args[0] = token;
+ _args[1] = title;
+ if (!xmlrpc->execute("SearchMoviesOnIMDB", _args, _result)) {
+ return SR_FAILED;
+ }
+ if (_result["data"].getType() == XmlRpcValue::Type::TypeArray) {
+ int nCount = _result["data"].size();
+ for (int i = 0; i < nCount; ++i) {
+ regexResults results;
+ stringMatch("(.+) [(](\\d{4})[)]", StringReplace((const char*)_result["data"][i]["title"], "and", "&"), results);
+ if (results.size() == 1) {
+ std::string _title(results[0][0]);
+
+ if (_stricmp(title.c_str(), _title.c_str()) == 0 /*&& (pSubtitlesInfo.year == -1 || (pSubtitlesInfo.year != -1 && pSubtitlesInfo.year == atoi(results[0][1].c_str())))*/) {
+ args[1]["baseinfo"]["idmovieimdb"] = _result["data"][i]["id"]; //imdbid
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (args[1]["baseinfo"]["idmovieimdb"].valid()) {
+ XmlRpcValue _args, _result;
+ _args[0] = token;
+ _args[1][0]["moviehash"] = pSubtitlesInfo.fileHash;
+ _args[1][0]["moviebytesize"] = (int)pSubtitlesInfo.fileSize;
+ _args[1][0]["imdbid"] = args[1]["baseinfo"]["idmovieimdb"];
+ //_args[1][0]["movietimems"];
+ //_args[1][0]["moviefps"];
+ _args[1][0]["moviefilename"] = pSubtitlesInfo.fileName + "." + pSubtitlesInfo.fileExtension;
+ if (!xmlrpc->execute("InsertMovieHash", _args, _result)) {
+ return SR_FAILED;
+ }
+ // REsult value is irrelevant
+ _result["data"]["accepted_moviehashes"];
+
+
+ //args[1]["baseinfo"]["moviereleasename"];
+ //args[1]["baseinfo"]["movieaka"];
+ //args[1]["baseinfo"]["sublanguageid"];
+ //args[1]["baseinfo"]["subauthorcomment"];
+ if (pSubtitlesInfo.hearingImpaired != -1) {
+ args[1]["baseinfo"]["hearingimpaired"] = pSubtitlesInfo.hearingImpaired;
+ }
+ //args[1]["baseinfo"]["highdefinition"];
+ //args[1]["baseinfo"]["automatictranslation"];
+
+ args[1]["cd1"]["subcontent"] = Base64::encode(StringGzipCompress(pSubtitlesInfo.fileContents));
+
+ if (!xmlrpc->execute("UploadSubtitles", args, result)) {
+ return SR_FAILED;
+ }
+ LOG(LOG_OUTPUT, (LPCSTR)result["data"]);
+
+ return SR_SUCCEEDED;
+ }
+ }
+ return SR_FAILED;
+}
+
+const std::set<std::string>& OpenSubtitles::Languages() const
+{
+ static std::once_flag initialized;
+ static std::set<std::string> result;
+
+ try {
+ std::call_once(initialized, [this]() {
+ if (!CheckInternetConnection()) {
+ throw LanguageDownloadException("No internet connection.");
+ }
+ XmlRpcValue args, res;
+ args = "en";
+ if (!xmlrpc->execute("GetSubLanguages", args, res)) {
+ throw LanguageDownloadException("Failed to execute xmlrpc command.");
+ }
+ if (res["data"].getType() != XmlRpcValue::Type::TypeArray) {
+ throw LanguageDownloadException("Response is not an array.");
+ }
+
+ auto& data = res["data"];
+ int count = data.size();
+ for (int i = 0; i < count; ++i) {
+#ifdef _DEBUG
+ // Validate if language code conversion is in sync with OpenSubtitles database.
+ std::string subLanguageID = data[i]["SubLanguageID"];
+ std::string ISO6391 = data[i]["ISO639"];
+ ASSERT(!ISO6391.empty());
+ ASSERT(!subLanguageID.empty());
+ ASSERT(ISOLang::ISO6391To6392(ISO6391.c_str()) == subLanguageID.c_str());
+ ASSERT(ISOLang::ISO6392To6391(subLanguageID.c_str()) == ISO6391.c_str());
+ //std::string languageName = data[i]["LanguageName"];
+ //ASSERT(ISO639XToLanguage(ISO6391.c_str()) == languageName.c_str());
+ //ASSERT(ISO639XToLanguage(subLanguageID.c_str()) == languageName.c_str());
+#endif
+ result.emplace(data[i]["ISO639"]);
+ }
+ });
+ } catch (const LanguageDownloadException& e) {
+ UNREFERENCED_PARAMETER(e);
+ LOG(LOG_ERROR, e.what());
+ }
+ return result;
+}
+
+bool OpenSubtitles::NeedLogin()
+{
+ // return true to call Login() or false to skip Login()
+ if (!token.valid()) {
+ return true;
+ }
+
+ XmlRpcValue args, result;
+ args[0] = token;
+ if (!xmlrpc->execute("NoOperation", args, result)) {
+ return false;
+ }
+
+ if ((result["status"].getType() == XmlRpcValue::Type::TypeString) && (result["status"] == std::string("200 OK"))) {
+ return false;
+ }
+
+ return true;
+}
+
+
+/******************************************************************************
+** SubDB
+******************************************************************************/
+
+SRESULT SubDB::Hash(SubtitlesInfo& pFileInfo)
+{
+ std::vector<BYTE> buffer(2 * PROBE_SIZE);
+ if (pFileInfo.pAsyncReader) {
+ UINT64 position = 0;
+ pFileInfo.pAsyncReader->SyncRead(position, PROBE_SIZE, (BYTE*)&buffer[0]);
+ position = std::max((UINT64)0, (UINT64)(pFileInfo.fileSize - PROBE_SIZE));
+ pFileInfo.pAsyncReader->SyncRead(position, PROBE_SIZE, (BYTE*)&buffer[PROBE_SIZE]);
+ } else {
+ CFile file;
+ CFileException fileException;
+ if (file.Open(CString(pFileInfo.filePath.c_str()),
+ CFile::modeRead | CFile::osSequentialScan | CFile::shareDenyNone | CFile::typeBinary,
+ &fileException)) {
+ file.Read(&buffer[0], PROBE_SIZE);
+ file.Seek(std::max((UINT64)0, (UINT64)(pFileInfo.fileSize - PROBE_SIZE)), CFile::begin);
+ file.Read(&buffer[PROBE_SIZE], PROBE_SIZE);
+ }
+ }
+ pFileInfo.fileHash = StringToHash(std::string((char*)&buffer[0], buffer.size()), CALG_MD5);
+ LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
+ return SR_SUCCEEDED;
+}
+
+SRESULT SubDB::Search(const SubtitlesInfo& pFileInfo)
+{
+ SRESULT searchResult = SR_UNDEFINED;
+ std::string url(StringFormat("http://api.thesubdb.com/?action=search&hash=%s", pFileInfo.fileHash.c_str()));
+ LOG(LOG_INPUT, url.c_str());
+
+ std::string data;
+ searchResult = DownloadInternal(url, "", data);
+
+ if (!data.empty()) {
+ for (const auto& iter : StringTokenize(data, ",")) {
+ CheckAbortAndReturn();
+ if (CheckLanguage(iter)) {
+ SubtitlesInfo pSubtitlesInfo;
+ pSubtitlesInfo.id = pFileInfo.fileHash;
+ pSubtitlesInfo.fileExtension = "srt";
+ pSubtitlesInfo.fileName = pFileInfo.fileName + GUESSED_NAME_POSTFIX;
+ pSubtitlesInfo.languageCode = iter;
+ pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(iter.c_str()));
+ pSubtitlesInfo.discNumber = 1;
+ pSubtitlesInfo.discCount = 1;
+ pSubtitlesInfo.title = pFileInfo.title;
+ Set(pSubtitlesInfo);
+ }
+ }
+ }
+
+ return searchResult;
+}
+
+SRESULT SubDB::Download(SubtitlesInfo& pSubtitlesInfo)
+{
+ std::string url(StringFormat("http://api.thesubdb.com/?action=download&hash=%s&language=%s", pSubtitlesInfo.id.c_str(), pSubtitlesInfo.languageCode.c_str()));
+ LOG(LOG_INPUT, url.c_str());
+ return DownloadInternal(url, "", pSubtitlesInfo.fileContents);
+}
+
+SRESULT SubDB::Upload(const SubtitlesInfo& pSubtitlesInfo)
+{
+#define MULTIPART_BOUNDARY "xYzZY"
+ std::string url(StringFormat("http://api.thesubdb.com/?action=upload&hash=%s", pSubtitlesInfo.fileHash.c_str()));
+ stringMap headers({
+ { "User-Agent", UserAgent() },
+ { "Content-Type", "multipart/form-data; boundary=" MULTIPART_BOUNDARY },
+ });
+
+ std::string content, data;
+ content += StringFormat("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", MULTIPART_BOUNDARY, "hash", pSubtitlesInfo.fileHash.c_str());
+ content += StringFormat("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s.%s\"\r\nContent-Type: application/octet-stream\r\nContent-Transfer-Encoding: binary\r\n\r\n",
+ MULTIPART_BOUNDARY, "file", pSubtitlesInfo.fileHash.c_str(), "srt");
+ content += pSubtitlesInfo.fileContents;
+ content += StringFormat("\r\n--%s--\r\n", MULTIPART_BOUNDARY);
+
+ CheckAbortAndReturn();
+
+ DWORD dwStatusCode = NULL;
+ StringUpload(url, headers, content, data, FALSE, &dwStatusCode);
+
+ LOG(LOG_BOTH, url.c_str(), std::to_string(dwStatusCode).c_str());
+
+ switch (dwStatusCode) {
+ case 201:
+ return SR_SUCCEEDED; //'Uploaded': (HTTP/1.1 201 Created): If everything was OK, the HTTP status code 201 will be returned.
+ case 403:
+ return SR_EXISTS; //'Duplicated': (HTTP/1.1 403 Forbidden): If the subtitle file already exists in our database, the HTTP status code 403 will be returned.
+ case 400:
+ return SR_FAILED; //'Malformed': (HTTP/1.1 400 Bad Request): If the request was malformed, the HTTP status code 400 will be returned.
+ case 415:
+ return SR_FAILED; //'Invalid': (HTTP/1.1 415 Unsupported Media Type): If the subtitle file is not supported by our database, the HTTP status code 415 will be returned.
+ default:
+ return SR_UNDEFINED;
+ }
+}
+
+const std::set<std::string>& SubDB::Languages() const
+{
+ static std::once_flag initialized;
+ static std::set<std::string> result;
+ try {
+ std::call_once(initialized, [this]() {
+ if (!CheckInternetConnection()) {
+ throw LanguageDownloadException("No internet connection.");
+ }
+ std::string data;
+ if (DownloadInternal("http://api.thesubdb.com/?action=languages", "", data) != SR_SUCCEEDED) {
+ throw LanguageDownloadException("Failed to download language list.");
+ }
+ for (const auto& str : StringTokenize(data, ",")) {
+ result.emplace(str);
+ }
+ });
+ } catch (const LanguageDownloadException& e) {
+ UNREFERENCED_PARAMETER(e);
+ LOG(LOG_ERROR, e.what());
+ }
+ return result;
+}
+
+/******************************************************************************
+** podnapisi
+******************************************************************************/
+
+SRESULT podnapisi::Login(const std::string& sUserName, const std::string& sPassword)
+{
+ //TODO: implement
+ return SR_UNDEFINED;
+}
+
+/*
+UPDATED
+https://www.podnapisi.net/forum/viewtopic.php?f=62&t=26164#p212652
+RESULTS ------------------------------------------------
+"/sXML/1/" //Reply in XML format
+"/page//" //Return nth page of results
+SEARCH -------------------------------------------------
+"/sT/1/" //Type: -1=all, 0=movies, 1=series, don't specify for auto detection
+"/sAKA/1/" //Include movie title aliases
+"/sM//" //Movie id from www.omdb.si
+"/sK//" //Title url encoded text
+"/sY//" //Year number
+"/sTS//" //Season number
+"/sTE//" //Episode number
+"/sR//" //Release name url encoded text
+"/sJ/0/" //Languages (old integer IDs), comma delimited, 0=all
+"/sL/en/" //Languages in ISO ISO codes (exception are sr-latn and pt-br), comma delimited
+"/sEH//" //Exact hash match (OSH)
+"/sMH//" //Movie hash (OSH)
+SEARCH ADDITIONAL --------------------------------------
+"/sFT/0/" //Subtitles Format: 0=all, 1=MicroDVD, 2=SAMI, 3=SSA, 4=SubRip, 5=SubViewer 2.0, 6=SubViewer, 7=MPSub, 8=Advanced SSA, 9=DVDSubtitle, 10=TMPlayer, 11=MPlayer2
+"/sA/0/" //Search subtitles by user id, 0=all
+"/sI//" //Search subtitles by subtitle id
+SORTING ------------------------------------------------
+"/sS//" //Sorting field: movie, year, fps, language, downloads, cds, username, time, rating
+"/sO//" //Soring order: asc, desc
+FILTERS ------------------------------------------------
+"/sOE/1/" //Subtitles for extended edition only
+"/sOD/1/" //Subtitles suitable for DVD only
+"/sOH/1/" //Subtitles for high-definition video only
+"/sOI/1/" //Subtitles for hearing impaired only
+"/sOT/1/" //Technically correct only
+"/sOL/1/" //Grammatically correct only
+"/sOA/1/" //Author subtitles only
+"/sOCS/1/" //Only subtitles for a complete season
+UNKNOWN ------------------------------------------------
+"/sH//" //Search subtitles by video file hash ??? (not working for me)
+*/
+
+SRESULT podnapisi::Search(const SubtitlesInfo& pFileInfo)
+{
+ SRESULT searchResult = SR_UNDEFINED;
+ int page = 1, pages = 1, results = 0;
+ do {
+ CheckAbortAndReturn();
+
+ const auto languages = LanguagesISO6391();
+ std::string search(pFileInfo.title);
+ if (!pFileInfo.country.empty()) {
+ search += " " + pFileInfo.country;
+ }
+ search = std::regex_replace(search, std::regex(" and | *[!?&':] *", RegexFlags), " ");
+
+ std::string url("https://www.podnapisi.net/ppodnapisi/search");
+ url += "?sXML=1";
+ url += "&sAKA=1";
+ url += (!search.empty() ? "&sK=" + UrlEncode(search.c_str()) : "");
+ url += (pFileInfo.year != -1 ? "&sY=" + std::to_string(pFileInfo.year) : "");
+ url += (pFileInfo.seasonNumber != -1 ? "&sTS=" + std::to_string(pFileInfo.seasonNumber) : "");
+ url += (pFileInfo.episodeNumber != -1 ? "&sTE=" + std::to_string(pFileInfo.episodeNumber) : "");
+ url += "&sMH=" + pFileInfo.fileHash;
+ //url += "&sR=" + UrlEncode(pFileInfo.fileName.c_str());
+ url += (!languages.empty() ? "&sL=" + JoinContainer(languages, ",") : "");
+ url += "&page=" + std::to_string(page);
+ LOG(LOG_INPUT, url.c_str());
+
+ std::string data;
+ searchResult = DownloadInternal(url, "", data);
+
+ using namespace tinyxml2;
+
+ tinyxml2::XMLDocument dxml;
+ if (dxml.Parse(data.c_str()) == XML_SUCCESS) {
+
+ auto GetChildElementText = [&](XMLElement * pElement, const char* value) -> std::string {
+ std::string str;
+ XMLElement* pChildElement = pElement->FirstChildElement(value);
+ if (pChildElement != nullptr)
+ {
+ auto pText = pChildElement->GetText();
+ if (pText != nullptr) {
+ str = pText;
+ }
+ }
+ return str;
+ };
+
+ XMLElement* pRootElmt = dxml.FirstChildElement("results");
+ if (pRootElmt) {
+ XMLElement* pPaginationElmt = pRootElmt->FirstChildElement("pagination");
+ if (pPaginationElmt) {
+ page = atoi(GetChildElementText(pPaginationElmt, "current").c_str());
+ pages = atoi(GetChildElementText(pPaginationElmt, "count").c_str());
+ results = atoi(GetChildElementText(pPaginationElmt, "results").c_str());
+ }
+ // 30 results per page
+ if (page > 1) {
+ return SR_TOOMANY;
+ }
+
+ if (results > 0) {
+ XMLElement* pSubtitleElmt = pRootElmt->FirstChildElement("subtitle");
+
+ while (pSubtitleElmt) {
+ CheckAbortAndReturn();
+
+ SubtitlesInfo pSubtitlesInfo;
+
+ pSubtitlesInfo.id = GetChildElementText(pSubtitleElmt, "pid");
+ pSubtitlesInfo.title = HtmlSpecialCharsDecode(GetChildElementText(pSubtitleElmt, "title").c_str());
+
+ std::string year = GetChildElementText(pSubtitleElmt, "year");
+ pSubtitlesInfo.year = year.empty() ? -1 : atoi(year.c_str());
+
+ pSubtitlesInfo.url = GetChildElementText(pSubtitleElmt, "url");
+ std::string format = GetChildElementText(pSubtitleElmt, "format");
+ pSubtitlesInfo.fileExtension = (format == "SubRip" || format == "N/A") ? "srt" : format;
+
+ pSubtitlesInfo.languageCode = podnapisi_languages[atoi(GetChildElementText(pSubtitleElmt, "languageId").c_str())].code;
+ pSubtitlesInfo.languageName = GetChildElementText(pSubtitleElmt, "languageName");
+ pSubtitlesInfo.seasonNumber = atoi(GetChildElementText(pSubtitleElmt, "tvSeason").c_str());
+ pSubtitlesInfo.episodeNumber = atoi(GetChildElementText(pSubtitleElmt, "tvEpisode").c_str());
+ pSubtitlesInfo.discCount = atoi(GetChildElementText(pSubtitleElmt, "cds").c_str());
+ pSubtitlesInfo.discNumber = pSubtitlesInfo.discCount;
+
+ std::string flags = GetChildElementText(pSubtitleElmt, "flags");
+ pSubtitlesInfo.hearingImpaired = (flags.find("n") != std::string::npos) ? TRUE : FALSE;
+ pSubtitlesInfo.corrected = (flags.find("r") != std::string::npos) ? -1 : 0;
+ pSubtitlesInfo.downloadCount = atoi(GetChildElementText(pSubtitleElmt, "downloads").c_str());
+ pSubtitlesInfo.imdbid = GetChildElementText(pSubtitleElmt, "movieId");
+ pSubtitlesInfo.frameRate = atof(GetChildElementText(pSubtitleElmt, "fps").c_str());
+
+ XMLElement* pReleasesElem = pSubtitleElmt->FirstChildElement("releases");
+ if (pReleasesElem) {
+ XMLElement* pReleaseElem = pReleasesElem->FirstChildElement("release");
+
+ while (pReleaseElem) {
+ auto pText = pReleaseElem->GetText();
+
+ if (!pText) {
+ continue;
+ }
+
+ pSubtitlesInfo.releaseNames.emplace_back(pText);
+
+ if (pSubtitlesInfo.fileName.empty() || pFileInfo.fileName.find(pText) != std::string::npos) {
+ pSubtitlesInfo.fileName = pText;
+ pSubtitlesInfo.fileName += "." + pSubtitlesInfo.fileExtension;
+ }
+ pReleaseElem = pReleaseElem->NextSiblingElement();
+ }
+ }
+
+ if (pSubtitlesInfo.fileName.empty()) {
+ std::string str = pSubtitlesInfo.title;
+ if (!year.empty()) {
+ str += " " + year;
+ }
+ if (pSubtitlesInfo.seasonNumber > 0) {
+ str += StringFormat(" S%02d", pSubtitlesInfo.seasonNumber);
+ }
+ if (pSubtitlesInfo.episodeNumber > 0) {
+ str += StringFormat("%sE%02d", (pSubtitlesInfo.seasonNumber > 0) ? "" : " ", pSubtitlesInfo.episodeNumber);
+ }
+ str += GUESSED_NAME_POSTFIX;
+ pSubtitlesInfo.fileName = str;
+ }
+
+ Set(pSubtitlesInfo);
+ pSubtitleElmt = pSubtitleElmt->NextSiblingElement();
+ }
+ }
+ }
+ }
+ } while (page++ < pages);
+
+ return searchResult;
+}
+
+SRESULT podnapisi::Hash(SubtitlesInfo& pFileInfo)
+{
+ pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
+ LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
+ return SR_SUCCEEDED;
+}
+
+SRESULT podnapisi::Download(SubtitlesInfo& pSubtitlesInfo)
+{
+ std::string url = StringFormat("https://www.podnapisi.net/subtitles/%s/download", pSubtitlesInfo.id.c_str());
+ LOG(LOG_INPUT, url.c_str());
+ return DownloadInternal(url, "", pSubtitlesInfo.fileContents);
+}
+
+const std::set<std::string>& podnapisi::Languages() const
+{
+ static std::once_flag initialized;
+ static std::set<std::string> result;
+
+ std::call_once(initialized, [this]() {
+ for (const auto& iter : podnapisi_languages) {
+ if (strlen(iter.code)) {
+ result.emplace(iter.code);
+ }
+ }
+ });
+ return result;
+}
+
+#ifdef MPCHC_DISABLED_SUBTITLES_PROVIDER
+/******************************************************************************
+** titlovi
+******************************************************************************/
+
+/*
+ x-dev_api_id=
+ uiculture=hr,rs,si,ba,en,mk
+ language=hr,rs,sr,si,ba,en,mk
+ keyword=
+ year=
+ mt=numeric value representing type of subtitle (Movie / TV show / documentary 1, 2, 3)
+ season=numeric value representing season
+ episode=numeric value representing season episode
+ forcefilename=true (default is false) return direct download link
+*/
+
+SRESULT titlovi::Search(const SubtitlesInfo& pFileInfo)
+{
+ // Need to filter not supported languages, because their API returns .hr language otherwise.
+ auto selectedLanguages = LanguagesISO6391();
+ bool userSelectedLanguage = !selectedLanguages.empty();
+ const auto languagesIntersection = GetLanguagesIntersection(std::move(selectedLanguages));
+ std::string KEY = "WC1ERVYtREVTS1RPUF9maWUyYS1hMVJzYS1hSHc0UA==";
+ std::string url(StringFormat("http://api.titlovi.com/xml_get_api.ashx?x-dev_api_id=%s&uiculture=en&forcefilename=true", Base64::decode(KEY).c_str()));
+ url += "&mt=" + (pFileInfo.seasonNumber != -1 ? std::to_string(2) : std::to_string(1));
+ url += "&keyword=" + UrlEncode(pFileInfo.title.c_str());
+ url += (pFileInfo.seasonNumber != -1 ? "&season=" + std::to_string(pFileInfo.seasonNumber) : "");
+ url += (pFileInfo.episodeNumber != -1 ? "&episode=" + std::to_string(pFileInfo.episodeNumber) : "");
+ url += (pFileInfo.year != -1 ? "&year=" + std::to_string(pFileInfo.year) : "");
+ url += (userSelectedLanguage ? "&language=" + JoinContainer(languagesIntersection, ",") : "");
+ LOG(LOG_INPUT, url.c_str());
+
+ std::string data;
+ SRESULT searchResult = DownloadInternal(url, "", data);
+
+ tinyxml2::XMLDocument dxml;
+ if (dxml.Parse(data.c_str()) == tinyxml2::XMLError::XML_SUCCESS) {
+
+ auto GetChildElementText = [&](tinyxml2::XMLElement * pElement, const char* value) -> std::string {
+ std::string str;
+ auto pChildElement = pElement->FirstChildElement(value);
+
+ if (pChildElement != nullptr)
+ {
+ auto pText = pChildElement->GetText();
+ if (pText != nullptr) {
+ str = pText;
+ }
+ }
+ return str;
+ };
+
+ auto pRootElmt = dxml.FirstChildElement("subtitles");
+
+ if (pRootElmt) {
+ int num = pRootElmt->IntAttribute("resultsCount");
+
+ if (num > 0/* && num < 50*/) {
+ auto pSubtitleElmt = pRootElmt->FirstChildElement();
+
+ while (pSubtitleElmt) {
+ SubtitlesInfo pSubtitlesInfo;
+
+ pSubtitlesInfo.title = GetChildElementText(pSubtitleElmt, "title");
+ pSubtitlesInfo.languageCode = GetChildElementText(pSubtitleElmt, "language");
+
+ for (const auto& language : titlovi_languages) {
+ if (pSubtitlesInfo.languageCode == language.code) {
+ pSubtitlesInfo.languageCode = language.name;
+ }
+ }
+
+ pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(pSubtitlesInfo.languageCode.c_str()));
+ auto releaseNames = StringTokenize(GetChildElementText(pSubtitleElmt, "release"), "/");
+ pSubtitlesInfo.releaseNames = { releaseNames.begin(), releaseNames.end() };
+ pSubtitlesInfo.imdbid = GetChildElementText(pSubtitleElmt, "imdbId");
+ pSubtitlesInfo.frameRate = atof(GetChildElementText(pSubtitleElmt, "fps").c_str());
+ pSubtitlesInfo.year = atoi(GetChildElementText(pSubtitleElmt, "year").c_str());
+ pSubtitlesInfo.discNumber = atoi(GetChildElementText(pSubtitleElmt, "cd").c_str());
+ pSubtitlesInfo.discCount = pSubtitlesInfo.discNumber;
+ pSubtitlesInfo.downloadCount = atoi(GetChildElementText(pSubtitleElmt, "downloads").c_str());
+
+ auto pSubtitleChildElmt = pSubtitleElmt->FirstChildElement("urls");
+ if (pSubtitleChildElmt) {
+ auto pURLElement = pSubtitleChildElmt->FirstChildElement("url");
+ while (pURLElement) {
+ if (pURLElement->Attribute("what", "download")) {
+ pSubtitlesInfo.url = pURLElement->GetText();
+ }
+ if (pURLElement->Attribute("what", "direct")) {
+ pSubtitlesInfo.id = pURLElement->GetText();
+ }
+ pURLElement = pURLElement->NextSiblingElement();
+ }
+ }
+
+ if ((pSubtitleChildElmt = pSubtitleElmt->FirstChildElement("TVShow")) != nullptr) {
+ pSubtitlesInfo.seasonNumber = atoi(GetChildElementText(pSubtitleChildElmt, "season").c_str());
+ pSubtitlesInfo.episodeNumber = atoi(GetChildElementText(pSubtitleChildElmt, "episode").c_str());
+ }
+
+ pSubtitlesInfo.fileName = pSubtitlesInfo.title + " " + std::to_string(pSubtitlesInfo.year);
+ if (pSubtitlesInfo.seasonNumber > 0) {
+ pSubtitlesInfo.fileName += StringFormat(" S%02d", pSubtitlesInfo.seasonNumber);
+ }
+ if (pSubtitlesInfo.episodeNumber > 0) {
+ pSubtitlesInfo.fileName += StringFormat("%sE%02d", (pSubtitlesInfo.seasonNumber > 0) ? "" : " ", pSubtitlesInfo.episodeNumber);
+ }
+
+ bool found = false;
+ for (const auto& str : pSubtitlesInfo.releaseNames) {
+ if (pFileInfo.fileName.find(str) != std::string::npos) {
+ pSubtitlesInfo.fileName += " " + str;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && !pSubtitlesInfo.releaseNames.empty()) {
+ pSubtitlesInfo.fileName += " " + pSubtitlesInfo.releaseNames.front();
+ }
+ pSubtitlesInfo.fileName += GUESSED_NAME_POSTFIX;
+
+ Set(pSubtitlesInfo);
+ pSubtitleElmt = pSubtitleElmt->NextSiblingElement();
+ }
+ }
+ }
+ }
+ return searchResult;
+}
+
+SRESULT titlovi::Download(SubtitlesInfo& pSubtitlesInfo)
+{
+ LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
+ return DownloadInternal(pSubtitlesInfo.id, "", pSubtitlesInfo.fileContents);
+}
+
+const std::set<std::string>& titlovi::Languages() const
+{
+ static std::once_flag initialized;
+ static std::set<std::string> result;
+
+ std::call_once(initialized, [this]() {
+ for (const auto& iter : titlovi_languages) {
+ if (strlen(iter.name)) {
+ result.emplace(iter.name);
+ }
+ }
+ });
+ return result;
+}
+
+/******************************************************************************
+** ysubs
+******************************************************************************/
+
+SRESULT ysubs::Search(const SubtitlesInfo& pFileInfo)
+{
+ SRESULT searchResult = SR_UNDEFINED;
+ using namespace rapidjson;
+
+ if (pFileInfo.year && pFileInfo.seasonNumber == -1 && pFileInfo.episodeNumber == -1) {
+ std::string urlApi(StringFormat("https://yts.ag/api/v2/list_movies.json?query_term=%s", UrlEncode(pFileInfo.title.c_str())));
+ LOG(LOG_INPUT, urlApi.c_str());
+
+ std::string data;
+ searchResult = DownloadInternal(urlApi, "", data);
+
+ Document d;
+ if (d.ParseInsitu(&data[0]).HasParseError()) {
+ return SR_FAILED;
+ }
+
+ auto root = d.FindMember("data");
+ if (root != d.MemberEnd()) {
+ auto iter = root->value.FindMember("movies");
+ if ((iter != root->value.MemberEnd()) && (iter->value.IsArray())) {
+ std::set<std::string> imdb_ids;
+ for (auto elem = iter->value.Begin(); elem != iter->value.End(); ++elem) {
+ std::string imdb = elem->FindMember("imdb_code")->value.GetString();
+ if (imdb_ids.find(imdb) == imdb_ids.end()) {
+ imdb_ids.insert(imdb);
+
+ std::string urlSubs(StringFormat("http://api.ysubs.com/subs/%s", imdb.c_str()));
+ LOG(LOG_INPUT, urlSubs.c_str());
+
+ std::string data1;
+ searchResult = DownloadInternal(urlSubs, "", data1);
+ Document d1;
+ if (d1.ParseInsitu(&data1[0]).HasParseError()) {
+ return SR_FAILED;
+ }
+
+ auto iter1 = d1.FindMember("subs");
+ if (iter1 != d1.MemberEnd()) {
+ iter1 = iter1->value.FindMember(imdb.c_str());
+ if (iter1 != d1.MemberEnd()) {
+ for (auto elem1 = iter1->value.MemberBegin(); elem1 != iter1->value.MemberEnd(); ++elem1) {
+ std::string lang = elem1->name.GetString();
+ std::string lang_code;
+ for (const auto& language : ysubs_languages) {
+ if (lang == language.name) {
+ lang_code = language.code;
+ }
+ }
+ if (CheckLanguage(lang_code)) {
+ for (auto elem2 = elem1->value.Begin(); elem2 != elem1->value.End(); ++elem2) {
+ SubtitlesInfo pSubtitlesInfo;
+
+ pSubtitlesInfo.title = elem->FindMember("title")->value.GetString();
+ pSubtitlesInfo.languageCode = lang_code;
+ pSubtitlesInfo.languageName = UTF16To8(ISOLang::ISO639XToLanguage(pSubtitlesInfo.languageCode.c_str()));
+ pSubtitlesInfo.releaseNames.emplace_back("YIFY");
+ pSubtitlesInfo.imdbid = imdb;
+ pSubtitlesInfo.year = elem->FindMember("year")->value.GetInt();
+ pSubtitlesInfo.discNumber = 1;
+ pSubtitlesInfo.discCount = 1;
+
+ pSubtitlesInfo.url = "http://www.yifysubtitles.com/movie-imdb/" + imdb;
+ std::string str = elem2->FindMember("url")->value.GetString();
+ pSubtitlesInfo.id = "http://www.yifysubtitles.com" + str;
+ pSubtitlesInfo.hearingImpaired = elem2->FindMember("hi")->value.GetInt();
+ pSubtitlesInfo.corrected = elem2->FindMember("rating")->value.GetInt();
+
+ pSubtitlesInfo.fileName = pFileInfo.fileName;
+ pSubtitlesInfo.fileName += GUESSED_NAME_POSTFIX;
+
+ Set(pSubtitlesInfo);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return searchResult;
+}
+
+SRESULT ysubs::Download(SubtitlesInfo& pSubtitlesInfo)
+{
+ LOG(LOG_INPUT, pSubtitlesInfo.id.c_str());
+ return DownloadInternal(pSubtitlesInfo.id, "", pSubtitlesInfo.fileContents);
+}
+
+const std::set<std::string>& ysubs::Languages() const
+{
+ static std::once_flag initialized;
+ static std::set<std::string> result;
+
+ std::call_once(initialized, [this]() {
+ for (const auto& iter : ysubs_languages) {
+ if (strlen(iter.code)) {
+ result.emplace(iter.code);
+ }
+ }
+ });
+ return result;
+}
+#endif // MPCHC_DISABLED_SUBTITLES_PROVIDER
+
+/******************************************************************************
+** Napisy24
+******************************************************************************/
+
+SRESULT Napisy24::Search(const SubtitlesInfo& pFileInfo)
+{
+ stringMap headers({
+ { "User-Agent", UserAgent() },
+ { "Content-Type", "application/x-www-form-urlencoded" }
+ });
+ std::string data;
+ std::string url = "http://napisy24.pl/run/CheckSubAgent.php";
+ std::string content = "postAction=CheckSub";
+ content += "&ua=mpc-hc";
+ content += "&ap=mpc-hc";
+ content += "&fh=" + pFileInfo.fileHash;
+ content += "&fs=" + std::to_string(pFileInfo.fileSize);
+ content += "&fn=" + pFileInfo.fileName;
+
+ LOG(LOG_INPUT, std::string(url + "?" + content).c_str());
+ StringUpload(url, headers, content, data);
+
+ if (data.length() < 4) {
+ return SR_FAILED;
+ }
+
+ // Get status
+ std::string status = data.substr(0, 4);
+ if (status != "OK-2" && status != "OK-3") {
+ return SR_FAILED;
+ }
+ data.erase(0, 5);
+
+ size_t infoEnd = data.find("||");
+ if (infoEnd == std::string::npos) {
+ return SR_FAILED;
+ }
+
+ // Search already returns whole file
+ SubtitlesInfo subtitleInfo;
+ subtitleInfo.fileContents = data.substr(infoEnd + 2);
+ subtitleInfo.languageCode = "pl"; // API doesn't support other languages yet.
+
+ // Remove subtitle data
+ data.erase(infoEnd);
+
+ std::unordered_map<std::string, std::string> subtitleInfoMap;
+ std::istringstream stringStream(data);
+ std::string entry;
+ while (std::getline(stringStream, entry, '|')) {
+ auto delimPos = entry.find(':');
+ if (delimPos == std::string::npos) {
+ continue;
+ }
+ std::string key = entry.substr(0, delimPos);
+ if (entry.length() <= delimPos + 1) {
+ continue;
+ }
+ std::string value = entry.substr(delimPos + 1);
+ subtitleInfoMap[key] = value;
+ }
+
+ subtitleInfo.url = "http://napisy24.pl/komentarze?napisId=" + subtitleInfoMap["napisId"];
+ subtitleInfo.title = subtitleInfoMap["ftitle"];
+ subtitleInfo.imdbid = subtitleInfoMap["fimdb"];
+
+ auto it = subtitleInfoMap.find("fyear");
+ if (it != subtitleInfoMap.end()) {
+ subtitleInfo.year = std::stoi(it->second);
+ }
+
+ it = subtitleInfoMap.find("fps");
+ if (it != subtitleInfoMap.end()) {
+ subtitleInfo.frameRate = std::stod(it->second);
+ }
+
+ int hour, minute, second;
+ if (sscanf_s(subtitleInfoMap["time"].c_str(), "%02d:%02d:%02d", &hour, &minute, &second) == 3) {
+ subtitleInfo.lengthMs = ((hour * 60 + minute) * 60 + second) * 1000;
+ }
+
+ subtitleInfo.fileName = pFileInfo.fileName + "." + pFileInfo.fileExtension;
+ subtitleInfo.discNumber = 1;
+ subtitleInfo.discCount = 1;
+
+ Set(subtitleInfo);
+
+ return SR_SUCCEEDED;
+}
+
+SRESULT Napisy24::Hash(SubtitlesInfo& pFileInfo)
+{
+ pFileInfo.fileHash = StringFormat("%016I64x", GenerateOSHash(pFileInfo));
+ LOG(LOG_OUTPUT, pFileInfo.fileHash.c_str());
+ return SR_SUCCEEDED;
+}
+
+SRESULT Napisy24::Download(SubtitlesInfo& subtitlesInfo)
+{
+ LOG(LOG_INPUT, subtitlesInfo.url.c_str());
+ return subtitlesInfo.fileContents.empty() ? SR_FAILED : SR_SUCCEEDED;
+}
+
+const std::set<std::string>& Napisy24::Languages() const
+{
+ static std::set<std::string> result = {"pl"};
+ return result;
+}
diff --git a/src/mpc-hc/SubtitlesProvider.h b/src/mpc-hc/SubtitlesProvider.h
index ebe9e628d..511bc9481 100644
--- a/src/mpc-hc/SubtitlesProvider.h
+++ b/src/mpc-hc/SubtitlesProvider.h
@@ -1,139 +1,143 @@
-/*
- * (C) 2016 see Authors.txt
- *
- * This file is part of MPC-HC.
- *
- * MPC-HC is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * MPC-HC is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#pragma once
-
-#include "SubtitlesProviders.h"
-#include "VersionInfo.h"
-
-#include "XmlRpc4Win/TimXmlRpc.h"
-
-#define DEFINE_SUBTITLESPROVIDER_BEGIN(P, U, I, F) \
-class P final : public SubtitlesProvider { \
-public: \
- P(SubtitlesProviders* pOwner) \
- : SubtitlesProvider(pOwner) { \
- Initialize(); \
- } \
- ~P() { Uninitialize(); } \
- P(P const&) = delete; \
- P& operator=(P const&) = delete; \
- static std::shared_ptr<P> Create(SubtitlesProviders* pOwner) { \
- return std::make_shared<P>(pOwner); \
- } \
-private: \
- virtual std::string Name() const override { return #P; } \
- virtual std::string Url() const override { return U; } \
- virtual const std::set<std::string>& Languages() const override; \
- virtual bool Flags(DWORD dwFlags) const override { return (dwFlags & (F)) == dwFlags; } \
- virtual int Icon() const override { return I; } \
-private: \
- virtual SRESULT Search(const SubtitlesInfo& pFileInfo) override; \
- virtual SRESULT Download(SubtitlesInfo& pSubtitlesInfo) override;
-#define DEFINE_SUBTITLESPROVIDER_END \
-};
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(OpenSubtitles, "http://www.opensubtitles.org", IDI_OPENSUBTITLES, SPF_LOGIN | SPF_HASH | SPF_UPLOAD)
-void Initialize() override;
-bool NeedLogin() override;
-SRESULT Login(const std::string& sUserName, const std::string& sPassword) override;
-SRESULT LogOut() override;
-SRESULT Hash(SubtitlesInfo& pFileInfo) override;
-SRESULT Upload(const SubtitlesInfo& pSubtitlesInfo) override;
-std::unique_ptr<XmlRpcClient> xmlrpc;
-XmlRpcValue token;
-DEFINE_SUBTITLESPROVIDER_END
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(SubDB, "http://api.thesubdb.com", IDI_SUBDB, SPF_HASH | SPF_UPLOAD)
-SRESULT Hash(SubtitlesInfo& pFileInfo) override;
-SRESULT Upload(const SubtitlesInfo& pSubtitlesInfo) override;
-std::string UserAgent() const override
-{
- return SubtitlesProvidersUtils::StringFormat("SubDB/1.0 (MPC-HC/%s; http://mpc-hc.org)",
- VersionInfo::GetVersionString());
-}
-DEFINE_SUBTITLESPROVIDER_END
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(podnapisi, "https://www.podnapisi.net", IDI_PODNAPISI, SPF_SEARCH)
-SRESULT Login(const std::string& sUserName, const std::string& sPassword) override;
-SRESULT Hash(SubtitlesInfo& pFileInfo) override;
-DEFINE_SUBTITLESPROVIDER_END
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(titlovi, "http://www.titlovi.com", IDI_TITLOVI, SPF_SEARCH)
-DEFINE_SUBTITLESPROVIDER_END
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(ysubs, "http://www.yifysubtitles.com", IDI_YSUBS, SPF_SEARCH)
-DEFINE_SUBTITLESPROVIDER_END
-
-DEFINE_SUBTITLESPROVIDER_BEGIN(Napisy24, "http://napisy24.pl/", IDI_N24, SPF_HASH | SPF_SEARCH)
-SRESULT Hash(SubtitlesInfo& pFileInfo) override;
-DEFINE_SUBTITLESPROVIDER_END
-
-static const struct {
- const char* code;
- const char* name;
-} podnapisi_languages[] = {
- { /* 0*/ "", "" }, { /* 1*/ "sl", "Slovenian" }, { /* 2*/ "en", "English" },
- { /* 3*/ "no", "Norwegian" }, { /* 4*/ "ko", "Korean" }, { /* 5*/ "de", "German" },
- { /* 6*/ "is", "Icelandic" }, { /* 7*/ "cs", "Czech" }, { /* 8*/ "fr", "French" },
- { /* 9*/ "it", "Italian" }, { /*10*/ "bs", "Bosnian" }, { /*11*/ "ja", "Japanese" },
- { /*12*/ "ar", "Arabic" }, { /*13*/ "ro", "Romanian" }, { /*14*/ "es", "Argentino" },
- { /*15*/ "hu", "Hungarian" }, { /*16*/ "el", "Greek" }, { /*17*/ "zh", "Chinese" },
- { /*18*/ "", "" }, { /*19*/ "lt", "Lithuanian" }, { /*20*/ "et", "Estonian" },
- { /*21*/ "lv", "Latvian" }, { /*22*/ "he", "Hebrew" }, { /*23*/ "nl", "Dutch" },
- { /*24*/ "da", "Danish" }, { /*25*/ "sv", "Swedish" }, { /*26*/ "pl", "Polish" },
- { /*27*/ "ru", "Russian" }, { /*28*/ "es", "Spanish" }, { /*29*/ "sq", "Albanian" },
- { /*30*/ "tr", "Turkish" }, { /*31*/ "fi", "Finnish" }, { /*32*/ "pt", "Portuguese" },
- { /*33*/ "bg", "Bulgarian" }, { /*34*/ "", "" }, { /*35*/ "mk", "Macedonian" },
- { /*36*/ "sr", "Serbian" }, { /*37*/ "sk", "Slovak" }, { /*38*/ "hr", "Croatian" },
- { /*39*/ "", "" }, { /*40*/ "zh", "Mandarin" }, { /*41*/ "", "" },
- { /*42*/ "hi", "Hindi" }, { /*43*/ "", "" }, { /*44*/ "th", "Thai" },
- { /*45*/ "", "" }, { /*46*/ "uk", "Ukrainian" }, { /*47*/ "sr", "Serbian (Cyrillic)" },
- { /*48*/ "pb", "Brazilian" }, { /*49*/ "ga", "Irish" }, { /*50*/ "be", "Belarus" },
- { /*51*/ "vi", "Vietnamese" }, { /*52*/ "fa", "Farsi" }, { /*53*/ "ca", "Catalan" },
- { /*54*/ "id", "Indonesian" }, { /*55*/ "ms", "Malay" }, { /*56*/ "si", "Sinhala" },
- { /*57*/ "kl", "Greenlandic" }, { /*58*/ "kk", "Kazakh" }, { /*59*/ "bn", "Bengali" },
-};
-
-static const struct {
- const char* code;
- const char* name;
-} titlovi_languages[] = {
- { "hr", "hr" }, { "sr", "sr" }, { "rs", "sr" }, { "si", "sl" }, { "ba", "bs" }, { "en", "en" }, { "mk", "mk" },
-};
-
-static const struct {
- const char* code;
- const char* name;
-} ysubs_languages[] = {
- { "sq", "albanian" }, { "ar", "arabic" }, { "bn", "bengali" },
- { "pb", "brazilian-portuguese" }, { "bg", "bulgarian" }, { "zh", "chinese" },
- { "hr", "croatian" }, { "cs", "czech" }, { "da", "danish" },
- { "nl", "dutch" }, { "en", "english" }, { "fa", "farsi-persian" },
- { "fi", "finnish" }, { "fr", "french" }, { "de", "german" },
- { "el", "greek" }, { "he", "hebrew" }, { "hu", "hungarian" },
- { "id", "indonesian" }, { "it", "italian" }, { "ja", "japanese" },
- { "ko", "korean" }, { "lt", "lithuanian" }, { "mk", "macedonian" },
- { "ms", "malay" }, { "no", "norwegian" }, { "pl", "polish" },
- { "pt", "portuguese" }, { "ro", "romanian" }, { "ru", "russian" },
- { "sr", "serbian" }, { "sl", "slovenian" }, { "es", "spanish" },
- { "sv", "swedish" }, { "th", "thai" }, { "tr", "turkish" },
- { "ur", "urdu" }, { "vi", "vietnamese" },
-};
+/*
+ * (C) 2016-2017 see Authors.txt
+ *
+ * This file is part of MPC-HC.
+ *
+ * MPC-HC is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MPC-HC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include "SubtitlesProviders.h"
+#include "VersionInfo.h"
+
+#include "XmlRpc4Win/TimXmlRpc.h"
+
+#define DEFINE_SUBTITLESPROVIDER_BEGIN(P, U, I, F) \
+class P final : public SubtitlesProvider { \
+public: \
+ P(SubtitlesProviders* pOwner) \
+ : SubtitlesProvider(pOwner) { \
+ Initialize(); \
+ } \
+ ~P() { Uninitialize(); } \
+ P(P const&) = delete; \
+ P& operator=(P const&) = delete; \
+ static std::shared_ptr<P> Create(SubtitlesProviders* pOwner) { \
+ return std::make_shared<P>(pOwner); \
+ } \
+private: \
+ virtual std::string Name() const override { return #P; } \
+ virtual std::string Url() const override { return U; } \
+ virtual const std::set<std::string>& Languages() const override; \
+ virtual bool Flags(DWORD dwFlags) const override { return (dwFlags & (F)) == dwFlags; } \
+ virtual int Icon() const override { return I; } \
+private: \
+ virtual SRESULT Search(const SubtitlesInfo& pFileInfo) override; \
+ virtual SRESULT Download(SubtitlesInfo& pSubtitlesInfo) override;
+#define DEFINE_SUBTITLESPROVIDER_END \
+};
+
+DEFINE_SUBTITLESPROVIDER_BEGIN(OpenSubtitles, "http://www.opensubtitles.org", IDI_OPENSUBTITLES, SPF_LOGIN | SPF_HASH | SPF_UPLOAD)
+void Initialize() override;
+bool NeedLogin() override;
+SRESULT Login(const std::string& sUserName, const std::string& sPassword) override;
+SRESULT LogOut() override;
+SRESULT Hash(SubtitlesInfo& pFileInfo) override;
+SRESULT Upload(const SubtitlesInfo& pSubtitlesInfo) override;
+std::unique_ptr<XmlRpcClient> xmlrpc;
+XmlRpcValue token;
+DEFINE_SUBTITLESPROVIDER_END
+
+DEFINE_SUBTITLESPROVIDER_BEGIN(SubDB, "http://api.thesubdb.com", IDI_SUBDB, SPF_HASH | SPF_UPLOAD)
+SRESULT Hash(SubtitlesInfo& pFileInfo) override;
+SRESULT Upload(const SubtitlesInfo& pSubtitlesInfo) override;
+std::string UserAgent() const override
+{
+ return SubtitlesProvidersUtils::StringFormat("SubDB/1.0 (MPC-HC/%s; http://mpc-hc.org)",
+ VersionInfo::GetVersionString());
+}
+DEFINE_SUBTITLESPROVIDER_END
+
+DEFINE_SUBTITLESPROVIDER_BEGIN(podnapisi, "https://www.podnapisi.net", IDI_PODNAPISI, SPF_SEARCH)
+SRESULT Login(const std::string& sUserName, const std::string& sPassword) override;
+SRESULT Hash(SubtitlesInfo& pFileInfo) override;
+DEFINE_SUBTITLESPROVIDER_END
+
+#ifdef MPCHC_DISABLED_SUBTITLES_PROVIDER
+DEFINE_SUBTITLESPROVIDER_BEGIN(titlovi, "http://www.titlovi.com", IDI_TITLOVI, SPF_SEARCH)
+DEFINE_SUBTITLESPROVIDER_END
+
+DEFINE_SUBTITLESPROVIDER_BEGIN(ysubs, "http://www.yifysubtitles.com", IDI_YSUBS, SPF_SEARCH)
+DEFINE_SUBTITLESPROVIDER_END
+#endif // MPCHC_DISABLED_SUBTITLES_PROVIDER
+
+DEFINE_SUBTITLESPROVIDER_BEGIN(Napisy24, "http://napisy24.pl/", IDI_N24, SPF_HASH | SPF_SEARCH)
+SRESULT Hash(SubtitlesInfo& pFileInfo) override;
+DEFINE_SUBTITLESPROVIDER_END
+
+static const struct {
+ const char* code;
+ const char* name;
+} podnapisi_languages[] = {
+ { /* 0*/ "", "" }, { /* 1*/ "sl", "Slovenian" }, { /* 2*/ "en", "English" },
+ { /* 3*/ "no", "Norwegian" }, { /* 4*/ "ko", "Korean" }, { /* 5*/ "de", "German" },
+ { /* 6*/ "is", "Icelandic" }, { /* 7*/ "cs", "Czech" }, { /* 8*/ "fr", "French" },
+ { /* 9*/ "it", "Italian" }, { /*10*/ "bs", "Bosnian" }, { /*11*/ "ja", "Japanese" },
+ { /*12*/ "ar", "Arabic" }, { /*13*/ "ro", "Romanian" }, { /*14*/ "es", "Argentino" },
+ { /*15*/ "hu", "Hungarian" }, { /*16*/ "el", "Greek" }, { /*17*/ "zh", "Chinese" },
+ { /*18*/ "", "" }, { /*19*/ "lt", "Lithuanian" }, { /*20*/ "et", "Estonian" },
+ { /*21*/ "lv", "Latvian" }, { /*22*/ "he", "Hebrew" }, { /*23*/ "nl", "Dutch" },
+ { /*24*/ "da", "Danish" }, { /*25*/ "sv", "Swedish" }, { /*26*/ "pl", "Polish" },
+ { /*27*/ "ru", "Russian" }, { /*28*/ "es", "Spanish" }, { /*29*/ "sq", "Albanian" },
+ { /*30*/ "tr", "Turkish" }, { /*31*/ "fi", "Finnish" }, { /*32*/ "pt", "Portuguese" },
+ { /*33*/ "bg", "Bulgarian" }, { /*34*/ "", "" }, { /*35*/ "mk", "Macedonian" },
+ { /*36*/ "sr", "Serbian" }, { /*37*/ "sk", "Slovak" }, { /*38*/ "hr", "Croatian" },
+ { /*39*/ "", "" }, { /*40*/ "zh", "Mandarin" }, { /*41*/ "", "" },
+ { /*42*/ "hi", "Hindi" }, { /*43*/ "", "" }, { /*44*/ "th", "Thai" },
+ { /*45*/ "", "" }, { /*46*/ "uk", "Ukrainian" }, { /*47*/ "sr", "Serbian (Cyrillic)" },
+ { /*48*/ "pb", "Brazilian" }, { /*49*/ "ga", "Irish" }, { /*50*/ "be", "Belarus" },
+ { /*51*/ "vi", "Vietnamese" }, { /*52*/ "fa", "Farsi" }, { /*53*/ "ca", "Catalan" },
+ { /*54*/ "id", "Indonesian" }, { /*55*/ "ms", "Malay" }, { /*56*/ "si", "Sinhala" },
+ { /*57*/ "kl", "Greenlandic" }, { /*58*/ "kk", "Kazakh" }, { /*59*/ "bn", "Bengali" },
+};
+
+#ifdef MPCHC_DISABLED_SUBTITLES_PROVIDER
+static const struct {
+ const char* code;
+ const char* name;
+} titlovi_languages[] = {
+ { "hr", "hr" }, { "sr", "sr" }, { "rs", "sr" }, { "si", "sl" }, { "ba", "bs" }, { "en", "en" }, { "mk", "mk" },
+};
+
+static const struct {
+ const char* code;
+ const char* name;
+} ysubs_languages[] = {
+ { "sq", "albanian" }, { "ar", "arabic" }, { "bn", "bengali" },
+ { "pb", "brazilian-portuguese" }, { "bg", "bulgarian" }, { "zh", "chinese" },
+ { "hr", "croatian" }, { "cs", "czech" }, { "da", "danish" },
+ { "nl", "dutch" }, { "en", "english" }, { "fa", "farsi-persian" },
+ { "fi", "finnish" }, { "fr", "french" }, { "de", "german" },
+ { "el", "greek" }, { "he", "hebrew" }, { "hu", "hungarian" },
+ { "id", "indonesian" }, { "it", "italian" }, { "ja", "japanese" },
+ { "ko", "korean" }, { "lt", "lithuanian" }, { "mk", "macedonian" },
+ { "ms", "malay" }, { "no", "norwegian" }, { "pl", "polish" },
+ { "pt", "portuguese" }, { "ro", "romanian" }, { "ru", "russian" },
+ { "sr", "serbian" }, { "sl", "slovenian" }, { "es", "spanish" },
+ { "sv", "swedish" }, { "th", "thai" }, { "tr", "turkish" },
+ { "ur", "urdu" }, { "vi", "vietnamese" },
+};
+#endif // MPCHC_DISABLED_SUBTITLES_PROVIDER