From 6ef6d1752c7fac4f1db7ddc283e934acc8a4556b Mon Sep 17 00:00:00 2001 From: Flavio Date: Thu, 13 Nov 2014 13:22:36 +0100 Subject: [PATCH] Fixed VEVO videos --- src/jsfunctions.cpp | 30 +++++++++++-- src/jsfunctions.h | 13 +++++- src/video.cpp | 107 ++++++++++---------------------------------- src/video.h | 2 - 4 files changed, 62 insertions(+), 90 deletions(-) diff --git a/src/jsfunctions.cpp b/src/jsfunctions.cpp index 961d3f6..2c95a1d 100644 --- a/src/jsfunctions.cpp +++ b/src/jsfunctions.cpp @@ -94,7 +94,7 @@ void JsFunctions::errorJs(QNetworkReply *reply) { << reply->url().toString() << reply->errorString(); } -QString JsFunctions::evaluateFunction(const QString &function) { +QString JsFunctions::evaluate(const QString &function) { if (!engine) return QString(); QScriptValue value = engine->evaluate(function); if (value.isUndefined()) @@ -106,9 +106,33 @@ QString JsFunctions::evaluateFunction(const QString &function) { } QString JsFunctions::decryptSignature(const QString &s) { - return evaluateFunction("decryptSignature('" + s + "')"); + return evaluate("decryptSignature('" + s + "')"); } QString JsFunctions::decryptAgeSignature(const QString &s) { - return evaluateFunction("decryptAgeSignature('" + s + "')"); + return evaluate("decryptAgeSignature('" + s + "')"); +} + +QString JsFunctions::videoIdRE() { + return evaluate("videoIdRE()"); +} + +QString JsFunctions::videoTokenRE() { + return evaluate("videoTokenRE()"); +} + +QString JsFunctions::videoInfoFmtMapRE() { + return evaluate("videoInfoFmtMapRE()"); +} + +QString JsFunctions::webPageFmtMapRE() { + return evaluate("webPageFmtMapRE()"); +} + +QString JsFunctions::jsPlayerRE() { + return evaluate("jsPlayerRE()"); +} + +QString JsFunctions::signatureFunctionNameRE() { + return evaluate("signatureFunctionNameRE()"); } diff --git a/src/jsfunctions.h b/src/jsfunctions.h index 31d7aac..ab0f1d1 100644 --- a/src/jsfunctions.h +++ b/src/jsfunctions.h @@ -31,8 +31,20 @@ class JsFunctions : public QObject { public: static JsFunctions* instance(); + + // Specialized functions + // TODO move to subclass QString decryptSignature(const QString &s); QString decryptAgeSignature(const QString &s); + QString videoIdRE(); + QString videoTokenRE(); + QString videoInfoFmtMapRE(); + QString webPageFmtMapRE(); + QString jsPlayerRE(); + QString signatureFunctionNameRE(); + +protected: + QString evaluate(const QString &function); private slots: void gotJs(QByteArray bytes); @@ -44,7 +56,6 @@ private: static const QString &jsPath(); void loadJs(); void parseJs(const QString &js); - QString evaluateFunction(const QString &function); QScriptEngine *engine; }; diff --git a/src/video.cpp b/src/video.cpp index 64cbf6d..f9018a8 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -35,10 +35,10 @@ static const QString jsNameChars = "a-zA-Z0-9\\$_"; Video::Video() : m_duration(0), m_viewCount(-1), + m_license(LicenseYouTube), definitionCode(0), elIndex(0), ageGate(false), - m_license(LicenseYouTube), loadingStreamUrl(false), loadingThumbnail(false) { } @@ -67,11 +67,8 @@ void Video::setWebpage(QUrl webpage) { m_webpage = webpage; // Get Video ID - // youtube-dl line 428 - // QRegExp re("^((?:http://)?(?:\\w+\\.)?youtube\\.com/(?:(?:v/)|(?:(?:watch(?:\\.php)?)?\\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$"); - QRegExp re("^https?://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+).*"); - bool match = re.exactMatch(m_webpage.toString()); - if (!match) { + QRegExp re(JsFunctions::instance()->videoIdRE()); + if (re.indexIn(m_webpage.toString()) == -1) { qWarning() << QString("Cannot get video id for %1").arg(m_webpage.toString()); // emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString())); // loadingStreamUrl = false; @@ -163,29 +160,26 @@ void Video::gotVideoInfo(QByteArray data) { // qDebug() << "videoInfo" << videoInfo; // get video token - QRegExp re = QRegExp("^.*&token=([^&]+).*$"); - bool match = re.exactMatch(videoInfo); - // handle regexp failure - if (!match) { - // qDebug() << "Cannot get token. Trying next el param"; + QRegExp videoTokeRE(JsFunctions::instance()->videoTokenRE()); + if (videoTokeRE.indexIn(videoInfo) == -1) { + qWarning() << "Cannot get token. Trying next el param" << videoInfo << videoTokeRE.pattern(); // Don't panic! We're gonna try another magic "el" param elIndex++; getVideoInfo(); return; } - QString videoToken = re.cap(1); + QString videoToken = videoTokeRE.cap(1); + // qWarning() << "got token" << videoToken; while (videoToken.contains('%')) videoToken = QByteArray::fromPercentEncoding(videoToken.toLatin1()); // qDebug() << "videoToken" << videoToken; this->videoToken = videoToken; // get fmt_url_map - re = QRegExp("^.*&url_encoded_fmt_stream_map=([^&]+).*$"); - match = re.exactMatch(videoInfo); - // handle regexp failure - if (!match) { - // qDebug() << "Cannot get urlMap. Trying next el param"; + QRegExp fmtMapRE(JsFunctions::instance()->videoInfoFmtMapRE()); + if (fmtMapRE.indexIn(videoInfo) == -1) { + qWarning() << "Cannot get urlMap. Trying next el param"; // Don't panic! We're gonna try another magic "el" param elIndex++; getVideoInfo(); @@ -194,7 +188,8 @@ void Video::gotVideoInfo(QByteArray data) { // qDebug() << "Got token and urlMap" << elIndex; - QString fmtUrlMap = re.cap(1); + QString fmtUrlMap = fmtMapRE.cap(1); + // qWarning() << "got fmtUrlMap" << fmtUrlMap; fmtUrlMap = QByteArray::fromPercentEncoding(fmtUrlMap.toUtf8()); parseFmtUrlMap(fmtUrlMap); } @@ -339,18 +334,16 @@ void Video::scrapeWebPage(QByteArray data) { return; } - QRegExp re(".*\"url_encoded_fmt_stream_map\":\\s+\"([^\"]+)\".*"); - bool match = re.exactMatch(html); - // on regexp failure, stop and report error - if (!match) { - qWarning() << "Error parsing video page"; + QRegExp fmtMapRE(JsFunctions::instance()->webPageFmtMapRE()); + if (fmtMapRE.indexIn(html) == -1) { + // qWarning() << "Error parsing video page"; // emit errorStreamUrl("Error parsing video page"); // loadingStreamUrl = false; elIndex++; getVideoInfo(); return; } - fmtUrlMap = re.cap(1); + fmtUrlMap = fmtMapRE.cap(1); fmtUrlMap.replace("\\u0026", "&"); // parseFmtUrlMap(fmtUrlMap, true); @@ -367,7 +360,7 @@ void Video::scrapeWebPage(QByteArray data) { } #endif - QRegExp jsPlayerRe("\"assets\":.+\"js\":\\s*\"([^\"]+)\""); + QRegExp jsPlayerRe(JsFunctions::instance()->jsPlayerRE()); if (jsPlayerRe.indexIn(html) != -1) { QString jsPlayerUrl = jsPlayerRe.cap(1); jsPlayerUrl.remove('\\'); @@ -384,55 +377,15 @@ void Video::scrapeWebPage(QByteArray data) { } } -void Video::gotHeadHeaders(QNetworkReply* reply) { - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // qDebug() << "gotHeaders" << statusCode; - if (statusCode == 200) { - foundVideoUrl(videoToken, definitionCode); - } else { - - // try next (lower quality) definition - /* - QStringList definitionNames = VideoDefinition::getDefinitionNames(); - int currentIndex = definitionNames.indexOf(currentDefinition); - int previousIndex = 0; - if (currentIndex > 0) { - previousIndex = currentIndex - 1; - } - if (previousIndex > 0) { - QString nextDefinitionName = definitionNames.at(previousIndex); - findVideoUrl(nextDefinitionName); - } else { - foundVideoUrl(videoToken, 18); - }*/ - - - QList definitionCodes = VideoDefinition::getDefinitionCodes(); - int currentIndex = definitionCodes.indexOf(definitionCode); - int previousIndex = 0; - if (currentIndex > 0) { - previousIndex = currentIndex - 1; - int definitionCode = definitionCodes.at(previousIndex); - if (definitionCode == 18) { - // This is assumed always available - foundVideoUrl(videoToken, 18); - } else { - findVideoUrl(definitionCode); - } - - } else { - foundVideoUrl(videoToken, 18); - } - - } -} - void Video::parseJsPlayer(QByteArray bytes) { QString js = QString::fromUtf8(bytes); // qWarning() << "jsPlayer" << js; - QRegExp funcNameRe("signature=([" + jsNameChars + "]+)"); + + // QRegExp funcNameRe("\"signature\"\\w*,\\w*([" + jsNameChars + "]+)"); + QRegExp funcNameRe(JsFunctions::instance()->signatureFunctionNameRE().arg(jsNameChars)); + if (funcNameRe.indexIn(js) == -1) { - qWarning() << "Cannot capture signature function name"; + qWarning() << "Cannot capture signature function name" << js; } else { sigFuncName = funcNameRe.cap(1); captureFunction(sigFuncName, js); @@ -548,20 +501,6 @@ QString Video::decryptSignature(const QString &s) { return value.toString(); } -void Video::findVideoUrl(int definitionCode) { - this->definitionCode = definitionCode; - - QUrl videoUrl = QUrl(QString( - "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=&ps=&asv=&fmt=%3" - ).arg(videoId, videoToken, QString::number(definitionCode))); - - QObject *reply = The::http()->head(videoUrl); - connect(reply, SIGNAL(finished(QNetworkReply*)), SLOT(gotHeadHeaders(QNetworkReply*))); - // connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*))); - - // see you in gotHeadHeaders() -} - QString Video::formattedDuration() const { QString format = m_duration > 3600 ? "h:mm:ss" : "m:ss"; return QTime().addSecs(m_duration).toString(format); diff --git a/src/video.h b/src/video.h index 48b2f1a..897fd52 100644 --- a/src/video.h +++ b/src/video.h @@ -97,13 +97,11 @@ private slots: void gotVideoInfo(QByteArray); void errorVideoInfo(QNetworkReply*); void scrapeWebPage(QByteArray); - void gotHeadHeaders(QNetworkReply*); void parseJsPlayer(QByteArray bytes); void parseDashManifest(QByteArray bytes); private: void getVideoInfo(); - void findVideoUrl(int definitionCode); void foundVideoUrl(QString videoToken, int definitionCode); void parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage = false); void captureFunction(const QString &name, const QString &js); -- 2.39.5