From 831ecf74b394df57cbc109014189906dc972a92d Mon Sep 17 00:00:00 2001 From: Jakob Haufe Date: Wed, 4 Mar 2020 19:52:19 +0000 Subject: [PATCH] New upstream version 3.3 --- README.md | 2 +- locale/ca.ts | 6 +-- locale/fr.ts | 8 ++-- locale/hu.ts | 22 +++++----- locale/ko_KR.ts | 38 ++++++++--------- locale/pt_PT.ts | 22 +++++----- locale/ru.ts | 32 +++++++------- minitube.pro | 2 +- src/httputils.cpp | 4 +- src/ytvideo.cpp | 104 ++++++++++++++++++++++++++++++++++++---------- src/ytvideo.h | 5 ++- 11 files changed, 155 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 1580dc6..4106a0e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Clone from Github: git clone --recursive https://github.com/flaviotordini/minitube.git -You need Qt >= 5.6 and MPV >= 0.29.0. The following Qt modules are needed: core, gui, widgets, network, sql (using the Sqlite plugin), declarative, dbus, x11extras. +You need Qt >= 5.10 and MPV >= 0.29.0. The following Qt modules are needed: core, gui, widgets, network, sql (using the Sqlite plugin), declarative, dbus, x11extras. To be able to build on a Debian (or derivative) system: diff --git a/locale/ca.ts b/locale/ca.ts index f3d56f3..64c3ecf 100644 --- a/locale/ca.ts +++ b/locale/ca.ts @@ -209,17 +209,17 @@ K K as in Kilo, i.e. thousands - + K M M stands for Millions - + M B B stands for Billions - + B %1 views diff --git a/locale/fr.ts b/locale/fr.ts index 978bb23..46ff0f3 100644 --- a/locale/fr.ts +++ b/locale/fr.ts @@ -27,7 +27,7 @@ Powered by %1 - + Alimenté par %1 Open-source software @@ -856,7 +856,7 @@ Switched to %1 - + Basculé vers %1 Unsubscribed from %1 @@ -905,7 +905,7 @@ PickMessage Pick a video - + Choisir une vidéo @@ -1102,7 +1102,7 @@ &Forward - + &Continuer Forward to %1 diff --git a/locale/hu.ts b/locale/hu.ts index 9d60117..fa4c83f 100644 --- a/locale/hu.ts +++ b/locale/hu.ts @@ -27,11 +27,11 @@ Powered by %1 - + %1 támogatásával Open-source software - + Nyílt forráskódú szoftver Icon designed by %1. @@ -209,17 +209,17 @@ K K as in Kilo, i.e. thousands - + K M M stands for Millions - + M B B stands for Billions - + B %1 views @@ -674,11 +674,11 @@ Toggle &Menu Bar - + Toggle &Menu Bar Menu - + Menü &Love %1? Rate it! @@ -806,7 +806,7 @@ You can still access the menu bar by pressing the ALT key - + A menüt az ALT gomb lenyomásával továbbra is eléred @@ -856,7 +856,7 @@ Switched to %1 - + Ide váltás: %1 Unsubscribed from %1 @@ -905,7 +905,7 @@ PickMessage Pick a video - + Válassz videót @@ -1102,7 +1102,7 @@ &Forward - + &Előre Forward to %1 diff --git a/locale/ko_KR.ts b/locale/ko_KR.ts index 1240d49..503df0e 100644 --- a/locale/ko_KR.ts +++ b/locale/ko_KR.ts @@ -69,7 +69,7 @@ ActivationView Please license %1 - %1을(를) 구입 하세요 + %1을(를) 구입하세요 This demo has expired. @@ -145,11 +145,11 @@ Last Watched - 마지막 시청 + 최근 본 동영상 Most Watched - 가장 많이 시청 + 가장 많이 본 동영상 Sort by @@ -161,11 +161,11 @@ Show Updated - 업데이트 표시 + 업데이트 You have no subscriptions. Use the star symbol to subscribe to channels. - 구독 항목이 없습니다. 채널을 구독 하려면 별 아이콘을 사용 하세요. + 구독 항목이 없습니다. 채널을 구독하려면 별 아이콘을 사용하세요. All Videos @@ -185,26 +185,26 @@ There are no updated subscriptions at this time. - 현재 업데이트된 구독이 없습니다. + 구독 목록이 최신입니다. DataUtils Just now - 지금 + 방금 전 %n hour(s) ago - + %n시간 전 %n day(s) ago - + %s일 전 %n month(s) ago - + %n달 전 K @@ -223,7 +223,7 @@ %1 views - %1 조회수 + 조회수 %1 %n week(s) ago @@ -273,7 +273,7 @@ DownloadManager %1 downloaded in %2 - %1 downloaded in %2 + %2 중 %1 다운로드 완료 Download finished @@ -1018,7 +1018,7 @@ Long - 김 + 오래 전 Less than 4 minutes @@ -1079,7 +1079,7 @@ Enter - 언터 + 이동 Recent keywords @@ -1106,11 +1106,11 @@ Forward to %1 - %1(으)로 이동 + 앞으로 (%1) Back to %1 - %1(으)로 이동 + 뒤로 (%1) @@ -1121,7 +1121,7 @@ Did you mean: %1 - 원했던 단어: %1 + 이것을 검색하셨나요: %1 @@ -1161,7 +1161,7 @@ YTRegions Algeria - 알제리아 + 알제리 Argentina @@ -1337,7 +1337,7 @@ United Arab Emirates - 아랍에메레이트 + 아랍 에미리트 United Kingdom diff --git a/locale/pt_PT.ts b/locale/pt_PT.ts index 6caf996..30161ae 100644 --- a/locale/pt_PT.ts +++ b/locale/pt_PT.ts @@ -27,11 +27,11 @@ Powered by %1 - + Animado por %1 Open-source software - + Programa Livre Icon designed by %1. @@ -115,7 +115,7 @@ You have %n new video(s) - + Você tem %n novos vídeosVocê tem %n novos vídeos @@ -196,30 +196,30 @@ %n hour(s) ago - + há %n horashá %n horas %n day(s) ago - + há %n diashá %n dias %n month(s) ago - + há %n mêshá %n meses K K as in Kilo, i.e. thousands - + K M M stands for Millions - + M B B stands for Billions - + B %1 views @@ -227,7 +227,7 @@ %n week(s) ago - + há %n semanashá %n semanas @@ -281,7 +281,7 @@ %n Download(s) - + %n Transferência%n Transferências diff --git a/locale/ru.ts b/locale/ru.ts index 179bbb5..1de2897 100644 --- a/locale/ru.ts +++ b/locale/ru.ts @@ -27,11 +27,11 @@ Powered by %1 - + При помощи %1 Open-source software - + ПО с открытым исходным кодом Icon designed by %1. @@ -116,7 +116,7 @@ You have %n new video(s) - + У вас %n новых видео()У вас %n новых видео()У вас %n новых видео()У вас %n новых видео() @@ -178,7 +178,7 @@ Mark as Watched - Отметить как Просмотренное. + Отметить как просмотренное Unsubscribe @@ -197,30 +197,30 @@ %n hour(s) ago - + %n час назад ()%n часов назад ()%n часов назад ()%n час(-а, -ов) назад %n day(s) ago - + %n день назад%n дней (-ня) назад%n дней (-ня) назад%n день (суток) назад %n month(s) ago - + %n месяц назад ()%n месяца назад ()%n месяцев назад ()%n месяц(-ев) назад K K as in Kilo, i.e. thousands - + Тыс M M stands for Millions - + Млн B B stands for Billions - + Млрд %1 views @@ -228,7 +228,7 @@ %n week(s) ago - + %n неделю назад ()%n недель назад ()%n недель назад ()%n неделя назад () @@ -675,11 +675,11 @@ Toggle &Menu Bar - + Скрыть &Меню Menu - + Меню &Love %1? Rate it! @@ -807,7 +807,7 @@ You can still access the menu bar by pressing the ALT key - + Открыть меню можно нажатием ALT @@ -906,7 +906,7 @@ PickMessage Pick a video - + Посмотреть видео @@ -1103,7 +1103,7 @@ &Forward - + &Вверх Forward to %1 diff --git a/minitube.pro b/minitube.pro index 27c5a01..778eb3e 100644 --- a/minitube.pro +++ b/minitube.pro @@ -1,7 +1,7 @@ CONFIG += c++14 exceptions_off rtti_off optimize_full TEMPLATE = app -VERSION = 3.1 +VERSION = 3.3 DEFINES += APP_VERSION="$$VERSION" APP_NAME = Minitube diff --git a/src/httputils.cpp b/src/httputils.cpp index d522f6e..6865499 100644 --- a/src/httputils.cpp +++ b/src/httputils.cpp @@ -56,6 +56,8 @@ const QByteArray &HttpUtils::userAgent() { } const QByteArray &HttpUtils::stealthUserAgent() { - static const QByteArray ua = "curl/7.37.0"; + static const QByteArray ua = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like " + "Gecko) Chrome/79.0.3945.79 Safari/537.36"; return ua; } diff --git a/src/ytvideo.cpp b/src/ytvideo.cpp index f311011..157eeb9 100644 --- a/src/ytvideo.cpp +++ b/src/ytvideo.cpp @@ -28,8 +28,10 @@ void YTVideo::loadStreamUrl() { loadingStreamUrl = true; elIndex = 0; ageGate = false; + webPageLoaded = false; - getVideoInfo(); + // getVideoInfo(); + loadWebPage(); } void YTVideo::getVideoInfo() { @@ -49,9 +51,13 @@ void YTVideo::getVideoInfo() { q.addQueryItem("sts", "1588"); url.setQuery(q); } else if (elIndex > elTypes.size() - 1) { - qWarning() << "Cannot get video info"; - loadingStreamUrl = false; - emit errorStreamUrl("Cannot get video info"); + qDebug() << "Cannot get video info"; + if (!webPageLoaded) { + // no video info file, but we can try loading the "urlmap" from the web page + loadWebPage(); + } else { + emitError("Cannot get video info"); + } return; } else { // qDebug() << "Trying el param:" << elTypes.at(elIndex) << elIndex; @@ -62,7 +68,7 @@ void YTVideo::getVideoInfo() { QObject *reply = HttpUtils::yt().get(url); connect(reply, SIGNAL(data(QByteArray)), SLOT(gotVideoInfo(QByteArray))); - connect(reply, SIGNAL(error(QString)), SLOT(errorVideoInfo(QString))); + connect(reply, SIGNAL(error(QString)), SLOT(emitError(QString))); // see you in gotVideoInfo... } @@ -71,10 +77,54 @@ void YTVideo::gotVideoInfo(const QByteArray &bytes) { QString videoInfo = QString::fromUtf8(bytes); // qDebug() << "videoInfo" << videoInfo; + // get player_response + static const QRegExp playerResponseRE("&player_response=([^&]+)"); + if (playerResponseRE.indexIn(videoInfo) != -1) { + QString playerResponse = playerResponseRE.cap(1); + QByteArray playerResponseUtf8 = QByteArray::fromPercentEncoding(playerResponse.toUtf8()); + // qDebug() << "player_response" << playerResponseUtf8; + QJsonDocument doc = QJsonDocument::fromJson(playerResponseUtf8); + QJsonObject obj = doc.object(); + if (obj.contains("streamingData")) { + auto parseFormats = [this](const QJsonArray &formats) { + for (const QJsonValue &format : formats) { + QJsonObject formatObj = format.toObject(); + int itag = formatObj["itag"].toInt(); + QString url = formatObj["url"].toString(); + if (url.isEmpty()) { + QString cipher = formatObj["cipher"].toString(); + QUrlQuery q(cipher); + qDebug() << "Cipher is " << q.toString(); + url = q.queryItemValue("url").trimmed(); + // while (url.contains('%')) + url = QByteArray::fromPercentEncoding(url.toUtf8()); + if (q.hasQueryItem("s")) { + QString s = q.queryItemValue("s"); + qDebug() << "s is" << s; + s = decryptSignature(s); + if (!s.isEmpty()) { + qDebug() << "Added signature" << s; + url += "&sig="; + url += s; + } + } + } + // qDebug() << "player_response format" << itag << url; + if (!url.isEmpty()) urlMap.insert(itag, url); + } + }; + QJsonObject streamingDataObj = obj["streamingData"].toObject(); + // qDebug() << "Found streamingData" << streamingDataObj; + parseFormats(streamingDataObj["formats"].toArray()); + parseFormats(streamingDataObj["adaptiveFormats"].toArray()); + } + } + + /* // get video token static const QRegExp videoTokeRE(JsFunctions::instance()->videoTokenRE()); if (videoTokeRE.indexIn(videoInfo) == -1) { - qDebug() << "Cannot get token. Trying next el param" << videoInfo << videoTokeRE.pattern(); + qDebug() << "Cannot get token. Trying next el param" << videoTokeRE.pattern() << videoInfo; // Don't panic! We're gonna try another magic "el" param elIndex++; getVideoInfo(); @@ -96,16 +146,22 @@ void YTVideo::gotVideoInfo(const QByteArray &bytes) { getVideoInfo(); return; } - QString fmtUrlMap = fmtMapRE.cap(1); // qDebug() << "got fmtUrlMap" << fmtUrlMap; fmtUrlMap = QByteArray::fromPercentEncoding(fmtUrlMap.toUtf8()); +*/ + + if (urlMap.isEmpty()) { + elIndex++; + getVideoInfo(); + return; + } qDebug() << "Got token and urlMap" << elIndex << videoToken << fmtUrlMap; parseFmtUrlMap(fmtUrlMap); } -void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage) { +void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap) { int videoFormat = 0; const VideoDefinition &definition = YT3::instance().maxVideoDefinition(); @@ -137,7 +193,7 @@ void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage) { int separator = urlParam.indexOf('='); sig = QByteArray::fromPercentEncoding(urlParam.mid(separator + 1).toUtf8()); } else if (urlParam.startsWith(QLatin1String("s="))) { - if (fromWebPage || ageGate) { + if (webPageLoaded || ageGate) { int separator = urlParam.indexOf('='); sig = QByteArray::fromPercentEncoding(urlParam.mid(separator + 1).toUtf8()); sig = decryptSignature(sig); @@ -173,7 +229,7 @@ void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage) { urlMap.insert(format, url); } - if (!fromWebPage && !ageGate) { + if (!webPageLoaded && !ageGate) { loadWebPage(); return; } @@ -185,6 +241,7 @@ void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage) { return; } + qDebug() << "available formats" << urlMap.keys(); const QVector &definitions = VideoDefinition::getDefinitions(); int previousIndex = std::max(definitions.indexOf(definition) - 1, 0); for (; previousIndex >= 0; previousIndex--) { @@ -209,22 +266,26 @@ void YTVideo::loadWebPage() { q.addQueryItem("has_verified", "1"); q.addQueryItem("bpctr", "9999999999"); url.setQuery(q); + + // QUrl url("https://www.youtube.com/embed/" + videoId); + qDebug() << "Loading webpage" << url; QObject *reply = HttpUtils::yt().get(url); connect(reply, SIGNAL(data(QByteArray)), SLOT(scrapeWebPage(QByteArray))); - connect(reply, SIGNAL(error(QString)), SLOT(errorVideoInfo(QString))); + connect(reply, SIGNAL(error(QString)), SLOT(emitError(QString))); // see you in scrapWebPage(QByteArray) } -void YTVideo::errorVideoInfo(const QString &message) { - loadingStreamUrl = false; +void YTVideo::emitError(const QString &message) { + qWarning() << message; emit errorStreamUrl(message); } void YTVideo::scrapeWebPage(const QByteArray &bytes) { - qDebug() << "scrapeWebPage"; + webPageLoaded = true; const QString html = QString::fromUtf8(bytes); + // qDebug() << "scrapeWebPage" << html; static const QRegExp ageGateRE(JsFunctions::instance()->ageGateRE()); if (ageGateRE.indexIn(html) != -1) { @@ -249,11 +310,11 @@ void YTVideo::scrapeWebPage(const QByteArray &bytes) { fmtUrlMap += adaptiveFormatsRE.cap(1).replace("\\u0026", "&"); } - if (fmtUrlMap.isEmpty()) { + if (fmtUrlMap.isEmpty() && urlMap.isEmpty()) { qWarning() << "Cannot get fmtUrlMap from video page. Trying next el"; - elIndex++; - getVideoInfo(); - return; + // elIndex++; + // getVideoInfo(); + // return; } static const QRegExp jsPlayerRe(JsFunctions::instance()->jsPlayerRE()); @@ -273,7 +334,7 @@ void YTVideo::scrapeWebPage(const QByteArray &bytes) { */ QObject *reply = HttpUtils::yt().get(jsPlayerUrl); connect(reply, SIGNAL(data(QByteArray)), SLOT(parseJsPlayer(QByteArray))); - connect(reply, SIGNAL(error(QString)), SLOT(errorVideoInfo(QString))); + connect(reply, SIGNAL(error(QString)), SLOT(emitError(QString))); } } @@ -291,7 +352,7 @@ void YTVideo::parseJsPlayer(const QByteArray &bytes) { }(); for (const QRegExp &funcNameRe : funcNameRes) { if (funcNameRe.indexIn(jsPlayer) == -1) { - qWarning() << "Cannot capture signature function name" << funcNameRe; + qDebug() << "Cannot capture signature function name" << funcNameRe; continue; } else { sigFuncName = funcNameRe.cap(1); @@ -307,7 +368,8 @@ void YTVideo::parseJsPlayer(const QByteArray &bytes) { } if (sigFuncName.isEmpty()) qDebug() << "Empty signature function name"; - parseFmtUrlMap(fmtUrlMap, true); + // parseFmtUrlMap(fmtUrlMap, true); + getVideoInfo(); } void YTVideo::captureFunction(const QString &name, const QString &js) { diff --git a/src/ytvideo.h b/src/ytvideo.h index 772df5f..d50fa59 100644 --- a/src/ytvideo.h +++ b/src/ytvideo.h @@ -19,13 +19,13 @@ signals: private slots: void gotVideoInfo(const QByteArray &bytes); - void errorVideoInfo(const QString &message); + void emitError(const QString &message); void scrapeWebPage(const QByteArray &bytes); void parseJsPlayer(const QByteArray &bytes); private: void getVideoInfo(); - void parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage = false); + void parseFmtUrlMap(const QString &fmtUrlMap); void loadWebPage(); void captureFunction(const QString &name, const QString &js); void captureObject(const QString &name, const QString &js); @@ -46,6 +46,7 @@ private: QString dashManifestUrl; QString jsPlayer; QMap urlMap; + bool webPageLoaded = false; }; #endif // YTVIDEO_H -- 2.39.5