Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package minitube for openSUSE:Factory checked in at 2021-06-26 21:25:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/minitube (Old) and /work/SRC/openSUSE:Factory/.minitube.new.2625 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "minitube" Sat Jun 26 21:25:27 2021 rev:26 rq:902405 version:3.9 Changes: -------- --- /work/SRC/openSUSE:Factory/minitube/minitube.changes 2021-04-08 21:32:40.431840598 +0200 +++ /work/SRC/openSUSE:Factory/.minitube.new.2625/minitube.changes 2021-06-26 21:25:45.791367083 +0200 @@ -1,0 +2,6 @@ +Wed Jun 23 17:10:35 UTC 2021 - Carsten Ziepke <[email protected]> + +- Update to version 3.9: + https://github.com/flaviotordini/minitube/compare/3.8.2...3.9 + +------------------------------------------------------------------- Old: ---- minitube-3.8.2.tar.xz New: ---- minitube-3.9.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ minitube.spec ++++++ --- /var/tmp/diff_new_pack.uT3Hf6/_old 2021-06-26 21:25:46.227367658 +0200 +++ /var/tmp/diff_new_pack.uT3Hf6/_new 2021-06-26 21:25:46.231367663 +0200 @@ -17,7 +17,7 @@ Name: minitube -Version: 3.8.2 +Version: 3.9 Release: 0 Summary: Native YouTube Client License: GPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.uT3Hf6/_old 2021-06-26 21:25:46.259367701 +0200 +++ /var/tmp/diff_new_pack.uT3Hf6/_new 2021-06-26 21:25:46.259367701 +0200 @@ -4,7 +4,7 @@ <param name="url">https://github.com/flaviotordini/minitube.git</param> <param name="filename">minitube</param> <param name="versionformat">@PARENT_TAG@</param> - <param name="revision">3.8.2</param> + <param name="revision">3.9</param> </service> <service mode="disabled" name="recompress"> <param name="file">*.tar</param> ++++++ minitube-3.8.2.tar.xz -> minitube-3.9.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/.gitignore new/minitube-3.9/.gitignore --- old/minitube-3.8.2/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/.gitignore 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,16 @@ +build/ +Makefile* +minitube.pro.user* +.settings/ +.DS_Store +.cproject +.project +local/ +*.swp +.tx +android +qtc_packaging +debian + + +*.stash diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/.gitmodules new/minitube-3.9/.gitmodules --- old/minitube-3.8.2/.gitmodules 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/.gitmodules 2021-06-22 01:31:51.000000000 +0200 @@ -18,3 +18,7 @@ path = lib/js url = https://github.com/flaviotordini/js branch = master +[submodule "lib/promises"] + path = lib/promises + url = https://github.com/flaviotordini/promises + branch = master diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/http/.gitignore new/minitube-3.9/lib/http/.gitignore --- old/minitube-3.8.2/lib/http/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/http/.gitignore 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,5 @@ +.idea +cmake-build-debug +*.user +.DS_Store +build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/http/src/http.cpp new/minitube-3.9/lib/http/src/http.cpp --- old/minitube-3.8.2/lib/http/src/http.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/lib/http/src/http.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -5,7 +5,13 @@ namespace { QNetworkAccessManager *networkAccessManager() { - static thread_local QNetworkAccessManager *nam = new QNetworkAccessManager(); + static thread_local QNetworkAccessManager *nam = [] { + auto nam = new QNetworkAccessManager(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); +#endif + return nam; + }(); return nam; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/http/src/networkhttpreply.cpp new/minitube-3.9/lib/http/src/networkhttpreply.cpp --- old/minitube-3.8.2/lib/http/src/networkhttpreply.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/lib/http/src/networkhttpreply.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -54,6 +54,7 @@ } void NetworkHttpReply::replyFinished() { +#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (redirection.isValid()) { HttpRequest redirectReq; @@ -71,6 +72,7 @@ readTimeoutTimer->start(); return; } +#endif if (isSuccessful()) { bytes = networkReply->readAll(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/js/.gitignore new/minitube-3.9/lib/js/.gitignore --- old/minitube-3.8.2/lib/js/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/js/.gitignore 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,2 @@ + +.DS_Store diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/js/js.cpp new/minitube-3.9/lib/js/js.cpp --- old/minitube-3.8.2/lib/js/js.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/lib/js/js.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -55,9 +55,7 @@ return *result; } - auto nam = getEngine().networkAccessManager(); - nam->clearAccessCache(); - nam->setCookieJar(new QNetworkCookieJar()); + resetNAM(); auto function = engine->evaluate(name); if (!function.isCallable()) { @@ -75,6 +73,24 @@ return *result; } +void JS::resetNAM() { + class MyCookieJar : public QNetworkCookieJar { + bool insertCookie(const QNetworkCookie &cookie) { + if (cookie.name().contains("CONSENT")) { + qDebug() << "Fixing CONSENT cookie" << cookie; + auto cookie2 = cookie; + cookie2.setValue(cookie.value().replace("PENDING", "YES")); + return QNetworkCookieJar::insertCookie(cookie2); + } + return QNetworkCookieJar::insertCookie(cookie); + } + }; + + auto nam = getEngine().networkAccessManager(); + nam->clearAccessCache(); + nam->setCookieJar(new MyCookieJar()); +} + void JS::initialize() { if (url.isEmpty()) { qDebug() << "No js url set"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/js/js.h new/minitube-3.9/lib/js/js.h --- old/minitube-3.8.2/lib/js/js.h 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/lib/js/js.h 2021-06-22 01:31:51.000000000 +0200 @@ -18,14 +18,15 @@ auto getId() const { return id; } // This should be static but cannot bind static functions to QJSEngine - Q_INVOKABLE QJSValue clearTimeout(QJSValue id) { + Q_INVOKABLE void clearTimeout(QJSValue id) { + // qDebug() << "Clearing timer" << id.toString(); auto timer = getTimers().take(id.toUInt()); if (timer) { timer->stop(); timer->deleteLater(); } else qDebug() << "Unknown id" << id.toUInt(); - return QJSValue(); + return; } // This should be static but cannot bind static functions to QJSEngine Q_INVOKABLE QJSValue setTimeout(QJSValue callback, QJSValue delayTime, QJSValue args) { @@ -57,7 +58,7 @@ } Q_INVOKABLE JSTimer(QObject *parent = nullptr) : QTimer(parent) { - setTimerType(Qt::CoarseTimer); + setTimerType(Qt::PreciseTimer); setSingleShot(true); // avoid 0 static uint counter = 1; @@ -87,6 +88,7 @@ QQmlEngine &getEngine() { return *engine; } JSResult &callFunction(JSResult *result, const QString &name, const QJSValueList &args); + void resetNAM(); signals: void initialized(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/js/jsnamfactory.cpp new/minitube-3.9/lib/js/jsnamfactory.cpp --- old/minitube-3.8.2/lib/js/jsnamfactory.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/lib/js/jsnamfactory.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -62,8 +62,25 @@ << req2.rawHeader(i.key()); } - qDebug() << req2.url() << req2.rawHeaderList(); - return QNetworkAccessManager::createRequest(op, req2, outgoingData); +#ifndef QT_NO_DEBUG_OUTPUT + qDebug() << req2.url(); + for (const auto &h : req2.rawHeaderList()) + qDebug() << h << req2.rawHeader(h); +#endif + + auto reply = QNetworkAccessManager::createRequest(op, req2, outgoingData); + +#ifndef QT_NO_DEBUG_OUTPUT + connect(reply, &QNetworkReply::finished, this, [reply] { + qDebug() << "finished" + << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt() + << reply->url() << reply->rawHeaderPairs(); + }); + connect(reply, &QNetworkReply::redirectAllowed, this, + [reply] { qDebug() << "redirectAllowed" << reply->url(); }); +#endif + + return reply; } QNetworkAccessManager *JSNAMFactory::create(QObject *parent) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/media/.gitignore new/minitube-3.9/lib/media/.gitignore --- old/minitube-3.8.2/lib/media/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/media/.gitignore 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1 @@ +.DS_Store diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/promises/LICENSE new/minitube-3.9/lib/promises/LICENSE --- old/minitube-3.8.2/lib/promises/LICENSE 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/promises/LICENSE 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Flavio Tordini + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/promises/emptypromise.h new/minitube-3.9/lib/promises/emptypromise.h --- old/minitube-3.8.2/lib/promises/emptypromise.h 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/promises/emptypromise.h 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,33 @@ +#ifndef EMPTYPROMISE_H +#define EMPTYPROMISE_H + +#include <QtCore> + +class EmptyPromise : public QObject { + Q_OBJECT + +public: + explicit EmptyPromise(QObject *parent = nullptr) : QObject(parent) { + connect(this, &EmptyPromise::success, this, &QObject::deleteLater); + connect(this, &EmptyPromise::error, this, &QObject::deleteLater); + }; + + template <typename Functor> EmptyPromise &onSuccess(Functor func) { + connect(this, &EmptyPromise::success, this, func); + return *this; + } + template <typename Functor> EmptyPromise &onError(Functor func) { + connect(this, &EmptyPromise::error, this, func); + return *this; + } + template <typename Functor> EmptyPromise &finally(Functor func) { + connect(this, &EmptyPromise::destroyed, this, func); + return *this; + } + +signals: + void success(); + void error(const QString &message); +}; + +#endif // EMPTYPROMISE_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/promises/promise.h new/minitube-3.9/lib/promises/promise.h --- old/minitube-3.8.2/lib/promises/promise.h 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/promises/promise.h 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,46 @@ +#ifndef PROMISE_H +#define PROMISE_H + +#include <QtCore> + +/// private, don't use directly +class BasePromise : public QObject { + Q_OBJECT + +public: + explicit BasePromise(QObject *parent = nullptr) : QObject(parent) { + connect(this, &BasePromise::resolve, this, &QObject::deleteLater); + connect(this, &BasePromise::reject, this, &QObject::deleteLater); + }; + + template <typename Function> BasePromise &then(Function func) { + connect(this, &BasePromise::resolve, this, func); + return *this; + } + template <typename Function> BasePromise &onFailed(Function func) { + connect(this, &BasePromise::reject, this, func); + return *this; + } + template <typename Function> BasePromise &finally(Function func) { + connect(this, &BasePromise::destroyed, this, func); + return *this; + } + +signals: + void resolve(); + void reject(const QString &message); +}; + +template <class T> class Promise : public BasePromise { +public: + void resolve(T value) { + data = value; + resolve(); + } + T result() const { return data; } + +private: + T data; +}; + +#endif // PROMISE_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/promises/promises.pri new/minitube-3.9/lib/promises/promises.pri --- old/minitube-3.8.2/lib/promises/promises.pri 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/promises/promises.pri 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,9 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +QT *= core + +HEADERS += \ + $$PWD/emptypromise.h \ + $$PWD/promise.h \ + $$PWD/variantpromise.h diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/promises/variantpromise.h new/minitube-3.9/lib/promises/variantpromise.h --- old/minitube-3.8.2/lib/promises/variantpromise.h 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/promises/variantpromise.h 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,33 @@ +#ifndef VARIANTPROMISE_H +#define VARIANTPROMISE_H + +#include <QtCore> + +class VariantPromise : public QObject { + Q_OBJECT + +public: + explicit VariantPromise(QObject *parent = nullptr) : QObject(parent) { + connect(this, &VariantPromise::resolve, this, &QObject::deleteLater); + connect(this, &VariantPromise::reject, this, &QObject::deleteLater); + }; + + template <typename Function> VariantPromise &then(Function func) { + connect(this, &VariantPromise::resolve, this, func); + return *this; + } + template <typename Function> VariantPromise &onFailed(Function func) { + connect(this, &VariantPromise::reject, this, func); + return *this; + } + template <typename Function> VariantPromise &finally(Function func) { + connect(this, &VariantPromise::destroyed, this, func); + return *this; + } + +signals: + void resolve(QVariant result); + void reject(const QString &message); +}; + +#endif // VARIANTPROMISE_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/lib/updater/.gitignore new/minitube-3.9/lib/updater/.gitignore --- old/minitube-3.8.2/lib/updater/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/lib/updater/.gitignore 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,3 @@ + +.DS_Store +.vscode diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/minitube.pro new/minitube-3.9/minitube.pro --- old/minitube-3.8.2/minitube.pro 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/minitube.pro 2021-06-22 01:31:51.000000000 +0200 @@ -1,7 +1,7 @@ CONFIG += c++17 exceptions_off rtti_off object_parallel_to_source TEMPLATE = app -VERSION = 3.8.2 +VERSION = 3.9 DEFINES += APP_VERSION="$$VERSION" APP_NAME = Minitube @@ -38,6 +38,7 @@ include(lib/http/http.pri) include(lib/idle/idle.pri) include(lib/js/js.pri) +include(lib/promises/promises.pri) DEFINES += MEDIA_MPV include(lib/media/media.pri) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/org.tordini.flavio.minitube.metainfo.xml new/minitube-3.9/org.tordini.flavio.minitube.metainfo.xml --- old/minitube-3.8.2/org.tordini.flavio.minitube.metainfo.xml 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/org.tordini.flavio.minitube.metainfo.xml 2021-06-22 01:31:51.000000000 +0200 @@ -2,6 +2,7 @@ <component type="desktop-application"> <name>Minitube</name> <id>org.tordini.flavio.minitube</id> + <launchable type="desktop-id">minitube.desktop</launchable> <metadata_license>CC0-1.0</metadata_license> <project_license>GPL-3.0-or-later</project_license> <summary>YouTube app</summary> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/resources.qrc new/minitube-3.9/resources.qrc --- old/minitube-3.8.2/resources.qrc 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/resources.qrc 2021-06-22 01:31:51.000000000 +0200 @@ -52,5 +52,6 @@ <file>images/[email protected]</file> <file>images/64x64/app.png</file> <file>images/64x64/[email protected]</file> + <file>flags/us.png</file> </qresource> </RCC> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/aggregatevideosource.cpp new/minitube-3.9/src/aggregatevideosource.cpp --- old/minitube-3.8.2/src/aggregatevideosource.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/aggregatevideosource.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -63,7 +63,18 @@ video->setChannelId(query.value(4).toString()); video->setDescription(query.value(5).toString()); video->setWebpage(query.value(6).toString()); - video->setThumbnailUrl(query.value(7).toString()); + + QString thumbString = query.value(7).toString(); + if (thumbString.startsWith('[')) { + const auto thumbs = QJsonDocument::fromJson(thumbString.toUtf8()).array(); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), t["url"].toString()); + } + } else { + // assume it's a URL + video->addThumb(0, 0, thumbString); + } + video->setViewCount(query.value(8).toInt()); video->setDuration(query.value(9).toInt()); videos << video; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/channelaggregator.cpp new/minitube-3.9/src/channelaggregator.cpp --- old/minitube-3.8.2/src/channelaggregator.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/channelaggregator.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -132,7 +132,7 @@ } else { currentChannel->updateChecked(); currentChannel = 0; - processNextChannel(); + QTimer::singleShot(5000, this, &ChannelAggregator::processNextChannel); } } @@ -273,7 +273,18 @@ query.bindValue(7, video->getChannelId()); query.bindValue(8, video->getDescription()); query.bindValue(9, video->getWebpage()); - query.bindValue(10, video->getThumbnailUrl()); + + QJsonDocument thumbsDoc; + auto thumbsArray = thumbsDoc.array(); + for (const auto &t : video->getThumbs()) { + thumbsArray.append(QJsonObject{ + {"url", t.getUrl()}, + {"width", t.getWidth()}, + {"height", t.getHeight()}, + }); + } + thumbsDoc.setArray(thumbsArray); + query.bindValue(10, thumbsDoc.toJson(QJsonDocument::Compact)); query.bindValue(11, video->getViewCount()); query.bindValue(12, video->getDuration()); success = query.exec(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/mainwindow.cpp new/minitube-3.9/src/mainwindow.cpp --- old/minitube-3.8.2/src/mainwindow.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/mainwindow.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -649,7 +649,7 @@ action->setEnabled(false); actionMap.insert("refineSearch", action); - action = new QAction(YTRegions::worldwideRegion().name, this); + action = new QAction(YTRegions::defaultRegion().name, this); actionMap.insert("worldwideRegion", action); action = new QAction(YTRegions::localRegion().name, this); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/mediaview.cpp new/minitube-3.9/src/mediaview.cpp --- old/minitube-3.8.2/src/mediaview.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/mediaview.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -254,6 +254,7 @@ VideoSource *vs = history.takeLast(); if (!vs->parent()) { qDebug() << "Deleting VideoSource" << vs->getName() << vs; + vs->abort(); vs->deleteLater(); } } @@ -376,20 +377,25 @@ void MediaView::pause() { switch (media->state()) { case Media::PlayingState: + qDebug() << "Pausing"; media->pause(); pauseTimer.start(); break; default: - if (pauseTimer.hasExpired(60000)) { + if (pauseTimer.isValid() && pauseTimer.hasExpired(60000)) { + qDebug() << "Pause timer expired"; pauseTimer.invalidate(); auto activeVideo = playlistModel->activeVideo(); if (activeVideo) { connect(activeVideo, &Video::gotStreamUrl, this, &MediaView::resumeWithNewStreamUrl); activeVideo->loadStreamUrl(); - } - } else + } else + qDebug() << "No active video"; + } else { + qDebug() << "Playing" << media->file(); media->play(); + } break; } } @@ -405,6 +411,7 @@ VideoSource *videoSource = history.takeFirst(); // Don't delete videoSource in the Browse view if (!videoSource->parent()) { + videoSource->abort(); videoSource->deleteLater(); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/playlistitemdelegate.cpp new/minitube-3.9/src/playlistitemdelegate.cpp --- old/minitube-3.8.2/src/playlistitemdelegate.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/playlistitemdelegate.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -25,6 +25,7 @@ #include "iconutils.h" #include "playlistmodel.h" #include "playlistview.h" +#include "variantpromise.h" #include "video.h" #include "videodefinition.h" @@ -128,17 +129,31 @@ const bool isActive = index.data(ActiveTrackRole).toBool(); // get the video metadata - const Video *video = index.data(VideoRole).value<VideoPointer>().data(); + Video *video = index.data(VideoRole).value<VideoPointer>().data(); // draw the "current track" highlight underneath the text if (isActive && !isSelected) paintActiveOverlay(painter, option, line); // thumb - const QPixmap &thumb = video->getThumbnail(); + qreal pixelRatio = painter->device()->devicePixelRatioF(); + QByteArray thumbKey = ("t" + QString::number(pixelRatio)).toUtf8(); + const QPixmap &thumb = video->property(thumbKey).value<QPixmap>(); if (!thumb.isNull()) { painter->drawPixmap(0, 0, thumb); if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line); - } + } else + video->loadThumb({thumbWidth, thumbHeight}, pixelRatio) + .then([pixelRatio, thumbKey, video](auto variant) { + QPixmap pixmap; + pixmap.loadFromData(variant.toByteArray()); + pixmap.setDevicePixelRatio(pixelRatio); + const int thumbWidth = PlaylistItemDelegate::thumbWidth * pixelRatio; + if (pixmap.width() > thumbWidth) + pixmap = pixmap.scaledToWidth(thumbWidth, Qt::SmoothTransformation); + video->setProperty(thumbKey, pixmap); + video->changed(); + }) + .onFailed([](auto msg) { qDebug() << msg; }); const bool thumbsOnly = line.width() <= thumbWidth + 60; const bool isHovered = index.data(HoveredItemRole).toBool(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/playlistmodel.cpp new/minitube-3.9/src/playlistmodel.cpp --- old/minitube-3.8.2/src/playlistmodel.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/playlistmodel.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -20,6 +20,7 @@ #include "playlistmodel.h" #include "mediaview.h" +#include "playlistitemdelegate.h" #include "searchparams.h" #include "video.h" #include "videomimedata.h" @@ -242,8 +243,10 @@ videos.append(newVideos); endInsertRows(); for (Video *video : newVideos) { - connect(video, SIGNAL(gotThumbnail()), SLOT(updateVideoSender()), Qt::UniqueConnection); - video->loadThumbnail(); + connect(video, &Video::changed, this, [video, this] { + int row = rowForVideo(video); + emit dataChanged(createIndex(row, 0), createIndex(row, columnCount() - 1)); + }); } } @@ -297,16 +300,6 @@ } } -void PlaylistModel::updateVideoSender() { - Video *video = static_cast<Video *>(sender()); - if (!video) { - qDebug() << "Cannot get sender"; - return; - } - int row = rowForVideo(video); - emit dataChanged(createIndex(row, 0), createIndex(row, columnCount() - 1)); -} - void PlaylistModel::emitDataChanged() { QModelIndex index = createIndex(rowCount() - 1, 0); emit dataChanged(index, index); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/playlistmodel.h new/minitube-3.9/src/playlistmodel.h --- old/minitube-3.8.2/src/playlistmodel.h 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/playlistmodel.h 2021-06-22 01:31:51.000000000 +0200 @@ -89,7 +89,6 @@ void addVideos(const QVector<Video *> &newVideos); void searchFinished(int total); void searchError(const QString &message); - void updateVideoSender(); void emitDataChanged(); void setHoveredRow(int row); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/regionsview.cpp new/minitube-3.9/src/regionsview.cpp --- old/minitube-3.8.2/src/regionsview.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/regionsview.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -32,7 +32,7 @@ layout->setSpacing(0); l->addLayout(layout); - addRegion(YTRegions::worldwideRegion()); + addRegion(YTRegions::defaultRegion()); foreach (YTRegion region, YTRegions::list()) addRegion(region); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/searchview.cpp new/minitube-3.9/src/searchview.cpp --- old/minitube-3.8.2/src/searchview.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/searchview.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -488,7 +488,7 @@ #ifdef APP_ACTIVATION oneYearUsage = (QDateTime::currentSecsSinceEpoch() - Activation::instance().getLicenseTimestamp()) > 86400 * 365; -#elif APP_MAC_STORE +#elif defined APP_MAC_STORE oneYearUsage = false; #endif if (oneYearUsage) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/standardfeedsview.cpp new/minitube-3.9/src/standardfeedsview.cpp --- old/minitube-3.8.2/src/standardfeedsview.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/standardfeedsview.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -29,6 +29,8 @@ #include "ivvideolist.h" #include "videoapi.h" +#include "ytjstrending.h" + StandardFeedsView::StandardFeedsView(QWidget *parent) : View(parent), layout(0) { setBackgroundRole(QPalette::Base); setAutoFillBackground(true); @@ -46,12 +48,26 @@ YTRegion region = YTRegions::currentRegion(); + // TODO consolidate in YT if (VideoAPI::impl() == VideoAPI::YT3) { YTCategories *youTubeCategories = new YTCategories(this); connect(youTubeCategories, SIGNAL(categoriesLoaded(const QVector<YTCategory> &)), SLOT(layoutCategories(const QVector<YTCategory> &))); youTubeCategories->loadCategories(); addVideoSourceWidget(buildStandardFeed("most_popular", tr("Most Popular"))); + } else if (VideoAPI::impl() == VideoAPI::JS) { + const QMap<QString, QString> pages = {{"default", tr("Trending")}, + {"music", tr("Music")}, + {"movies", tr("Movies")}, + {"gaming", tr("Gaming")}}; + auto i = pages.constBegin(); + while (i != pages.constEnd()) { + addVideoSourceWidget( + new YTJSTrending(i.value(), {{"page", i.key()}, {"geoLocation", region.id}})); + ++i; + } + + setUpdatesEnabled(true); } else { QString regionParam = "region=" + region.id; addVideoSourceWidget(new IVVideoList("popular?" + regionParam, tr("Most Popular"))); @@ -88,7 +104,7 @@ connect(w, SIGNAL(unavailable(VideoSourceWidget *)), SLOT(removeVideoSourceWidget(VideoSourceWidget *))); int i = layout->count(); - const int cols = VideoAPI::impl() == VideoAPI::YT3 ? 5 : 3; + const int cols = VideoAPI::impl() == VideoAPI::YT3 ? 5 : 2; layout->addWidget(w, i / cols, i % cols); } @@ -107,7 +123,7 @@ } const int itemCount = items.size(); - const int cols = 4; // itemCount / 3; + const int cols = 2; // itemCount / 3; for (int i = itemCount - 1; i >= 0; i--) { QLayoutItem *item = items.at(i); int index = itemCount - 1 - i; @@ -155,7 +171,7 @@ } void StandardFeedsView::selectWorldwideRegion() { - YTRegions::setRegion(YTRegions::worldwideRegion().id); + YTRegions::setRegion(YTRegions::defaultRegion().id); load(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/video.cpp new/minitube-3.9/src/video.cpp --- old/minitube-3.8.2/src/video.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/video.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -29,9 +29,11 @@ #include "ytjsvideo.h" #include "ytvideo.h" +#include "variantpromise.h" + Video::Video() - : duration(0), viewCount(-1), license(LicenseYouTube), definitionCode(0), - loadingThumbnail(false), ytVideo(nullptr), ytjsVideo(nullptr) {} + : duration(0), viewCount(-1), license(LicenseYouTube), definitionCode(0), ytVideo(nullptr), + ytjsVideo(nullptr) {} Video::~Video() { qDebug() << "Deleting" << id; @@ -45,9 +47,8 @@ clone->channelId = channelId; clone->webpage = webpage; clone->streamUrl = streamUrl; - clone->thumbnail = thumbnail; - clone->thumbnailUrl = thumbnailUrl; - clone->mediumThumbnailUrl = mediumThumbnailUrl; + clone->thumbs = thumbs; + clone->thumbsNeedSorting = thumbsNeedSorting; clone->duration = duration; clone->formattedDuration = formattedDuration; clone->published = published; @@ -81,17 +82,6 @@ } } -void Video::loadThumbnail() { - if (thumbnailUrl.isEmpty() || loadingThumbnail) return; - loadingThumbnail = true; - auto reply = HttpUtils::yt().get(thumbnailUrl); - connect(reply, SIGNAL(data(QByteArray)), SLOT(setThumbnail(QByteArray))); - connect(reply, &HttpReply::error, this, [this](auto &msg) { - qWarning() << msg; - loadingThumbnail = false; - }); -} - void Video::setDuration(int value) { duration = value; formattedDuration = DataUtils::formatDuration(duration); @@ -107,17 +97,6 @@ formattedPublished = DataUtils::formatDateTime(published); } -void Video::setThumbnail(const QByteArray &bytes) { - qreal ratio = qApp->devicePixelRatio(); - thumbnail.loadFromData(bytes); - thumbnail.setDevicePixelRatio(ratio); - const int thumbWidth = PlaylistItemDelegate::thumbWidth * ratio; - if (thumbnail.width() > thumbWidth) - thumbnail = thumbnail.scaledToWidth(thumbWidth, Qt::SmoothTransformation); - emit gotThumbnail(); - loadingThumbnail = false; -} - void Video::streamUrlLoaded(const QString &streamUrl, const QString &audioUrl) { qDebug() << "Streams loaded"; this->streamUrl = streamUrl; @@ -145,7 +124,8 @@ qDebug() << msg; ytjsVideo->deleteLater(); ytjsVideo = nullptr; - loadStreamUrlYT(); + // loadStreamUrlYT(); + emit errorStreamUrl(msg); }); ytjsVideo->loadStreamUrl(); } @@ -170,10 +150,85 @@ loadStreamUrlJS(); } +bool Video::isLoadingStreamUrl() const { + return ytVideo != nullptr || ytjsVideo != nullptr; +} + void Video::abortLoadStreamUrl() { if (ytVideo) { ytVideo->disconnect(this); ytVideo->deleteLater(); ytVideo = nullptr; } + if (ytjsVideo) { + ytjsVideo->disconnect(this); + ytjsVideo->deleteLater(); + ytjsVideo = nullptr; + } +} + +void Video::addThumb(int width, int height, QString url) { + thumbs << YTThumb(width, height, url); + thumbsNeedSorting = true; +} + +VariantPromise &Video::loadThumb(QSize size, qreal pixelRatio) { + if (thumbsNeedSorting) { + std::sort(thumbs.begin(), thumbs.end(), + [](auto a, auto b) { return a.getWidth() < b.getWidth(); }); + thumbsNeedSorting = false; + } + + auto promise = new VariantPromise(this); + if (thumbs.isEmpty()) { + QTimer::singleShot(0, promise, [promise] { promise->reject("Empty thumbs"); }); + return *promise; + } + + auto reallyLoad = [this, promise, size, pixelRatio](auto &&self, + YTThumb *previous = nullptr) -> void { + YTThumb *selected = nullptr; + + static bool fallback = false; + if (fallback) { + qDebug() << "Doing fallback loop"; + bool skip = previous != nullptr; + for (int i = thumbs.size() - 1; i >= 0; --i) { + auto &thumb = thumbs.at(i); + if (!skip) { + selected = (YTThumb *)&thumb; + qDebug() << "selected" << selected->getUrl(); + break; + } + if (&thumb == previous) skip = false; + } + } else { + bool skip = previous != nullptr; + for (auto &thumb : qAsConst(thumbs)) { + if (!skip && thumb.getWidth() * pixelRatio >= size.width() && + thumb.getHeight() * pixelRatio >= size.height()) { + selected = (YTThumb *)&thumb; + qDebug() << "selected" << selected->getUrl(); + break; + } + if (&thumb == previous) skip = false; + } + } + if (!selected && !fallback) { + qDebug() << "Falling back"; + fallback = true; + self(self); + return; + } + if (selected) { + qDebug() << "Loading" << selected->getUrl(); + selected->load(promise) + .then([promise](auto variant) { promise->resolve(variant); }) + .onFailed([self, selected] { self(self, selected); }); + } else + promise->reject("No thumb"); + }; + reallyLoad(reallyLoad); + + return *promise; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/video.h new/minitube-3.9/src/video.h --- old/minitube-3.8.2/src/video.h 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/video.h 2021-06-22 01:31:51.000000000 +0200 @@ -24,8 +24,11 @@ #include <QtCore> #include <QtGui> +#include "ytthumb.h" + class YTVideo; class YTJSVideo; +class VariantPromise; class Video : public QObject { Q_OBJECT @@ -53,18 +56,6 @@ const QString &getWebpage(); void setWebpage(const QString &value); - void loadThumbnail(); - const QPixmap &getThumbnail() const { return thumbnail; } - - const QString &getThumbnailUrl() const { return thumbnailUrl; } - void setThumbnailUrl(const QString &value) { thumbnailUrl = value; } - - const QString &getMediumThumbnailUrl() const { return mediumThumbnailUrl; } - void setMediumThumbnailUrl(const QString &value) { mediumThumbnailUrl = value; } - - const QString &getLargeThumbnailUrl() const { return largeThumbnailUrl; } - void setLargeThumbnailUrl(const QString &value) { largeThumbnailUrl = value; } - int getDuration() const { return duration; } void setDuration(int value); const QString &getFormattedDuration() const { return formattedDuration; } @@ -81,7 +72,7 @@ void loadStreamUrl(); const QString &getStreamUrl() { return streamUrl; } - bool isLoadingStreamUrl() const { return ytVideo != nullptr; } + bool isLoadingStreamUrl() const; void abortLoadStreamUrl(); const QString &getId() const { return id; } @@ -90,15 +81,16 @@ License getLicense() const { return license; } void setLicense(License value) { license = value; } + const auto &getThumbs() const { return thumbs; } + void addThumb(int width, int height, QString url); + VariantPromise &loadThumb(QSize size, qreal pixelRatio); + signals: - void gotThumbnail(); - void gotMediumThumbnail(const QByteArray &bytes); - void gotLargeThumbnail(const QByteArray &bytes); void gotStreamUrl(const QString &videoUrl, const QString &audioUrl); void errorStreamUrl(const QString &message); + void changed(); private slots: - void setThumbnail(const QByteArray &bytes); void streamUrlLoaded(const QString &streamUrl, const QString &audioUrl); private: @@ -111,10 +103,6 @@ QString channelId; QString webpage; QString streamUrl; - QPixmap thumbnail; - QString thumbnailUrl; - QString mediumThumbnailUrl; - QString largeThumbnailUrl; int duration; QString formattedDuration; @@ -126,10 +114,11 @@ QString id; int definitionCode; - bool loadingThumbnail; - YTVideo *ytVideo; YTJSVideo *ytjsVideo; + + QVector<YTThumb> thumbs; + bool thumbsNeedSorting = false; }; // This is required in order to use QPointer<Video> as a QVariant diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/videosourcewidget.cpp new/minitube-3.9/src/videosourcewidget.cpp --- old/minitube-3.8.2/src/videosourcewidget.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/videosourcewidget.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -19,12 +19,13 @@ $END_LICENSE */ #include "videosourcewidget.h" -#include "videosource.h" -#include "video.h" #include "fontutils.h" -#include "iconutils.h" #include "http.h" #include "httputils.h" +#include "iconutils.h" +#include "variantpromise.h" +#include "video.h" +#include "videosource.h" VideoSourceWidget::VideoSourceWidget(VideoSource *videoSource, QWidget *parent) : GridWidget(parent), @@ -32,9 +33,7 @@ lastPixelRatio(0) { videoSource->setParent(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - loadPreview(); - connect(this, SIGNAL(activated()), SLOT(activate())); } @@ -50,13 +49,15 @@ return; } Video *video = videos.at(0); - lastPixelRatio = window()->devicePixelRatio(); - bool needLargeThumb = lastPixelRatio > 1.0 || window()->width() > 1000; - QString url = needLargeThumb ? video->getLargeThumbnailUrl() : video->getMediumThumbnailUrl(); - if (url.isEmpty()) url = video->getThumbnailUrl(); - video->deleteLater(); - QObject *reply = HttpUtils::yt().get(url); - connect(reply, SIGNAL(data(QByteArray)), SLOT(setPixmapData(QByteArray))); + lastPixelRatio = devicePixelRatio(); + + video->loadThumb(size(), lastPixelRatio) + .then([this](auto variant) { setPixmapData(variant.toByteArray()); }) + .onFailed([](auto msg) { qDebug() << msg; }) + .finally([videos] { + for (auto v : videos) + v->deleteLater(); + }); } void VideoSourceWidget::setPixmapData(const QByteArray &bytes) { @@ -75,7 +76,7 @@ const int s = height() / 2; const int padding = s / 8; - qreal ratio = window()->devicePixelRatio(); + qreal ratio = devicePixelRatio(); QPixmap playIcon = QPixmap(s * ratio, s * ratio); playIcon.setDevicePixelRatio(ratio); playIcon.fill(Qt::transparent); @@ -101,8 +102,9 @@ void VideoSourceWidget::paintEvent(QPaintEvent *event) { GridWidget::paintEvent(event); + // if (devicePixelRatio() != lastPixelRatio) loadPreview(); + if (pixmap.isNull()) return; - if (window()->devicePixelRatio() != lastPixelRatio) loadPreview(); QPainter p(this); @@ -149,7 +151,7 @@ QString name = videoSource->getName(); bool tooBig = false; p.save(); - p.setFont(FontUtils::medium()); + p.setFont(FontUtils::big()); QRect textBox = p.boundingRect(nameBox, Qt::AlignCenter | Qt::TextWordWrap, name); if (textBox.height() > nameBox.height()) { p.setFont(font()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/invidious/ivlistparser.cpp new/minitube-3.9/src/yt/invidious/ivlistparser.cpp --- old/minitube-3.8.2/src/yt/invidious/ivlistparser.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/invidious/ivlistparser.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -42,16 +42,9 @@ video->setTitle(title); video->setDescription(item[QLatin1String("descriptionHtml")].toString()); - const auto thumbnails = item[QLatin1String("videoThumbnails")].toArray(); - for (const auto &thumbnail : thumbnails) { - auto q = thumbnail["quality"]; - if (q == QLatin1String("medium")) { - video->setThumbnailUrl(thumbnail["url"].toString()); - } else if (q == QLatin1String("high")) { - video->setMediumThumbnailUrl(thumbnail["url"].toString()); - } else if (q == QLatin1String("sddefault")) { - video->setLargeThumbnailUrl(thumbnail["url"].toString()); - } + const auto thumbs = item[QLatin1String("videoThumbnails")].toArray(); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), t["url"].toString()); } video->setChannelTitle(item[QLatin1String("author")].toString()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/yt.pri new/minitube-3.9/src/yt/yt.pri --- old/minitube-3.8.2/src/yt/yt.pri 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/yt.pri 2021-06-22 01:31:51.000000000 +0200 @@ -6,10 +6,12 @@ HEADERS += \ $$PWD/searchvideosource.h \ - $$PWD/singlevideosource.h + $$PWD/singlevideosource.h \ + $$PWD/ytthumb.h SOURCES += \ $$PWD/searchvideosource.cpp \ - $$PWD/singlevideosource.cpp + $$PWD/singlevideosource.cpp \ + $$PWD/ytthumb.cpp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjs.pri new/minitube-3.9/src/yt/ytjs/ytjs.pri --- old/minitube-3.8.2/src/yt/ytjs/ytjs.pri 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/ytjs/ytjs.pri 2021-06-22 01:31:51.000000000 +0200 @@ -6,6 +6,7 @@ $$PWD/ytjschannelsource.h \ $$PWD/ytjssearch.h \ $$PWD/ytjssinglevideosource.h \ + $$PWD/ytjstrending.h \ $$PWD/ytjsvideo.h SOURCES += \ @@ -13,4 +14,5 @@ $$PWD/ytjschannelsource.cpp \ $$PWD/ytjssearch.cpp \ $$PWD/ytjssinglevideosource.cpp \ + $$PWD/ytjstrending.cpp \ $$PWD/ytjsvideo.cpp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjschannel.cpp new/minitube-3.9/src/yt/ytjs/ytjschannel.cpp --- old/minitube-3.8.2/src/yt/ytjs/ytjschannel.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/ytjs/ytjschannel.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -18,10 +18,10 @@ const auto thumbs = obj["authorThumbnails"].toArray(); int maxFoundWidth = 0; for (const auto &thumbObj : thumbs) { - QString url = thumbObj["url"].toString(); int width = thumbObj["width"].toInt(); if (width > maxFoundWidth) { maxFoundWidth = width; + QString url = thumbObj["url"].toString(); thumbnailUrl = url; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjschannelsource.cpp new/minitube-3.9/src/yt/ytjs/ytjschannelsource.cpp --- old/minitube-3.8.2/src/yt/ytjs/ytjschannelsource.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/ytjs/ytjschannelsource.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -99,15 +99,9 @@ video->setDescription(desc); const auto thumbs = i["videoThumbnails"].toArray(); - for (const auto &thumbObj : thumbs) { - QString url = thumbObj["url"].toString(); - int width = thumbObj["width"].toInt(); - if (width >= 336) - video->setLargeThumbnailUrl(url); - else if (width >= 246) - video->setMediumThumbnailUrl(url); - else if (width >= 168) - video->setThumbnailUrl(url); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), + t["url"].toString()); } int views = i["viewCount"].toInt(); @@ -134,13 +128,10 @@ emit gotVideos(videos); emit finished(videos.size()); }) - .onError([this, &js, max, startIndex](auto &msg) { + .onError([this, max, startIndex](auto &msg) { static int retries = 0; if (retries < 3) { qDebug() << "Retrying..."; - auto nam = js.getEngine().networkAccessManager(); - nam->clearAccessCache(); - nam->setCookieJar(new QNetworkCookieJar()); QTimer::singleShot(0, this, [this, max, startIndex] { loadVideos(max, startIndex); }); retries++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjssearch.cpp new/minitube-3.9/src/yt/ytjs/ytjssearch.cpp --- old/minitube-3.8.2/src/yt/ytjs/ytjssearch.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/ytjs/ytjssearch.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -127,6 +127,8 @@ addFilter("Duration", "Short"); break; case SearchParams::DurationMedium: + addFilter("Duration", "Medium"); + break; case SearchParams::DurationLong: addFilter("Duration", "Long"); break; @@ -142,6 +144,9 @@ case SearchParams::TimeMonth: addFilter("Upload date", "This month"); break; + case SearchParams::TimeYear: + addFilter("Upload date", "This year"); + break; } switch (searchParams->quality()) { @@ -183,8 +188,11 @@ QString desc = i["description"].toString(); video->setDescription(desc); - QString thumb = i["thumbnail"].toString(); - video->setThumbnailUrl(thumb); + const auto thumbs = i["thumbnails"].toArray(); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), + t["url"].toString()); + } int views = i["views"].toInt(); video->setViewCount(views); @@ -211,13 +219,10 @@ emit finished(videos.size()); } }) - .onError([this, &js, max, startIndex](auto &msg) { + .onError([this, max, startIndex](auto &msg) { static int retries = 0; if (retries < 3) { qDebug() << "Retrying..."; - auto nam = js.getEngine().networkAccessManager(); - nam->clearAccessCache(); - nam->setCookieJar(new QNetworkCookieJar()); QTimer::singleShot(0, this, [this, max, startIndex] { loadVideos(max, startIndex); }); retries++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjssinglevideosource.cpp new/minitube-3.9/src/yt/ytjs/ytjssinglevideosource.cpp --- old/minitube-3.8.2/src/yt/ytjs/ytjssinglevideosource.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt/ytjs/ytjssinglevideosource.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -3,6 +3,34 @@ #include "js.h" #include "video.h" +namespace { + +QDateTime parsePublishedText(const QString &s) { + int num = 0; + const auto parts = s.splitRef(' '); + for (const auto &part : parts) { + num = part.toInt(); + if (num > 0) break; + } + if (num == 0) return QDateTime(); + + auto now = QDateTime::currentDateTimeUtc(); + if (s.contains("hour")) { + return now.addSecs(-num * 3600); + } else if (s.contains("day")) { + return now.addDays(-num); + } else if (s.contains("week")) { + return now.addDays(-num * 7); + } else if (s.contains("month")) { + return now.addMonths(-num); + } else if (s.contains("year")) { + return now.addDays(-num * 365); + } + return QDateTime(); +} + +} // namespace + YTJSSingleVideoSource::YTJSSingleVideoSource(QObject *parent) : VideoSource(parent), video(nullptr) {} @@ -26,12 +54,12 @@ if (aborted) return; auto obj = doc.object(); - // qDebug() << doc.toJson(); auto parseVideoObject = [](QJsonObject i) { Video *video = new Video(); QString id = i["id"].toString(); + if (id.isEmpty()) id = i["videoId"].toString(); video->setId(id); QString title = i["title"].toString(); @@ -39,31 +67,36 @@ QString desc = i["description"].toString(); if (desc.isEmpty()) desc = i["desc"].toString(); + if (desc.isEmpty()) desc = i["shortDescription"].toString(); + video->setDescription(desc); const auto thumbs = i["thumbnails"].toArray(); - for (const auto &thumb : thumbs) { - QString url = thumb["url"].toString(); - int width = thumb["width"].toInt(); - if (width >= 336) - video->setLargeThumbnailUrl(url); - else if (width >= 246) - video->setMediumThumbnailUrl(url); - else if (width >= 168) - video->setThumbnailUrl(url); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), + t["url"].toString()); } - int views = i["view_count"].toInt(); + qDebug() << i["view_count"] << i["viewCount"]; + int views = i["view_count"].toString().toInt(); + if (views == 0) views = i["viewCount"].toString().toInt(); video->setViewCount(views); int duration = i["length_seconds"].toInt(); video->setDuration(duration); - QString channelId = i["ucid"].toString(); - video->setChannelId(channelId); + auto author = i["author"]; + if (author.isObject()) { + auto authorObject = author.toObject(); + video->setChannelId(authorObject["id"].toString()); + video->setChannelTitle(authorObject["name"].toString()); + } else if (author.isString()) { + video->setChannelId(i["ucid"].toString()); + video->setChannelTitle(author.toString()); + } - QString channelName = i["author"].toString(); - video->setChannelTitle(channelName); + auto published = parsePublishedText(i["published"].toString()); + if (published.isValid()) video->setPublished(published); return video; }; @@ -71,8 +104,12 @@ QVector<Video *> videos; if (!video) { - // parse video details - videos << parseVideoObject(obj["videoDetails"].toObject()); + // first video + auto video = parseVideoObject(obj["videoDetails"].toObject()); + videos << video; + name = video->getTitle(); + qDebug() << "Emitting name changed" << name; + emit nameChanged(name); } const auto items = obj["related_videos"].toArray(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjstrending.cpp new/minitube-3.9/src/yt/ytjs/ytjstrending.cpp --- old/minitube-3.8.2/src/yt/ytjs/ytjstrending.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/src/yt/ytjs/ytjstrending.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,104 @@ +#include "ytjstrending.h" + +#include "js.h" +#include "video.h" + +namespace { + +QDateTime parsePublishedText(const QString &s) { + int num = 0; + const auto parts = s.splitRef(' '); + for (const auto &part : parts) { + num = part.toInt(); + if (num > 0) break; + } + if (num == 0) return QDateTime(); + + auto now = QDateTime::currentDateTimeUtc(); + if (s.contains("hour")) { + return now.addSecs(-num * 3600); + } else if (s.contains("day")) { + return now.addDays(-num); + } else if (s.contains("week")) { + return now.addDays(-num * 7); + } else if (s.contains("month")) { + return now.addMonths(-num); + } else if (s.contains("year")) { + return now.addDays(-num * 365); + } + return QDateTime(); +} + +} // namespace + +YTJSTrending::YTJSTrending(QString name, QVariantMap params, QObject *parent) + : VideoSource(parent), name(name), params(params) {} + +void YTJSTrending::loadVideos(int max, int startIndex) { + aborted = false; + + auto &js = JS::instance(); + + QJSValue options = js.getEngine().toScriptValue(params); + + js.callFunction(new JSResult(this), "trending", {options}) + .onJson([this](auto &doc) { + const auto items = doc.array(); + + QVector<Video *> videos; + videos.reserve(items.size()); + + for (const auto &i : items) { + QString type = i["type"].toString(); + if (type != "video") continue; + + Video *video = new Video(); + + QString id = i["videoId"].toString(); + video->setId(id); + + QString title = i["title"].toString(); + video->setTitle(title); + + QString desc = i["description"].toString(); + if (desc.isEmpty()) desc = i["desc"].toString(); + video->setDescription(desc); + + const auto thumbs = i["videoThumbnails"].toArray(); + for (const auto &t : thumbs) { + video->addThumb(t["width"].toInt(), t["height"].toInt(), + t["url"].toString()); + } + + int views = i["viewCount"].toInt(); + video->setViewCount(views); + + int duration = i["lengthSeconds"].toInt(); + video->setDuration(duration); + + auto published = parsePublishedText(i["publishedText"].toString()); + if (published.isValid()) video->setPublished(published); + + QString channelName = i["author"].toString(); + video->setChannelTitle(channelName); + QString channelId = i["authorId"].toString(); + video->setChannelId(channelId); + + videos << video; + } + + emit gotVideos(videos); + emit finished(videos.size()); + }) + .onError([this, max, startIndex](auto &msg) { + static int retries = 0; + if (retries < 3) { + qDebug() << "Retrying..."; + QTimer::singleShot(0, this, + [this, max, startIndex] { loadVideos(max, startIndex); }); + retries++; + } else { + emit error(msg); + } + }); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytjs/ytjstrending.h new/minitube-3.9/src/yt/ytjs/ytjstrending.h --- old/minitube-3.8.2/src/yt/ytjs/ytjstrending.h 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/src/yt/ytjs/ytjstrending.h 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,23 @@ +#ifndef YTJSTRENDING_H +#define YTJSTRENDING_H + +#include "videosource.h" + +class Video; + +class YTJSTrending : public VideoSource { + Q_OBJECT + +public: + YTJSTrending(QString name, QVariantMap params, QObject *parent = 0); + void loadVideos(int max, int startIndex); + void abort() { aborted = true; } + QString getName() { return name; } + +private: + const QString name; + const QVariantMap params; + bool aborted = false; +}; + +#endif // YTJSTRENDING_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytthumb.cpp new/minitube-3.9/src/yt/ytthumb.cpp --- old/minitube-3.8.2/src/yt/ytthumb.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/src/yt/ytthumb.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,28 @@ +#include "ytthumb.h" + +#include "http.h" +#include "httpreply.h" +#include "httputils.h" + +YTThumb::YTThumb(int width, int height, const QString &url) + : width(width), height(height), url(url) {} + +VariantPromise &YTThumb::load(QObject *parent) { + qDebug() << parent; + if (promise) { + qDebug() << "Already loading" << promise; + return *promise; + } + promise = new VariantPromise(parent); + promise->connect(HttpUtils::yt().get(url), &HttpReply::finished, promise, [this](auto &reply) { + // clear promise member before emitting signals + auto promise2 = promise; + promise = nullptr; + if (reply.isSuccessful()) { + promise2->resolve(reply.body()); + } else { + promise2->reject(reply.reasonPhrase()); + } + }); + return *promise; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt/ytthumb.h new/minitube-3.9/src/yt/ytthumb.h --- old/minitube-3.8.2/src/yt/ytthumb.h 1970-01-01 01:00:00.000000000 +0100 +++ new/minitube-3.9/src/yt/ytthumb.h 2021-06-22 01:31:51.000000000 +0200 @@ -0,0 +1,25 @@ +#ifndef YTTHUMB_H +#define YTTHUMB_H + +#include <QtCore> + +#include "variantpromise.h" + +class YTThumb { +public: + YTThumb() {} // needed by QVector + YTThumb(int width, int height, const QString &url); + int getWidth() const { return width; } + int getHeight() const { return height; } + const QString &getUrl() const { return url; } + + VariantPromise &load(QObject *parent); + +private: + int width; + int height; + QString url; + VariantPromise *promise = nullptr; +}; + +#endif // YTTHUMB_H diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/yt3listparser.cpp new/minitube-3.9/src/yt3listparser.cpp --- old/minitube-3.8.2/src/yt3listparser.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/yt3listparser.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -59,10 +59,7 @@ video->setDescription(snippet[QLatin1String("description")].toString()); QJsonObject thumbnails = snippet[QLatin1String("thumbnails")].toObject(); - QLatin1String url("url"); - video->setThumbnailUrl(thumbnails[QLatin1String("medium")].toObject()[url].toString()); - video->setMediumThumbnailUrl(thumbnails[QLatin1String("high")].toObject()[url].toString()); - video->setLargeThumbnailUrl(thumbnails[QLatin1String("standard")].toObject()[url].toString()); + // TODO video->setChannelTitle(snippet[QLatin1String("channelTitle")].toString()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/ytregions.cpp new/minitube-3.9/src/ytregions.cpp --- old/minitube-3.8.2/src/ytregions.cpp 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/ytregions.cpp 2021-06-22 01:31:51.000000000 +0200 @@ -95,8 +95,8 @@ return region; } -const YTRegion &YTRegions::worldwideRegion() { - static const YTRegion region = {"", tr("Worldwide")}; +const YTRegion &YTRegions::defaultRegion() { + static const YTRegion region = {"US", tr("United States")}; return region; } @@ -115,11 +115,11 @@ } const YTRegion &YTRegions::regionById(const QString &id) { - if (id.isEmpty()) return worldwideRegion(); + if (id.isEmpty()) return defaultRegion(); for (const YTRegion &r : list()) { if (r.id == id) return r; } - return worldwideRegion(); + return defaultRegion(); } QIcon YTRegions::iconForRegionId(const QString ®ionId) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/minitube-3.8.2/src/ytregions.h new/minitube-3.9/src/ytregions.h --- old/minitube-3.8.2/src/ytregions.h 2021-04-02 01:43:59.000000000 +0200 +++ new/minitube-3.9/src/ytregions.h 2021-06-22 01:31:51.000000000 +0200 @@ -38,7 +38,7 @@ public: static const QVector<YTRegion> & list(); static const YTRegion & localRegion(); - static const YTRegion & worldwideRegion(); + static const YTRegion & defaultRegion(); static void setRegion(const QString ®ionId); static QString currentRegionId(); static const YTRegion ¤tRegion();
