+++ /dev/null
-BasedOnStyle: LLVM
-IndentWidth: 4
-AccessModifierOffset: -4
-ColumnLimit: 100
-AllowShortIfStatementsOnASingleLine: true
-AllowShortFunctionsOnASingleLine: Inline
-KeepEmptyLinesAtTheStartOfBlocks: false
-ContinuationIndentWidth: 8
-AlignAfterOpenBracket: true
-BinPackParameters: false
-AllowAllParametersOfDeclarationOnNextLine: false
+++ /dev/null
-build/
-Makefile*
-minitube.pro.user
-.settings/
-.DS_Store
-.cproject
-.project
-local/
-*.swp
-.tx
-android
-qtc_packaging
-debian
-
-
-*.stash
+<p align="center">
+<img src="https://flavio.tordini.org/files/products/minitube.png">
+</p>
+
# Minitube
Minitube is a YouTube desktop application. It is written in C++ using the Qt framework. Contributing is welcome, especially in the Linux desktop integration area.
-## Translating Minitube to your language
-Translations are done at https://www.transifex.com/projects/p/minitube/
+## Translating to your language
+Translations are done at https://www.transifex.com/flaviotordini/minitube/
Just register and apply for a language team. Please don't request translation merges on GitHub.
## Google API Key
Google is now requiring an API key in order to access YouTube Data web services.
-Create a "Browser Key" at https://console.developers.google.com
+Create a "Browser Key" at https://console.developers.google.com and enable the Youtube Data API.
The key must be specified at compile time as shown below.
Alternatively Minitube can read an API key from the GOOGLE_API_KEY environment variable.
## Build instructions
-To compile Minitube you need at least Qt 5.0. The following Qt modules are needed: core, gui, widgets, network, sql (using the Sqlite plugin), declarative, dbus.
+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.
To be able to build on a Debian (or derivative) system:
- $ sudo apt-get install build-essential qttools5-dev-tools qt5-qmake qtdeclarative5-dev libphonon4qt5-dev libqt5sql5-sqlite qt5-default
+ sudo apt install build-essential qt5-default qttools5-dev-tools qt5-qmake qtdeclarative5-dev libqt5sql5-sqlite libqt5x11extras5-dev libmpv-dev
Compiling:
- $ qmake "DEFINES += APP_GOOGLE_API_KEY=YourAPIKeyHere"
- $ make
-
-Beware of the Qt 4 version of qmake!
+ qmake "DEFINES += APP_GOOGLE_API_KEY=YourAPIKeyHere"
+ make
Running:
- $ build/target/minitube
-
-Installing on Linux:
+ build/target/minitube
- $ sudo make install
+Installing on Linux:
This is for packagers. End users should not install applications in this way.
-## A word about Phonon on Linux
-To be able to actually watch videos you need a working Phonon setup.
-Please don't report bugs about this, ask for help on your distribution support channels.
+ sudo make install
## Legal Stuff
Copyright (C) Flavio Tordini
--- /dev/null
+/usr/bin/*
+/usr/share/*
override_dh_auto_install::
dh_auto_install --destdir=debian/tmp
+ chmod a-x debian/tmp/usr/share/minitube/sounds/snapshot.wav
override_dh_missing::
dh_missing --fail-missing
<source>Translate %1 to your native language using %2</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation type="unfinished"></translation>
<message numerus="yes">
<source>You have %n new video(s)</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
</context>
<source>Show Updated</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>All Videos</source>
<translation type="unfinished"></translation>
<source>There are no updated subscriptions at this time.</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation type="unfinished"></translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<message numerus="yes">
<source>%n hour(s) ago</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation type="unfinished"></translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
</context>
<message numerus="yes">
<source>%n Download(s)</source>
<translation type="unfinished">
- <numerusform></numerusform>
+ <numerusform></numerusform><numerusform></numerusform>
</translation>
</message>
</context>
</context>
<context>
<name>MainWindow</name>
+ <message>
+ <source>&Window</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&Minimize</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>&Stop</source>
<translation type="unfinished"></translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"></translation>
<source>Remaining time: %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Volume at %1%</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>Volume is muted</source>
<translation type="unfinished"></translation>
<source>Update</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <source>Toggle &Menu Bar</source>
- <translation type="unfinished"></translation>
- </message>
<message>
<source>You can still access the menu bar by pressing the ALT key</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <source>&Window</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>&Minimize</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Menu</source>
- <translation type="unfinished"></translation>
- </message>
</context>
<context>
<name>MediaView</name>
<source>Subscribe to %1</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"></translation>
<source>Install Update</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <source>Pick a video</source>
- <translation type="unfinished"></translation>
- </message>
</context>
<context>
<name>PasteLineEdit</name>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
+ <source>Pick a video</source>
<translation type="unfinished"></translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation type="unfinished"></translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation type="unfinished"></translation>
- </message>
<message>
<source>Show %1 More</source>
<translation type="unfinished"></translation>
<translation type="unfinished"></translation>
</message>
<message>
- <source>Enter</source>
+ <source>to start watching videos.</source>
<translation type="unfinished"></translation>
</message>
<message>
<translation type="unfinished"></translation>
</message>
<message>
- <source>to start watching videos.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <source>Watch</source>
+ <source>Enter</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&Back</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation type="unfinished"></translation>
--- /dev/null
+# A wrapper for the Qt Network Access API
+
+This is just a wrapper around Qt's QNetworkAccessManager and friends. I use it in my Qt apps at http://flavio.tordini.org . It allows me to add missing functionality as needed, e.g.:
+
+- Throttling (as required by many web APIs nowadays)
+- Read timeouts (don't let your requests get stuck forever)
+- Automatic retries
+- User agent and request header defaults
+- Partial requests
+- Redirection support (now supported by Qt >= 5.6)
+
+It has a simpler, higher-level API that I find easier to work with. The design emerged naturally in years of practical use.
+
+A basic example:
+
+```
+QObject *reply = Http::instance().get("https://google.com/");
+connect(reply, SIGNAL(data(QByteArray)), SLOT(onSuccess(QByteArray)));
+connect(reply, SIGNAL(error(QString)), SLOT(onError(QString)));
+
+void MyClass::onSuccess(const QByteArray &bytes) {
+ qDebug() << "Feel the bytes!" << bytes;
+}
+
+void MyClass::onError(const QString &message) {
+ qDebug() << "Something's wrong here" << message;
+}
+```
+
+This is a real-world example of building a Http object suitable to a web service. It throttles requests, uses a custom user agent and caches results:
+
+```
+Http &myHttp() {
+ static Http *http = [] {
+ Http *http = new Http;
+ http->addRequestHeader("User-Agent", userAgent());
+
+ ThrottledHttp *throttledHttp = new ThrottledHttp(*http);
+ throttledHttp->setMilliseconds(1000);
+
+ CachedHttp *cachedHttp = new CachedHttp(*throttledHttp, "mycache");
+ cachedHttp->setMaxSeconds(86400 * 30);
+
+ return cachedHttp;
+ }();
+ return *http;
+}
+```
+
+If the full power (and complexity) of QNetworkReply is needed you can always fallback to it:
+
+```
+HttpRequest req;
+req.url = "https://flavio.tordini.org/";
+QNetworkReply *reply = Http::instance().networkReply(req);
+// Use QNetworkReply as needed...
+```
+
+You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes.
+
+Cheers,
+
+Flavio
--- /dev/null
+QT *= network
+
+INCLUDEPATH += $$PWD/src
+DEPENDPATH += $$PWD/src
+
+HEADERS += \
+ $$PWD/src/cachedhttp.h \
+ $$PWD/src/http.h \
+ $$PWD/src/localcache.h \
+ $$PWD/src/throttledhttp.h
+
+SOURCES += \
+ $$PWD/src/cachedhttp.cpp \
+ $$PWD/src/http.cpp \
+ $$PWD/src/localcache.cpp \
+ $$PWD/src/throttledhttp.cpp
--- /dev/null
+TEMPLATE = lib
+include(http.pri)
--- /dev/null
+#include "cachedhttp.h"
+#include "localcache.h"
+
+namespace {
+
+QByteArray requestHash(const HttpRequest &req) {
+ const char sep = '|';
+ QByteArray s = req.url.toEncoded() + sep + req.body + sep + QByteArray::number(req.offset);
+ if (req.operation == QNetworkAccessManager::PostOperation) {
+ s.append(sep);
+ s.append("POST");
+ }
+ return LocalCache::hash(s);
+}
+} // namespace
+
+CachedHttpReply::CachedHttpReply(const QByteArray &body, const HttpRequest &req)
+ : bytes(body), req(req) {
+ QTimer::singleShot(0, this, SLOT(emitSignals()));
+}
+
+QByteArray CachedHttpReply::body() const {
+ return bytes;
+}
+
+void CachedHttpReply::emitSignals() {
+ emit data(body());
+ emit finished(*this);
+ deleteLater();
+}
+
+WrappedHttpReply::WrappedHttpReply(LocalCache *cache, const QByteArray &key, HttpReply *httpReply)
+ : HttpReply(httpReply), cache(cache), key(key), httpReply(httpReply) {
+ connect(httpReply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray)));
+ connect(httpReply, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ connect(httpReply, SIGNAL(finished(HttpReply)), SLOT(originFinished(HttpReply)));
+}
+
+void WrappedHttpReply::originFinished(const HttpReply &reply) {
+ if (reply.isSuccessful()) cache->insert(key, reply.body());
+ emit finished(reply);
+}
+
+CachedHttp::CachedHttp(Http &http, const char *name)
+ : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {}
+
+void CachedHttp::setMaxSeconds(uint seconds) {
+ cache->setMaxSeconds(seconds);
+}
+
+void CachedHttp::setMaxSize(uint maxSize) {
+ cache->setMaxSize(maxSize);
+}
+
+HttpReply *CachedHttp::request(const HttpRequest &req) {
+ bool cacheable = req.operation == QNetworkAccessManager::GetOperation ||
+ (cachePostRequests && req.operation == QNetworkAccessManager::PostOperation);
+ if (!cacheable) {
+ qDebug() << "Not cacheable" << req.url;
+ return http.request(req);
+ }
+ const QByteArray key = requestHash(req);
+ const QByteArray value = cache->value(key);
+ if (!value.isNull()) {
+ qDebug() << "CachedHttp HIT" << req.url;
+ return new CachedHttpReply(value, req);
+ }
+ qDebug() << "CachedHttp MISS" << req.url.toString();
+ return new WrappedHttpReply(cache, key, http.request(req));
+}
--- /dev/null
+#ifndef CACHEDHTTP_H
+#define CACHEDHTTP_H
+
+#include "http.h"
+
+class LocalCache;
+
+class CachedHttp : public Http {
+public:
+ CachedHttp(Http &http = Http::instance(), const char *name = "http");
+ void setMaxSeconds(uint seconds);
+ void setMaxSize(uint maxSize);
+ void setCachePostRequests(bool value) { cachePostRequests = value; }
+ HttpReply *request(const HttpRequest &req);
+
+private:
+ Http &http;
+ LocalCache *cache;
+ bool cachePostRequests;
+};
+
+class CachedHttpReply : public HttpReply {
+ Q_OBJECT
+
+public:
+ CachedHttpReply(const QByteArray &body, const HttpRequest &req);
+ QUrl url() const { return req.url; }
+ int statusCode() const { return 200; }
+ QByteArray body() const;
+
+private slots:
+ void emitSignals();
+
+private:
+ const QByteArray bytes;
+ const HttpRequest req;
+};
+
+class WrappedHttpReply : public HttpReply {
+ Q_OBJECT
+
+public:
+ WrappedHttpReply(LocalCache *cache, const QByteArray &key, HttpReply *httpReply);
+ QUrl url() const { return httpReply->url(); }
+ int statusCode() const { return httpReply->statusCode(); }
+ QByteArray body() const { return httpReply->body(); }
+
+private slots:
+ void originFinished(const HttpReply &reply);
+
+private:
+ LocalCache *cache;
+ QByteArray key;
+ HttpReply *httpReply;
+};
+
+#endif // CACHEDHTTP_H
--- /dev/null
+#include "http.h"
+
+namespace {
+
+QNetworkAccessManager *createNetworkAccessManager() {
+ QNetworkAccessManager *nam = new QNetworkAccessManager();
+ return nam;
+}
+
+QNetworkAccessManager *networkAccessManager() {
+ static QMap<QThread *, QNetworkAccessManager *> nams;
+ QThread *t = QThread::currentThread();
+ QMap<QThread *, QNetworkAccessManager *>::const_iterator i = nams.constFind(t);
+ if (i != nams.constEnd()) return i.value();
+ QNetworkAccessManager *nam = createNetworkAccessManager();
+ nams.insert(t, nam);
+ return nam;
+}
+
+int defaultReadTimeout = 10000;
+} // namespace
+
+Http::Http() : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout) {}
+
+void Http::setRequestHeaders(const QMap<QByteArray, QByteArray> &headers) {
+ requestHeaders = headers;
+}
+
+QMap<QByteArray, QByteArray> &Http::getRequestHeaders() {
+ return requestHeaders;
+}
+
+void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) {
+ requestHeaders.insert(name, value);
+}
+
+void Http::setReadTimeout(int timeout) {
+ readTimeout = timeout;
+}
+
+Http &Http::instance() {
+ static Http i;
+ return i;
+}
+
+const QMap<QByteArray, QByteArray> &Http::getDefaultRequestHeaders() {
+ static const QMap<QByteArray, QByteArray> defaultRequestHeaders = [] {
+ QMap<QByteArray, QByteArray> h;
+ h.insert("Accept-Charset", "utf-8");
+ h.insert("Connection", "Keep-Alive");
+ return h;
+ }();
+ return defaultRequestHeaders;
+}
+
+void Http::setDefaultReadTimeout(int timeout) {
+ defaultReadTimeout = timeout;
+}
+
+QNetworkReply *Http::networkReply(const HttpRequest &req) {
+ QNetworkRequest request(req.url);
+
+ QMap<QByteArray, QByteArray> &headers = requestHeaders;
+ if (!req.headers.isEmpty()) headers = req.headers;
+
+ QMap<QByteArray, QByteArray>::const_iterator it;
+ for (it = headers.constBegin(); it != headers.constEnd(); ++it)
+ request.setRawHeader(it.key(), it.value());
+
+ if (req.offset > 0)
+ request.setRawHeader("Range", QString("bytes=%1-").arg(req.offset).toUtf8());
+
+ QNetworkAccessManager *manager = networkAccessManager();
+
+ QNetworkReply *networkReply = nullptr;
+ switch (req.operation) {
+ case QNetworkAccessManager::GetOperation:
+ networkReply = manager->get(request);
+ break;
+
+ case QNetworkAccessManager::HeadOperation:
+ networkReply = manager->head(request);
+ break;
+
+ case QNetworkAccessManager::PostOperation:
+ networkReply = manager->post(request, req.body);
+ break;
+
+ default:
+ qWarning() << "Unknown operation:" << req.operation;
+ }
+
+ return networkReply;
+}
+
+HttpReply *Http::request(const HttpRequest &req) {
+ return new NetworkHttpReply(req, *this);
+}
+
+HttpReply *Http::request(const QUrl &url,
+ QNetworkAccessManager::Operation operation,
+ const QByteArray &body,
+ uint offset) {
+ HttpRequest req;
+ req.url = url;
+ req.operation = operation;
+ req.body = body;
+ req.offset = offset;
+ return request(req);
+}
+
+HttpReply *Http::get(const QUrl &url) {
+ return request(url, QNetworkAccessManager::GetOperation);
+}
+
+HttpReply *Http::head(const QUrl &url) {
+ return request(url, QNetworkAccessManager::HeadOperation);
+}
+
+HttpReply *Http::post(const QUrl &url, const QMap<QString, QString> ¶ms) {
+ QByteArray body;
+ QMapIterator<QString, QString> i(params);
+ while (i.hasNext()) {
+ i.next();
+ body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&';
+ }
+ HttpRequest req;
+ req.url = url;
+ req.operation = QNetworkAccessManager::PostOperation;
+ req.body = body;
+ req.headers = requestHeaders;
+ req.headers.insert("Content-Type", "application/x-www-form-urlencoded");
+ return request(req);
+}
+
+HttpReply *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) {
+ HttpRequest req;
+ req.url = url;
+ req.operation = QNetworkAccessManager::PostOperation;
+ req.body = body;
+ req.headers = requestHeaders;
+ QByteArray cType = contentType;
+ if (cType.isEmpty()) cType = "application/x-www-form-urlencoded";
+ req.headers.insert("Content-Type", cType);
+ return request(req);
+}
+
+NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http)
+ : http(http), req(req), retryCount(0) {
+ if (req.url.isEmpty()) {
+ qWarning() << "Empty URL";
+ }
+
+ networkReply = http.networkReply(req);
+ setParent(networkReply);
+ setupReply();
+
+ readTimeoutTimer = new QTimer(this);
+ readTimeoutTimer->setInterval(http.getReadTimeout());
+ readTimeoutTimer->setSingleShot(true);
+ connect(readTimeoutTimer, SIGNAL(timeout()), SLOT(readTimeout()), Qt::UniqueConnection);
+ readTimeoutTimer->start();
+}
+
+void NetworkHttpReply::setupReply() {
+ connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
+ SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection);
+ connect(networkReply, SIGNAL(finished()), SLOT(replyFinished()), Qt::UniqueConnection);
+ connect(networkReply, SIGNAL(downloadProgress(qint64, qint64)),
+ SLOT(downloadProgress(qint64, qint64)), Qt::UniqueConnection);
+}
+
+QString NetworkHttpReply::errorMessage() {
+ return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') +
+ reasonPhrase();
+}
+
+void NetworkHttpReply::emitError() {
+ const QString msg = errorMessage();
+#ifndef QT_NO_DEBUG_OUTPUT
+ qDebug() << "Http:" << msg;
+ if (!req.body.isEmpty()) qDebug() << "Http:" << req.body;
+#endif
+ emit error(msg);
+ emitFinished();
+}
+
+void NetworkHttpReply::emitFinished() {
+ readTimeoutTimer->stop();
+
+ // disconnect to avoid replyFinished() from being called
+ networkReply->disconnect();
+
+ emit finished(*this);
+
+ // bye bye my reply
+ // this will also delete this object and HttpReply as the QNetworkReply is their parent
+ networkReply->deleteLater();
+}
+
+void NetworkHttpReply::replyFinished() {
+ QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ if (redirection.isValid()) {
+ HttpRequest redirectReq;
+ redirectReq.url = redirection;
+ redirectReq.operation = req.operation;
+ redirectReq.body = req.body;
+ redirectReq.offset = req.offset;
+ QNetworkReply *redirectReply = http.networkReply(redirectReq);
+ setParent(redirectReply);
+ networkReply->deleteLater();
+ networkReply = redirectReply;
+ setupReply();
+ readTimeoutTimer->start();
+ return;
+ }
+
+ if (isSuccessful()) {
+ bytes = networkReply->readAll();
+ emit data(bytes);
+
+#ifndef QT_NO_DEBUG_OUTPUT
+ if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool())
+ qDebug() << networkReply->url().toString() << statusCode();
+ else
+ qDebug() << "CACHE" << networkReply->url().toString();
+#endif
+ }
+
+ emitFinished();
+}
+
+void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) {
+ Q_UNUSED(code);
+ const int status = statusCode();
+ if (retryCount <= 3 && status >= 500 && status < 600) {
+ qDebug() << "Retrying" << req.url;
+ networkReply->disconnect();
+ networkReply->deleteLater();
+ QNetworkReply *retryReply = http.networkReply(req);
+ setParent(retryReply);
+ networkReply = retryReply;
+ setupReply();
+ retryCount++;
+ readTimeoutTimer->start();
+ } else {
+ emitError();
+ return;
+ }
+}
+
+void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) {
+ // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url();
+ if (bytesReceived > 0 && readTimeoutTimer->isActive()) {
+ readTimeoutTimer->stop();
+ disconnect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), this,
+ SLOT(downloadProgress(qint64, qint64)));
+ }
+}
+
+void NetworkHttpReply::readTimeout() {
+ if (!networkReply) return;
+ networkReply->disconnect();
+ networkReply->abort();
+ networkReply->deleteLater();
+
+ if (retryCount > 3 && (networkReply->operation() != QNetworkAccessManager::GetOperation &&
+ networkReply->operation() != QNetworkAccessManager::HeadOperation)) {
+ emitError();
+ emit finished(*this);
+ return;
+ }
+
+ qDebug() << "Timeout" << req.url;
+ QNetworkReply *retryReply = http.networkReply(req);
+ setParent(retryReply);
+ networkReply = retryReply;
+ setupReply();
+ retryCount++;
+ readTimeoutTimer->start();
+}
+
+QUrl NetworkHttpReply::url() const {
+ return networkReply->url();
+}
+
+int NetworkHttpReply::statusCode() const {
+ return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+}
+
+QString NetworkHttpReply::reasonPhrase() const {
+ return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
+}
+
+const QList<QNetworkReply::RawHeaderPair> NetworkHttpReply::headers() const {
+ return networkReply->rawHeaderPairs();
+}
+
+QByteArray NetworkHttpReply::header(const QByteArray &headerName) const {
+ return networkReply->rawHeader(headerName);
+}
+
+QByteArray NetworkHttpReply::body() const {
+ return bytes;
+}
--- /dev/null
+#ifndef HTTP_H
+#define HTTP_H
+
+#include <QtNetwork>
+
+class HttpRequest {
+public:
+ HttpRequest() : operation(QNetworkAccessManager::GetOperation), offset(0) {}
+ QUrl url;
+ QNetworkAccessManager::Operation operation;
+ QByteArray body;
+ uint offset;
+ QMap<QByteArray, QByteArray> headers;
+};
+
+class HttpReply : public QObject {
+ Q_OBJECT
+
+public:
+ HttpReply(QObject *parent = nullptr) : QObject(parent) {}
+ virtual QUrl url() const = 0;
+ virtual int statusCode() const = 0;
+ int isSuccessful() const { return statusCode() >= 200 && statusCode() < 300; }
+ virtual QString reasonPhrase() const { return QString(); }
+ virtual const QList<QNetworkReply::RawHeaderPair> headers() const {
+ return QList<QNetworkReply::RawHeaderPair>();
+ }
+ virtual QByteArray header(const QByteArray &headerName) const {
+ Q_UNUSED(headerName);
+ return QByteArray();
+ }
+
+ virtual QByteArray body() const = 0;
+
+signals:
+ void data(const QByteArray &bytes);
+ void error(const QString &message);
+ void finished(const HttpReply &reply);
+};
+
+class Http {
+public:
+ static Http &instance();
+ static const QMap<QByteArray, QByteArray> &getDefaultRequestHeaders();
+ static void setDefaultReadTimeout(int timeout);
+
+ Http();
+
+ void setRequestHeaders(const QMap<QByteArray, QByteArray> &headers);
+ QMap<QByteArray, QByteArray> &getRequestHeaders();
+ void addRequestHeader(const QByteArray &name, const QByteArray &value);
+
+ void setReadTimeout(int timeout);
+ int getReadTimeout() { return readTimeout; }
+
+ QNetworkReply *networkReply(const HttpRequest &req);
+ virtual HttpReply *request(const HttpRequest &req);
+ HttpReply *
+ request(const QUrl &url,
+ QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
+ const QByteArray &body = QByteArray(),
+ uint offset = 0);
+ HttpReply *get(const QUrl &url);
+ HttpReply *head(const QUrl &url);
+ HttpReply *post(const QUrl &url, const QMap<QString, QString> ¶ms);
+ HttpReply *post(const QUrl &url, const QByteArray &body, const QByteArray &contentType);
+
+private:
+ QMap<QByteArray, QByteArray> requestHeaders;
+ int readTimeout;
+};
+
+class NetworkHttpReply : public HttpReply {
+ Q_OBJECT
+
+public:
+ NetworkHttpReply(const HttpRequest &req, Http &http);
+ QUrl url() const;
+ int statusCode() const;
+ QString reasonPhrase() const;
+ const QList<QNetworkReply::RawHeaderPair> headers() const;
+ QByteArray header(const QByteArray &headerName) const;
+ QByteArray body() const;
+
+private slots:
+ void replyFinished();
+ void replyError(QNetworkReply::NetworkError);
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ void readTimeout();
+
+private:
+ void setupReply();
+ QString errorMessage();
+ void emitError();
+ void emitFinished();
+
+ Http &http;
+ HttpRequest req;
+ QNetworkReply *networkReply;
+ QTimer *readTimeoutTimer;
+ int retryCount;
+ QByteArray bytes;
+};
+
+#endif // HTTP_H
--- /dev/null
+#include "localcache.h"
+
+LocalCache *LocalCache::instance(const char *name) {
+ static QMap<QByteArray, LocalCache *> instances;
+ auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name)));
+ if (i != instances.constEnd()) return i.value();
+ LocalCache *instance = new LocalCache(name);
+ instances.insert(instance->getName(), instance);
+ return instance;
+}
+
+LocalCache::LocalCache(const QByteArray &name)
+ : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), expiring(false),
+ insertCount(0) {
+ directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') +
+ QLatin1String(name) + QLatin1Char('/');
+#ifndef QT_NO_DEBUG_OUTPUT
+ hits = 0;
+ misses = 0;
+#endif
+}
+
+LocalCache::~LocalCache() {
+#ifndef QT_NO_DEBUG_OUTPUT
+ debugStats();
+#endif
+}
+
+QByteArray LocalCache::hash(const QByteArray &s) {
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(s);
+ const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36);
+ static const char sep('/');
+ QByteArray p;
+ p.reserve(h.length() + 2);
+ p.append(h.at(0));
+ p.append(sep);
+ p.append(h.at(1));
+ p.append(sep);
+ p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2));
+ return p;
+}
+
+bool LocalCache::isCached(const QString &path) {
+ bool cached = (QFile::exists(path) &&
+ (maxSeconds == 0 || QDateTime::currentDateTimeUtc().toTime_t() -
+ QFileInfo(path).created().toTime_t() <
+ maxSeconds));
+#ifndef QT_NO_DEBUG_OUTPUT
+ if (!cached) misses++;
+#endif
+ return cached;
+}
+
+QByteArray LocalCache::value(const QByteArray &key) {
+ const QString path = cachePath(key);
+ if (!isCached(path)) return QByteArray();
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << __PRETTY_FUNCTION__ << file.fileName() << file.errorString();
+#ifndef QT_NO_DEBUG_OUTPUT
+ misses++;
+#endif
+ return QByteArray();
+ }
+#ifndef QT_NO_DEBUG_OUTPUT
+ hits++;
+#endif
+ return file.readAll();
+}
+
+void LocalCache::insert(const QByteArray &key, const QByteArray &value) {
+ const QueueItem item = {key, value};
+ insertQueue.append(item);
+ QTimer::singleShot(0, [this]() {
+ if (insertQueue.isEmpty()) return;
+ for (const auto &item : insertQueue) {
+ const QString path = cachePath(item.key);
+ const QString parentDir = path.left(path.lastIndexOf('/'));
+ if (!QFile::exists(parentDir)) {
+ QDir().mkpath(parentDir);
+ }
+ QFile file(path);
+ if (!file.open(QIODevice::WriteOnly)) {
+ qWarning() << "Cannot create" << path;
+ continue;
+ }
+ file.write(item.value);
+ file.close();
+ if (size > 0) size += item.value.size();
+ }
+ insertQueue.clear();
+
+ // expire cache every n inserts
+ if (maxSize > 0 && ++insertCount % 100 == 0) {
+ if (size == 0 || size > maxSize) size = expire();
+ }
+ });
+}
+
+bool LocalCache::clear() {
+#ifndef QT_NO_DEBUG_OUTPUT
+ hits = 0;
+ misses = 0;
+#endif
+ size = 0;
+ insertCount = 0;
+ return QDir(directory).removeRecursively();
+}
+
+QString LocalCache::cachePath(const QByteArray &key) const {
+ return directory + QLatin1String(key.constData());
+}
+
+qint64 LocalCache::expire() {
+ if (expiring) return size;
+ expiring = true;
+
+ QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
+ QDirIterator it(directory, filters, QDirIterator::Subdirectories);
+
+ QMultiMap<QDateTime, QString> cacheItems;
+ qint64 totalSize = 0;
+ while (it.hasNext()) {
+ QString path = it.next();
+ QFileInfo info = it.fileInfo();
+ cacheItems.insert(info.created(), path);
+ totalSize += info.size();
+ qApp->processEvents();
+ }
+
+ int removedFiles = 0;
+ qint64 goal = (maxSize * 9) / 10;
+ auto i = cacheItems.constBegin();
+ while (i != cacheItems.constEnd()) {
+ if (totalSize < goal) break;
+ QString name = i.value();
+ QFile file(name);
+ qint64 size = file.size();
+ file.remove();
+ totalSize -= size;
+ ++removedFiles;
+ ++i;
+ qApp->processEvents();
+ }
+#ifndef QT_NO_DEBUG_OUTPUT
+ debugStats();
+ if (removedFiles > 0) {
+ qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles
+ << "New Size:" << totalSize;
+ }
+#endif
+
+ expiring = false;
+
+ return totalSize;
+}
+
+#ifndef QT_NO_DEBUG_OUTPUT
+void LocalCache::debugStats() {
+ int total = hits + misses;
+ if (total > 0) {
+ qDebug() << "Cache:" << name << '\n'
+ << "Inserts:" << insertCount << '\n'
+ << "Requests:" << total << '\n'
+ << "Hits:" << hits << (hits * 100) / total << "%\n"
+ << "Misses:" << misses << (misses * 100) / total << "%";
+ }
+}
+#endif
--- /dev/null
+#ifndef LOCALCACHE_H
+#define LOCALCACHE_H
+
+#include <QtCore>
+
+/**
+ * @brief Not thread-safe
+ */
+class LocalCache {
+public:
+ static LocalCache *instance(const char *name);
+ ~LocalCache();
+ static QByteArray hash(const QByteArray &s);
+
+ const QByteArray &getName() const { return name; }
+
+ void setMaxSeconds(uint value) { maxSeconds = value; }
+ void setMaxSize(uint value) { maxSize = value; }
+
+ QByteArray value(const QByteArray &key);
+ void insert(const QByteArray &key, const QByteArray &value);
+ bool clear();
+
+private:
+ LocalCache(const QByteArray &name);
+ QString cachePath(const QByteArray &key) const;
+ bool isCached(const QString &path);
+ qint64 expire();
+#ifndef QT_NO_DEBUG_OUTPUT
+ void debugStats();
+#endif
+
+ QByteArray name;
+ QString directory;
+ uint maxSeconds;
+ qint64 maxSize;
+ qint64 size;
+ bool expiring;
+ uint insertCount;
+ struct QueueItem {
+ QByteArray key;
+ QByteArray value;
+ };
+ QVector<QueueItem> insertQueue;
+
+#ifndef QT_NO_DEBUG_OUTPUT
+ uint hits;
+ uint misses;
+#endif
+};
+
+#endif // LOCALCACHE_H
--- /dev/null
+#include "throttledhttp.h"
+
+ThrottledHttp::ThrottledHttp(Http &http) : http(http), milliseconds(1000) {
+ elapsedTimer.start();
+}
+
+HttpReply *ThrottledHttp::request(const HttpRequest &req) {
+ return new ThrottledHttpReply(http, req, milliseconds, elapsedTimer);
+}
+
+ThrottledHttpReply::ThrottledHttpReply(Http &http,
+ const HttpRequest &req,
+ int milliseconds,
+ QElapsedTimer &elapsedTimer)
+ : http(http), req(req), milliseconds(milliseconds), elapsedTimer(elapsedTimer), timer(nullptr) {
+ checkElapsed();
+}
+
+void ThrottledHttpReply::checkElapsed() {
+ /*
+ static QMutex mutex;
+ QMutexLocker locker(&mutex);
+ */
+
+ const qint64 elapsedSinceLastRequest = elapsedTimer.elapsed();
+ if (elapsedSinceLastRequest < milliseconds) {
+ if (!timer) {
+ timer = new QTimer(this);
+ timer->setSingleShot(true);
+ timer->setTimerType(Qt::PreciseTimer);
+ connect(timer, SIGNAL(timeout()), SLOT(checkElapsed()));
+ }
+ qDebug() << "Throttling" << req.url
+ << QString("%1ms").arg(milliseconds - elapsedSinceLastRequest);
+ timer->setInterval(milliseconds - elapsedSinceLastRequest);
+ timer->start();
+ return;
+ }
+ elapsedTimer.start();
+ doRequest();
+}
+
+void ThrottledHttpReply::doRequest() {
+ QObject *reply = http.request(req);
+ connect(reply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray)));
+ connect(reply, SIGNAL(error(QString)), SIGNAL(error(QString)));
+ connect(reply, SIGNAL(finished(HttpReply)), SIGNAL(finished(HttpReply)));
+
+ // this will cause the deletion of this object once the request is finished
+ setParent(reply);
+}
--- /dev/null
+#ifndef THROTTLEDHTTP_H
+#define THROTTLEDHTTP_H
+
+#include "http.h"
+#include <QtCore>
+#include <QtNetwork>
+
+class ThrottledHttp : public Http {
+public:
+ ThrottledHttp(Http &http = Http::instance());
+ void setMilliseconds(int milliseconds) { this->milliseconds = milliseconds; }
+ HttpReply *request(const HttpRequest &req);
+
+private:
+ Http &http;
+ int milliseconds;
+ QElapsedTimer elapsedTimer;
+};
+
+class ThrottledHttpReply : public HttpReply {
+ Q_OBJECT
+
+public:
+ ThrottledHttpReply(Http &http,
+ const HttpRequest &req,
+ int milliseconds,
+ QElapsedTimer &elapsedTimer);
+ QUrl url() const { return req.url; }
+ int statusCode() const { return 200; }
+ QByteArray body() const { return QByteArray(); }
+
+private slots:
+ void checkElapsed();
+
+private:
+ void doRequest();
+ Http &http;
+ HttpRequest req;
+ int milliseconds;
+ QElapsedTimer &elapsedTimer;
+ QTimer *timer;
+};
+
+#endif // THROTTLEDHTTP_H
--- /dev/null
+# Qt Idle library
+
+This simple Qt library manages system and display sleep on Mac, Windows and Linux. Contributions for other platforms are welcome.
+
+You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes.
+
+Cheers,
+
+Flavio
\ No newline at end of file
--- /dev/null
+INCLUDEPATH += $$PWD/src
+DEPENDPATH += $$PWD/src
+
+HEADERS += $$PWD/src/idle.h
+
+mac {
+ SOURCES += $$PWD/src/idle_mac.cpp
+} else:win32 {
+ SOURCES += $$PWD/src/idle_win.cpp
+} else {
+ QT *= dbus
+ SOURCES += $$PWD/src/idle_linux.cpp
+}
--- /dev/null
+#ifndef IDLE_H
+#define IDLE_H
+
+#include <QString>
+
+class Idle {
+public:
+ static bool preventDisplaySleep(const QString &reason);
+ static bool allowDisplaySleep();
+ static QString displayErrorMessage();
+
+ static bool preventSystemSleep(const QString &reason);
+ static bool allowSystemSleep();
+ static QString systemErrorMessage();
+};
+
+#endif // IDLE_H
--- /dev/null
+#include "idle.h"
+
+#include <QDBusConnectionInterface>
+#include <QDBusInterface>
+#include <QDBusReply>
+#include <QtCore>
+
+namespace {
+
+const QString fdDisplayService = "org.freedesktop.ScreenSaver";
+const QString fdDisplayPath = "/org/freedesktop/ScreenSaver";
+const QString fdDisplayInterface = fdDisplayService;
+
+const QString gnomeSystemService = "org.gnome.SessionManager";
+const QString gnomeSystemPath = "/org/gnome/SessionManager";
+const QString gnomeSystemInterface = gnomeSystemService;
+
+const QString inhibitMethod = "Inhibit";
+const QString uninhibitMethod = "UnInhibit";
+
+quint32 cookie;
+QString errorMessage;
+
+bool handleReply(const QDBusReply<quint32> &reply) {
+ if (reply.isValid()) {
+ cookie = reply.value();
+ errorMessage.clear();
+ return true;
+ }
+ errorMessage = reply.error().message();
+ return false;
+}
+
+} // namespace
+
+bool Idle::preventDisplaySleep(const QString &reason) {
+ QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface);
+ QDBusReply<quint32> reply =
+ dbus.call(inhibitMethod, QCoreApplication::applicationName(), reason);
+ return handleReply(reply);
+}
+
+bool Idle::allowDisplaySleep() {
+ QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface);
+ dbus.call(uninhibitMethod, cookie);
+ return true;
+}
+
+QString Idle::displayErrorMessage() {
+ return errorMessage;
+}
+
+bool Idle::preventSystemSleep(const QString &reason) {
+ QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface);
+ QDBusReply<quint32> reply =
+ dbus.call(inhibitMethod, QCoreApplication::applicationName(), reason);
+ return handleReply(reply);
+}
+
+bool Idle::allowSystemSleep() {
+ QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface);
+ dbus.call(uninhibitMethod, cookie);
+ return true;
+}
+
+QString Idle::systemErrorMessage() {
+ return errorMessage;
+}
--- /dev/null
+#include "idle.h"
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+
+namespace {
+
+IOPMAssertionID displayAssertionID = 0;
+IOReturn displayRes = 0;
+
+IOPMAssertionID systemAssertionID = 0;
+IOReturn systemRes = 0;
+
+} // namespace
+
+bool Idle::preventDisplaySleep(const QString &reason) {
+ displayRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,
+ kIOPMAssertionLevelOn, reason.toCFString(),
+ &displayAssertionID);
+ return displayRes == kIOReturnSuccess;
+}
+
+bool Idle::allowDisplaySleep() {
+ displayRes = IOPMAssertionRelease(displayAssertionID);
+ return displayRes == kIOReturnSuccess;
+}
+
+QString Idle::displayErrorMessage() {
+ return QString();
+ // return QString::fromUtf8(IOService::stringFromReturn(displayRes));
+}
+
+bool Idle::preventSystemSleep(const QString &reason) {
+ systemRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep,
+ kIOPMAssertionLevelOn, reason.toCFString(),
+ &systemAssertionID);
+ return systemRes == kIOReturnSuccess;
+}
+
+bool Idle::allowSystemSleep() {
+ systemRes = IOPMAssertionRelease(systemAssertionID);
+ return systemRes == kIOReturnSuccess;
+}
+
+QString Idle::systemErrorMessage() {
+ return QString();
+ // return QString::fromUtf8(IOService::stringFromReturn(systemRes));
+}
--- /dev/null
+#include "idle.h"
+
+#include "windows.h"
+
+namespace {
+EXECUTION_STATE executionState;
+}
+
+bool Idle::preventDisplaySleep(const QString &reason) {
+ executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
+ return true;
+}
+
+bool Idle::allowDisplaySleep() {
+ SetThreadExecutionState(ES_CONTINUOUS | executionState);
+ return true;
+}
+
+QString Idle::displayErrorMessage() {
+ return QString();
+}
+
+bool Idle::preventSystemSleep(const QString &reason) {
+ executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
+ return true;
+}
+
+bool Idle::allowSystemSleep() {
+ SetThreadExecutionState(ES_CONTINUOUS | executionState);
+ return true;
+}
+
+QString Idle::systemErrorMessage() {
+ return QString();
+}
--- /dev/null
+# Qt Media Library Abstraction
+
+This is a simple wrapper around a multimedia playback library.
+
+Define `MEDIA_QTAV` to link to QtAV or `MEDIA_MPV` to link to libmpv (>=0.29.0).
+
+`MEDIA_AUDIOONLY` can be defined if the application does not need video.
+
+You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes.
--- /dev/null
+INCLUDEPATH += $$PWD/src
+DEPENDPATH += $$PWD/src
+
+HEADERS += $$PWD/src/media.h
+
+contains(DEFINES, MEDIA_QTAV) {
+ QT += avwidgets
+ INCLUDEPATH += $$PWD/src/qtav
+ DEPENDPATH += $$PWD/src/qtav
+ HEADERS += $$PWD/src/mediaqtav.h
+ SOURCES += $$PWD/src/mediaqtav.cpp
+}
+
+contains(DEFINES, MEDIA_MPV) {
+ QT *= gui
+
+ LIBS += -lmpv
+mac {
+ # useful for homebrew: brew install mpv
+ # LIBS += -L/usr/local/lib
+ # INCLUDEPATH += /usr/local/include
+}
+
+ INCLUDEPATH += $$PWD/src/mpv
+ DEPENDPATH += $$PWD/src/mpv
+ HEADERS += $$PWD/src/mpv/mediampv.h
+ SOURCES += $$PWD/src/mpv/mediampv.cpp
+
+ !contains(DEFINES, MEDIA_AUDIOONLY) {
+ QT *= widgets
+ unix:!mac {
+ QT *= x11extras
+ }
+ HEADERS += $$PWD/src/mpv/mpvwidget.h
+ SOURCES += $$PWD/src/mpv/mpvwidget.cpp
+ }
+}
--- /dev/null
+#ifndef MEDIA_H
+#define MEDIA_H
+
+#include <QtCore>
+#ifndef MEDIA_AUDIOONLY
+#include <QtWidgets>
+#endif
+
+class Media : public QObject {
+ Q_OBJECT
+
+public:
+ enum State {
+ StoppedState,
+ LoadingState,
+ BufferingState,
+ PlayingState,
+ PausedState,
+ ErrorState
+ };
+ Q_ENUM(State)
+
+ Media(QObject *parent = nullptr) : QObject(parent) {
+ qRegisterMetaType<Media::State>("Media::State");
+ }
+ virtual void setAudioOnly(bool value) = 0;
+#ifndef MEDIA_AUDIOONLY
+ virtual void setRenderer(const QString &name) = 0;
+ virtual QWidget *videoWidget() = 0;
+ virtual void playSeparateAudioAndVideo(const QString &video, const QString &audio) = 0;
+ virtual void snapshot() = 0;
+#endif
+ virtual void init() = 0;
+
+ virtual Media::State state() const = 0;
+
+ virtual void play(const QString &file) = 0;
+ virtual void play() = 0;
+ virtual void pause() = 0;
+ virtual void stop() = 0;
+ virtual void seek(qint64 ms) = 0;
+ virtual QString file() const = 0;
+
+ virtual void setBufferMilliseconds(qint64 value) = 0;
+ virtual void setUserAgent(const QString &value) = 0;
+
+ virtual void enqueue(const QString &file) = 0;
+ virtual void clearQueue() = 0;
+ virtual bool hasQueue() const = 0;
+
+ virtual qint64 position() const = 0;
+ virtual qint64 duration() const = 0;
+ virtual qint64 remainingTime() const = 0;
+
+ virtual qreal volume() const = 0;
+ virtual void setVolume(qreal value) = 0;
+
+ virtual bool volumeMuted() const = 0;
+ virtual void setVolumeMuted(bool value) = 0;
+
+ virtual QString errorString() const = 0;
+
+signals:
+ void error(const QString &message);
+ void sourceChanged();
+ void bufferStatus(qreal value);
+ void loaded();
+ void started();
+ void stopped();
+ void paused(bool p);
+ void stateChanged(Media::State state);
+ void positionChanged(qint64 ms);
+ void aboutToFinish();
+ void finished();
+ void volumeChanged(qreal value);
+ void volumeMutedChanged(bool value);
+#ifndef MEDIA_AUDIOONLY
+ void snapshotReady(const QImage &image);
+#endif
+};
+
+#endif // MEDIA_H
--- /dev/null
+#include "mediampv.h"
+
+#include <clocale>
+#include <mpv/qthelper.hpp>
+
+#ifndef MEDIA_AUDIOONLY
+#include "mpvwidget.h"
+#endif
+
+namespace {
+void wakeup(void *ctx) {
+ // This callback is invoked from any mpv thread (but possibly also
+ // recursively from a thread that is calling the mpv API). Just notify
+ // the Qt GUI thread to wake up (so that it can process events with
+ // mpv_wait_event()), and return as quickly as possible.
+ MediaMPV *mediaMPV = (MediaMPV *)ctx;
+ emit mediaMPV->mpvEvents();
+}
+
+} // namespace
+
+MediaMPV::MediaMPV(QObject *parent) : Media(parent), widget(nullptr) {
+ QThread *thread = new QThread(this);
+ thread->start();
+ moveToThread(thread);
+ connect(this, &QObject::destroyed, thread, &QThread::quit);
+
+#ifndef Q_OS_WIN
+ // Qt sets the locale in the QApplication constructor, but libmpv requires
+ // the LC_NUMERIC category to be set to "C", so change it back.
+ std::setlocale(LC_NUMERIC, "C");
+#endif
+
+ mpv = mpv_create();
+ if (!mpv) qFatal("Cannot create MPV instance");
+
+ mpv_set_option_string(mpv, "config", "no");
+ mpv_set_option_string(mpv, "audio-display", "no");
+ mpv_set_option_string(mpv, "gapless-audio", "weak");
+ mpv_set_option_string(mpv, "idle", "yes");
+ mpv_set_option_string(mpv, "input-default-bindings", "no");
+ mpv_set_option_string(mpv, "input-vo-keyboard", "no");
+ mpv_set_option_string(mpv, "input-cursor", "no");
+ mpv_set_option_string(mpv, "input-media-keys", "no");
+ mpv_set_option_string(mpv, "ytdl", "no");
+ mpv_set_option_string(mpv, "fs", "no");
+ mpv_set_option_string(mpv, "osd-level", "0");
+ mpv_set_option_string(mpv, "quiet", "yes");
+ mpv_set_option_string(mpv, "load-scripts", "no");
+ mpv_set_option_string(mpv, "audio-client-name",
+ QCoreApplication::applicationName().toUtf8().data());
+ mpv_set_option_string(mpv, "hwdec", "auto");
+ mpv_set_option_string(mpv, "vo", "libmpv");
+
+ mpv_set_option_string(mpv, "cache", "no");
+ mpv_set_option_string(mpv, "demuxer-max-bytes", "10485760");
+ mpv_set_option_string(mpv, "demuxer-max-back-bytes", "10485760");
+
+#ifdef MEDIA_MPV_WID
+ widget = new QWidget();
+ widget->setAttribute(Qt::WA_DontCreateNativeAncestors);
+ widget->setAttribute(Qt::WA_NativeWindow);
+ // If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
+ int64_t wid = (intptr_t)widget->winId();
+ mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
+#endif
+
+#ifndef QT_NO_DEBUG_OUTPUT
+ // Request log messages
+ // They are received as MPV_EVENT_LOG_MESSAGE.
+ mpv_request_log_messages(mpv, "info");
+#endif
+
+ // From this point on, the wakeup function will be called. The callback
+ // can come from any thread, so we use the QueuedConnection mechanism to
+ // relay the wakeup in a thread-safe way.
+ connect(this, &MediaMPV::mpvEvents, this, &MediaMPV::onMpvEvents, Qt::QueuedConnection);
+ mpv_set_wakeup_callback(mpv, wakeup, this);
+
+ if (mpv_initialize(mpv) < 0) qFatal("mpv failed to initialize");
+
+ // Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
+ // this property changes.
+ mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE);
+ mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_FLAG);
+}
+
+// This slot is invoked by wakeup() (through the mpvEvents signal).
+void MediaMPV::onMpvEvents() {
+ // Process all events, until the event queue is empty.
+ while (mpv) {
+ mpv_event *event = mpv_wait_event(mpv, 0);
+ if (event->event_id == MPV_EVENT_NONE) break;
+ handleMpvEvent(event);
+ }
+}
+
+void MediaMPV::checkAboutToFinish(qint64 position) {
+ if (!aboutToFinishEmitted && currentState == Media::PlayingState) {
+ const qint64 dur = duration();
+ if (dur > 0 && dur - position < 5000) {
+ aboutToFinishEmitted = true;
+ qDebug() << "aboutToFinish" << position << dur;
+ emit aboutToFinish();
+ }
+ }
+}
+
+void MediaMPV::handleMpvEvent(mpv_event *event) {
+ // qDebug() << event->data;
+ switch (event->event_id) {
+ case MPV_EVENT_START_FILE:
+ clearTrackState();
+ emit sourceChanged();
+ setState(Media::LoadingState);
+ break;
+
+ case MPV_EVENT_SEEK:
+ setState(Media::BufferingState);
+ break;
+
+ case MPV_EVENT_FILE_LOADED:
+ setState(Media::PlayingState);
+ break;
+
+ case MPV_EVENT_PLAYBACK_RESTART:
+ case MPV_EVENT_UNPAUSE:
+ setState(Media::PlayingState);
+ break;
+
+ case MPV_EVENT_END_FILE: {
+ struct mpv_event_end_file *eof_event = (struct mpv_event_end_file *)event->data;
+ if (eof_event->reason == MPV_END_FILE_REASON_EOF ||
+ eof_event->reason == MPV_END_FILE_REASON_ERROR) {
+ qDebug() << "Finished";
+ setState(Media::StoppedState);
+ emit finished();
+ }
+ break;
+ }
+
+ case MPV_EVENT_PAUSE:
+ setState(Media::PausedState);
+ break;
+
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ mpv_event_property *prop = (mpv_event_property *)event->data;
+ // qDebug() << prop->name << prop->data;
+
+ if (strcmp(prop->name, "time-pos") == 0) {
+ if (prop->format == MPV_FORMAT_DOUBLE) {
+ double seconds = *(double *)prop->data;
+ qint64 ms = seconds * 1000.;
+ emit positionChanged(ms);
+ checkAboutToFinish(ms);
+ }
+ }
+
+ else if (strcmp(prop->name, "volume") == 0) {
+ if (prop->format == MPV_FORMAT_DOUBLE) {
+ double vol = *(double *)prop->data;
+ emit volumeChanged(vol / 100.);
+ }
+ }
+
+ else if (strcmp(prop->name, "mute") == 0) {
+ if (prop->format == MPV_FORMAT_FLAG) {
+ int mute = *(int *)prop->data;
+ emit volumeMutedChanged(mute == 1);
+ }
+ }
+
+ break;
+ }
+
+ case MPV_EVENT_LOG_MESSAGE: {
+ struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
+ qDebug() << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
+
+ if (msg->log_level == MPV_LOG_LEVEL_ERROR) {
+ lastErrorString = QString::fromUtf8(msg->text);
+ emit error(lastErrorString);
+ }
+
+ break;
+ }
+
+ case MPV_EVENT_SHUTDOWN: {
+ mpv_terminate_destroy(mpv);
+ mpv = nullptr;
+ break;
+ }
+
+ default:;
+ // Unhandled events
+ }
+}
+
+void MediaMPV::sendCommand(const char *args[]) {
+ // mpv_command_async(mpv, 0, args);
+ mpv_command(mpv, args);
+}
+
+void MediaMPV::setState(Media::State value) {
+ if (value != currentState) {
+ currentState = value;
+ emit stateChanged(currentState);
+ }
+}
+
+void MediaMPV::clearTrackState() {
+ lastErrorString.clear();
+ aboutToFinishEmitted = false;
+}
+
+void MediaMPV::setAudioOnly(bool value) {
+ Q_UNUSED(value);
+}
+
+#ifndef MEDIA_AUDIOONLY
+
+void MediaMPV::setRenderer(const QString &name) {
+ mpv_set_option_string(mpv, "vo", name.toUtf8().data());
+}
+
+QWidget *MediaMPV::videoWidget() {
+ if (!widget) {
+ widget = new MpvWidget(mpv);
+ }
+ return widget;
+}
+
+void MediaMPV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
+ const QByteArray fileUtf8 = video.toUtf8();
+ const char *args[] = {"loadfile", fileUtf8.constData(), nullptr};
+ sendCommand(args);
+
+ qApp->processEvents();
+
+ const QByteArray audioUtf8 = audio.toUtf8();
+ const char *args2[] = {"audio-add", audioUtf8.constData(), nullptr};
+ sendCommand(args2);
+
+ clearTrackState();
+}
+
+void MediaMPV::snapshot() {
+ if (currentState == State::StoppedState) return;
+
+ const QVariantList args = {"screenshot-raw", "video"};
+ mpv::qt::node_builder nodeBuilder(args);
+ mpv_node node;
+ const int ret = mpv_command_node(mpv, nodeBuilder.node(), &node);
+ if (ret < 0) {
+ emit error("Cannot take snapshot");
+ return;
+ }
+
+ mpv::qt::node_autofree auto_free(&node);
+ if (node.format != MPV_FORMAT_NODE_MAP) {
+ emit error("Cannot take snapshot");
+ return;
+ }
+
+ int width = 0;
+ int height = 0;
+ int stride = 0;
+ mpv_node_list *list = node.u.list;
+ uchar *data = nullptr;
+
+ for (int i = 0; i < list->num; ++i) {
+ const char *key = list->keys[i];
+ if (strcmp(key, "w") == 0) {
+ width = static_cast<int>(list->values[i].u.int64);
+ } else if (strcmp(key, "h") == 0) {
+ height = static_cast<int>(list->values[i].u.int64);
+ } else if (strcmp(key, "stride") == 0) {
+ stride = static_cast<int>(list->values[i].u.int64);
+ } else if (strcmp(key, "data") == 0) {
+ data = static_cast<uchar *>(list->values[i].u.ba->data);
+ }
+ }
+
+ if (data != nullptr) {
+ QImage img = QImage(data, width, height, stride, QImage::Format_RGB32);
+ img.bits();
+ emit snapshotReady(img);
+ }
+}
+
+#endif
+
+void MediaMPV::init() {}
+
+Media::State MediaMPV::state() const {
+ return currentState;
+}
+
+void MediaMPV::play(const QString &file) {
+ const QByteArray fileUtf8 = file.toUtf8();
+ const char *args[] = {"loadfile", fileUtf8.constData(), nullptr};
+ sendCommand(args);
+
+ clearTrackState();
+ if (currentState == Media::PausedState) play();
+}
+
+void MediaMPV::play() {
+ int flag = 0;
+ mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
+}
+
+void MediaMPV::pause() {
+ int flag = 1;
+ mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &flag);
+}
+
+void MediaMPV::stop() {
+ const char *args[] = {"stop", nullptr};
+ sendCommand(args);
+}
+
+void MediaMPV::seek(qint64 ms) {
+ double seconds = ms / 1000.;
+ QByteArray ba = QString::number(seconds).toUtf8();
+ const char *args[] = {"seek", ba.constData(), "absolute", nullptr};
+ sendCommand(args);
+}
+
+QString MediaMPV::file() const {
+ char *path;
+ mpv_get_property(mpv, "path", MPV_FORMAT_STRING, &path);
+ return QString::fromUtf8(path);
+}
+
+void MediaMPV::setBufferMilliseconds(qint64 value) {
+ Q_UNUSED(value);
+ // Not implemented
+}
+
+void MediaMPV::setUserAgent(const QString &value) {
+ mpv_set_option_string(mpv, "user-agent", value.toUtf8());
+}
+
+void MediaMPV::enqueue(const QString &file) {
+ const QByteArray fileUtf8 = file.toUtf8();
+ const char *args[] = {"loadfile", fileUtf8.constData(), "append", nullptr};
+ sendCommand(args);
+}
+
+void MediaMPV::clearQueue() {
+ const char *args[] = {"playlist-clear", nullptr};
+ sendCommand(args);
+}
+
+bool MediaMPV::hasQueue() const {
+ mpv_node node;
+ int r = mpv_get_property(mpv, "playlist", MPV_FORMAT_NODE, &node);
+ if (r < 0) return false;
+ QVariant v = mpv::qt::node_to_variant(&node);
+ mpv_free_node_contents(&node);
+ QVariantList list = v.toList();
+ return list.count() > 1;
+}
+
+qint64 MediaMPV::position() const {
+ double seconds;
+ mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &seconds);
+ return seconds * 1000.;
+}
+
+qint64 MediaMPV::duration() const {
+ double seconds;
+ mpv_get_property(mpv, "duration", MPV_FORMAT_DOUBLE, &seconds);
+ return seconds * 1000.;
+}
+
+qint64 MediaMPV::remainingTime() const {
+ double seconds;
+ mpv_get_property(mpv, "time-remaining", MPV_FORMAT_DOUBLE, &seconds);
+ return seconds * 1000.;
+}
+
+qreal MediaMPV::volume() const {
+ double vol;
+ mpv_get_property(mpv, "volume", MPV_FORMAT_DOUBLE, &vol);
+ return vol / 100.;
+}
+
+void MediaMPV::setVolume(qreal value) {
+ double percent = value * 100.;
+ mpv_set_property_async(mpv, 0, "volume", MPV_FORMAT_DOUBLE, &percent);
+}
+
+bool MediaMPV::volumeMuted() const {
+ int mute;
+ mpv_get_property(mpv, "mute", MPV_FORMAT_FLAG, &mute);
+ return mute == 1;
+}
+
+void MediaMPV::setVolumeMuted(bool value) {
+ int mute = value ? 1 : 0;
+ mpv_set_property(mpv, "mute", MPV_FORMAT_FLAG, &mute);
+}
+
+QString MediaMPV::errorString() const {
+ return lastErrorString;
+}
--- /dev/null
+#ifndef MEDIAMPV_H
+#define MEDIAMPV_H
+
+#include <QtCore>
+
+#include "media.h"
+#include <mpv/client.h>
+
+class MediaMPV : public Media {
+ Q_OBJECT
+
+public:
+ MediaMPV(QObject *parent = nullptr);
+
+ void setAudioOnly(bool value);
+#ifndef MEDIA_AUDIOONLY
+ void setRenderer(const QString &name);
+ QWidget *videoWidget();
+ void playSeparateAudioAndVideo(const QString &video, const QString &audio);
+ void snapshot();
+#endif
+ void init();
+ Media::State state() const;
+ void play(const QString &file);
+ void play();
+ void pause();
+ void stop();
+ void seek(qint64 ms);
+ QString file() const;
+ void setBufferMilliseconds(qint64 value);
+ void setUserAgent(const QString &value);
+ void enqueue(const QString &file);
+ void clearQueue();
+ bool hasQueue() const;
+ qint64 position() const;
+ qint64 duration() const;
+ qint64 remainingTime() const;
+ qreal volume() const;
+ void setVolume(qreal value);
+ bool volumeMuted() const;
+ void setVolumeMuted(bool value);
+ QString errorString() const;
+
+private slots:
+ void onMpvEvents();
+ void checkAboutToFinish(qint64 position);
+
+signals:
+ void mpvEvents();
+
+private:
+ void handleMpvEvent(mpv_event *event);
+ void sendCommand(const char *args[]);
+ void setState(Media::State value);
+ void clearTrackState();
+
+ QWidget *widget;
+ mpv_handle *mpv;
+ Media::State currentState = Media::StoppedState;
+ bool aboutToFinishEmitted = false;
+ QString lastErrorString;
+};
+
+#endif // MEDIAMPV_H
--- /dev/null
+#include "mpvwidget.h"
+#include <stdexcept>
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
+#include <QtX11Extras/QX11Info>
+#endif
+
+static void *get_proc_address(void *ctx, const char *name) {
+ Q_UNUSED(ctx);
+ QOpenGLContext *glctx = QOpenGLContext::currentContext();
+ if (!glctx) return nullptr;
+ return reinterpret_cast<void *>(glctx->getProcAddress(QByteArray(name)));
+}
+
+MpvWidget::MpvWidget(mpv_handle *mpv, QWidget *parent, Qt::WindowFlags f)
+ : QOpenGLWidget(parent, f), mpv(mpv), mpvContext(nullptr) {
+ moveToThread(qApp->thread());
+}
+
+MpvWidget::~MpvWidget() {
+ makeCurrent();
+ if (mpvContext) mpv_render_context_free(mpvContext);
+ mpv_terminate_destroy(mpv);
+}
+
+void MpvWidget::initializeGL() {
+ if (mpvContext) qFatal("Already initialized");
+
+ QWidget *nativeParent = nativeParentWidget();
+ qDebug() << "initializeGL" << nativeParent;
+ if (nativeParent == nullptr) qFatal("No native parent");
+
+ mpv_opengl_init_params gl_init_params{get_proc_address, this, nullptr};
+ mpv_render_param params[]{{MPV_RENDER_PARAM_API_TYPE, (void *)MPV_RENDER_API_TYPE_OPENGL},
+ {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
+ {MPV_RENDER_PARAM_INVALID, nullptr},
+ {MPV_RENDER_PARAM_INVALID, nullptr}};
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
+ const QString platformName = QGuiApplication::platformName();
+ if (platformName.contains("xcb")) {
+ params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
+ params[2].data = (void *)QX11Info::display();
+ qDebug() << platformName << params[2].data;
+ } else if (platformName.contains("wayland")) {
+ qWarning() << "Wayland not supported";
+ }
+#endif
+
+ if (mpv_render_context_create(&mpvContext, mpv, params) < 0)
+ qFatal("failed to initialize mpv GL context");
+ mpv_render_context_set_update_callback(mpvContext, MpvWidget::onUpdate, (void *)this);
+
+ connect(this, &QOpenGLWidget::frameSwapped, this, &MpvWidget::onFrameSwapped);
+}
+
+void MpvWidget::resizeGL(int w, int h) {
+ qreal r = devicePixelRatioF();
+ glWidth = int(w * r);
+ glHeight = int(h * r);
+}
+
+void MpvWidget::paintGL() {
+ mpv_opengl_fbo fbo{static_cast<int>(defaultFramebufferObject()), glWidth, glHeight, 0};
+
+ static bool yes = true;
+ mpv_render_param params[] = {{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
+ {MPV_RENDER_PARAM_FLIP_Y, &yes},
+ {MPV_RENDER_PARAM_INVALID, nullptr}};
+ // See render_gl.h on what OpenGL environment mpv expects, and
+ // other API details.
+ mpv_render_context_render(mpvContext, params);
+}
+
+// Make Qt invoke mpv_opengl_cb_draw() to draw a new/updated video frame.
+void MpvWidget::maybeUpdate() {
+ // If the Qt window is not visible, Qt's update() will just skip rendering.
+ // This confuses mpv's opengl-cb API, and may lead to small occasional
+ // freezes due to video rendering timing out.
+ // Handle this by manually redrawing.
+ // Note: Qt doesn't seem to provide a way to query whether update() will
+ // be skipped, and the following code still fails when e.g. switching
+ // to a different workspace with a reparenting window manager.
+ if (!updatesEnabled() || isHidden() || window()->isHidden() || window()->isMinimized()) {
+ makeCurrent();
+ paintGL();
+ QOpenGLContext *c = context();
+ c->swapBuffers(c->surface());
+ doneCurrent();
+ mpv_render_context_report_swap(mpvContext);
+ } else {
+ update();
+ }
+}
+
+void MpvWidget::onFrameSwapped() {
+ mpv_render_context_report_swap(mpvContext);
+}
+
+void MpvWidget::onUpdate(void *ctx) {
+ QMetaObject::invokeMethod((MpvWidget *)ctx, "maybeUpdate");
+}
--- /dev/null
+#ifndef PLAYERWINDOW_H
+#define PLAYERWINDOW_H
+
+#include <QtWidgets>
+#include <mpv/client.h>
+#include <mpv/qthelper.hpp>
+#include <mpv/render_gl.h>
+
+class MpvWidget Q_DECL_FINAL : public QOpenGLWidget {
+ Q_OBJECT
+
+public:
+ MpvWidget(mpv_handle *mpv, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr);
+ ~MpvWidget() Q_DECL_OVERRIDE;
+
+ QSize sizeHint() const Q_DECL_OVERRIDE { return QSize(480, 270); }
+
+protected:
+ void initializeGL() Q_DECL_OVERRIDE;
+ void resizeGL(int w, int h) Q_DECL_OVERRIDE;
+ void paintGL() Q_DECL_OVERRIDE;
+
+private slots:
+ void maybeUpdate();
+ void onFrameSwapped();
+
+private:
+ static void onUpdate(void *ctx);
+
+ mpv_handle *mpv;
+ mpv_render_context *mpvContext;
+ int glWidth;
+ int glHeight;
+};
+
+#endif // PLAYERWINDOW_H
--- /dev/null
+#include "mediaqtav.h"
+
+namespace {
+
+#if defined(Q_OS_ANDROID) || defined(Q_OS_MAC)
+const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_OpenGLWidget;
+#else
+const QtAV::VideoRendererId defaultRenderer = QtAV::VideoRendererId_GLWidget2;
+#endif
+
+#ifndef MEDIA_AUDIOONLY
+QtAV::VideoRendererId rendererIdFor(const QString &name) {
+ static const struct {
+ const char *name;
+ QtAV::VideoRendererId id;
+ } renderers[] = {{"opengl", QtAV::VideoRendererId_OpenGLWidget},
+ {"gl", QtAV::VideoRendererId_GLWidget2},
+ {"d2d", QtAV::VideoRendererId_Direct2D},
+ {"gdi", QtAV::VideoRendererId_GDI},
+ {"xv", QtAV::VideoRendererId_XV},
+ {"x11", QtAV::VideoRendererId_X11},
+ {"qt", QtAV::VideoRendererId_Widget}};
+
+ for (int i = 0; renderers[i].name; ++i) {
+ if (name == QLatin1String(renderers[i].name)) return renderers[i].id;
+ }
+
+ return defaultRenderer;
+}
+#endif
+
+Media::State stateFor(QtAV::AVPlayer::State state) {
+ static const QMap<QtAV::AVPlayer::State, Media::State> map{
+ {QtAV::AVPlayer::StoppedState, Media::StoppedState},
+ {QtAV::AVPlayer::PlayingState, Media::PlayingState},
+ {QtAV::AVPlayer::PausedState, Media::PausedState}};
+ return map[state];
+}
+
+} // namespace
+
+MediaQtAV::MediaQtAV(QObject *parent)
+ : Media(parent), player1(nullptr), player2(nullptr), currentPlayer(nullptr) {
+#ifndef MEDIA_AUDIOONLY
+ rendererId = defaultRenderer;
+#endif
+}
+
+#ifndef MEDIA_AUDIOONLY
+void MediaQtAV::setRenderer(const QString &name) {
+ rendererId = rendererIdFor(name);
+}
+#endif
+
+void MediaQtAV::setAudioOnly(bool value) {
+ audioOnly = value;
+}
+
+void MediaQtAV::init() {
+#ifndef QT_NO_DEBUG_OUTPUT
+ QtAV::setLogLevel(QtAV::LogAll);
+#endif
+
+#ifndef MEDIA_AUDIOONLY
+ QtAV::Widgets::registerRenderers();
+#endif
+ player1 = createPlayer(audioOnly);
+ setCurrentPlayer(player1);
+}
+
+#ifndef MEDIA_AUDIOONLY
+QWidget *MediaQtAV::videoWidget() {
+ return currentPlayer->renderer()->widget();
+}
+#endif
+
+Media::State MediaQtAV::state() const {
+ if (currentPlayer->mediaStatus() == QtAV::LoadingMedia) return LoadingState;
+ if (currentPlayer->bufferProgress() > 0. && currentPlayer->bufferProgress() < 1.)
+ return BufferingState;
+ return stateFor(currentPlayer->state());
+}
+
+void MediaQtAV::play(const QString &file) {
+ if (currentPlayer->isPlaying()) {
+ smoothSourceChange(file, QString());
+ return;
+ }
+
+#ifndef MEDIA_AUDIOONLY
+ if (!currentPlayer->externalAudio().isEmpty()) currentPlayer->setExternalAudio(QString());
+#endif
+ currentPlayer->play(file);
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+}
+
+#ifndef MEDIA_AUDIOONLY
+void MediaQtAV::playSeparateAudioAndVideo(const QString &video, const QString &audio) {
+ if (currentPlayer->isPlaying()) {
+ smoothSourceChange(video, audio);
+ return;
+ }
+ currentPlayer->stop();
+ currentPlayer->setExternalAudio(audio);
+ currentPlayer->play(video);
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+}
+
+void MediaQtAV::snapshot() {
+ auto videoCapture = currentPlayer->videoCapture();
+ connect(videoCapture, &QtAV::VideoCapture::imageCaptured, this, &Media::snapshotReady);
+ connect(videoCapture, &QtAV::VideoCapture::failed, this,
+ [this] { emit snapshotReady(QImage()); });
+ videoCapture->capture();
+}
+#endif
+
+void MediaQtAV::play() {
+ if (currentPlayer->isPaused())
+ currentPlayer->togglePause();
+ else
+ currentPlayer->play();
+}
+
+QString MediaQtAV::file() const {
+ return currentPlayer->file();
+}
+
+void MediaQtAV::setBufferMilliseconds(qint64 value) {
+ currentPlayer->setBufferValue(value);
+}
+
+void MediaQtAV::setUserAgent(const QString &value) {
+ qDebug() << "Setting user agent to" << value;
+ auto options = currentPlayer->optionsForFormat();
+ options.insert(QStringLiteral("user_agent"), value);
+ currentPlayer->setOptionsForFormat(options);
+}
+
+void MediaQtAV::enqueue(const QString &file) {
+ queue << file;
+ if (queue.size() == 1) {
+ qDebug() << "Preloading" << file;
+ auto nextPlayer = player1;
+ if (currentPlayer == player1) {
+ if (player2 == nullptr) player2 = createPlayer(audioOnly);
+ nextPlayer = player2;
+ }
+ nextPlayer->setFile(file);
+ nextPlayer->load();
+ }
+}
+
+void MediaQtAV::clearQueue() {
+ queue.clear();
+}
+
+bool MediaQtAV::hasQueue() const {
+ return !queue.isEmpty();
+}
+
+qint64 MediaQtAV::position() const {
+ return currentPlayer->position();
+}
+
+qint64 MediaQtAV::duration() const {
+ return currentPlayer->duration();
+}
+
+qint64 MediaQtAV::remainingTime() const {
+ return currentPlayer->duration() - currentPlayer->position();
+}
+
+qreal MediaQtAV::volume() const {
+ return currentPlayer->audio()->volume();
+}
+
+void MediaQtAV::setVolume(qreal value) {
+ auto audio = currentPlayer->audio();
+ if (!audio->isOpen()) audio->open();
+ audio->setVolume(value);
+}
+
+bool MediaQtAV::volumeMuted() const {
+ return currentPlayer->audio()->isMute();
+}
+
+void MediaQtAV::setVolumeMuted(bool value) {
+ currentPlayer->audio()->setMute(value);
+}
+
+QString MediaQtAV::errorString() const {
+ return lastErrorString;
+}
+
+void MediaQtAV::checkAboutToFinish(qint64 position) {
+ if (!aboutToFinishEmitted && currentPlayer->isPlaying() &&
+ duration() - position < currentPlayer->bufferValue()) {
+ aboutToFinishEmitted = true;
+ emit aboutToFinish();
+ }
+}
+
+void MediaQtAV::onMediaStatusChange(QtAV::MediaStatus status) {
+ qDebug() << QVariant(status).toString();
+
+ switch (status) {
+ case QtAV::LoadingMedia:
+ emit stateChanged(LoadingState);
+ break;
+ case QtAV::BufferedMedia:
+ if (currentPlayer->state() == QtAV::AVPlayer::PlayingState) emit stateChanged(PlayingState);
+ break;
+ case QtAV::BufferingMedia:
+ emit stateChanged(BufferingState);
+ break;
+ case QtAV::EndOfMedia:
+ if (queue.isEmpty())
+ emit finished();
+ else {
+ auto nextPlayer = currentPlayer == player1 ? player2 : player1;
+ if (nextPlayer->isLoaded()) {
+ qDebug() << "Preloaded";
+ setCurrentPlayer(nextPlayer);
+ nextPlayer->play();
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ emit sourceChanged();
+ queue.dequeue();
+ } else {
+ qDebug() << "Not preloaded";
+ currentPlayer->play(queue.dequeue());
+ }
+ }
+ break;
+ case QtAV::InvalidMedia:
+ emit stateChanged(Media::ErrorState);
+ break;
+ default:
+ qDebug() << "Unhandled" << QVariant(status).toString();
+ }
+}
+
+void MediaQtAV::onAVError(const QtAV::AVError &e) {
+ lastErrorString = e.string();
+ qDebug() << lastErrorString;
+ emit error(lastErrorString);
+}
+
+void MediaQtAV::pause() {
+ currentPlayer->pause();
+}
+
+void MediaQtAV::stop() {
+ currentPlayer->stop();
+}
+
+void MediaQtAV::seek(qint64 ms) {
+ currentPlayer->setPosition(ms);
+}
+
+QtAV::AVPlayer *MediaQtAV::createPlayer(bool audioOnly) {
+ QtAV::AVPlayer *p = new QtAV::AVPlayer(this);
+
+#ifndef MEDIA_AUDIOONLY
+ if (!audioOnly) {
+ if (currentPlayer) {
+ p->setRenderer(currentPlayer->renderer());
+ p->setVideoDecoderPriority(currentPlayer->videoDecoderPriority());
+ } else {
+ QtAV::VideoRenderer *renderer = QtAV::VideoRenderer::create(rendererId);
+ if (!renderer || !renderer->isAvailable() || !renderer->widget()) {
+ qFatal("No QtAV video renderer");
+ }
+ p->setRenderer(renderer);
+ p->setVideoDecoderPriority(
+ {"CUDA", "D3D11", "DXVA", "VAAPI", "VideoToolbox", "FFmpeg"});
+ }
+ }
+#endif
+
+ p->setBufferMode(QtAV::BufferTime);
+
+ if (currentPlayer) {
+ p->setBufferValue(currentPlayer->bufferValue());
+ p->setOptionsForFormat(currentPlayer->optionsForFormat());
+ }
+
+ return p;
+}
+
+void MediaQtAV::connectPlayer(QtAV::AVPlayer *player) {
+ connect(player, &QtAV::AVPlayer::error, this, &MediaQtAV::onAVError);
+
+ connect(player, &QtAV::AVPlayer::sourceChanged, this, &Media::sourceChanged);
+ connect(player, &QtAV::AVPlayer::sourceChanged, this, [this] { aboutToFinishEmitted = false; });
+
+ connect(player, &QtAV::AVPlayer::bufferProgressChanged, this, &Media::bufferStatus);
+ connect(player, &QtAV::AVPlayer::started, this, &Media::started);
+ connect(player, &QtAV::AVPlayer::stopped, this, &Media::stopped);
+ connect(player, &QtAV::AVPlayer::paused, this, &Media::paused);
+
+ connect(player, &QtAV::AVPlayer::positionChanged, this, &Media::positionChanged);
+ connect(player, &QtAV::AVPlayer::positionChanged, this, &MediaQtAV::checkAboutToFinish);
+
+ connect(player, &QtAV::AVPlayer::stateChanged, this, [this](QtAV::AVPlayer::State state) {
+ const State s = stateFor(state);
+ if (s != PlayingState) {
+ emit stateChanged(s);
+ } else if (currentPlayer->mediaStatus() == QtAV::BufferedMedia) {
+ // needed when resuming from pause
+ emit stateChanged(s);
+ }
+ });
+ connect(player, &QtAV::AVPlayer::mediaStatusChanged, this, &MediaQtAV::onMediaStatusChange);
+
+ connect(player->audio(), &QtAV::AudioOutput::volumeChanged, this, &Media::volumeChanged);
+ connect(player->audio(), &QtAV::AudioOutput::muteChanged, this, &Media::volumeMutedChanged);
+}
+
+void MediaQtAV::setCurrentPlayer(QtAV::AVPlayer *player) {
+ if (currentPlayer) {
+ currentPlayer->disconnect(this);
+ player->audio()->setVolume(currentPlayer->audio()->volume());
+ player->audio()->setMute(currentPlayer->audio()->isMute());
+ player->setBufferValue(currentPlayer->bufferValue());
+ }
+ currentPlayer = player;
+ connectPlayer(currentPlayer);
+}
+
+void MediaQtAV::smoothSourceChange(const QString &file, const QString &externalAudio) {
+ qDebug() << "smoothSourceChange";
+ auto nextPlayer = player1;
+ if (currentPlayer == player1) {
+ if (player2 == nullptr) player2 = createPlayer(audioOnly);
+ nextPlayer = player2;
+ }
+ QObject *context = new QObject();
+ connect(nextPlayer, &QtAV::AVPlayer::loaded, context, [this, nextPlayer, context] {
+ qDebug() << "smoothSourceChange preloaded";
+ setCurrentPlayer(nextPlayer);
+
+ aboutToFinishEmitted = false;
+ lastErrorString.clear();
+ clearQueue();
+ emit sourceChanged();
+ context->deleteLater();
+
+ QObject *context2 = new QObject();
+ connect(nextPlayer, &QtAV::AVPlayer::mediaStatusChanged, context2,
+ [this, context2](QtAV::MediaStatus mediaStatus) {
+ if (mediaStatus == QtAV::BufferedMedia) {
+ qDebug() << "smoothSourceChange playing";
+ auto oldPlayer = currentPlayer == player1 ? player2 : player1;
+ oldPlayer->stop();
+ context2->deleteLater();
+ }
+ });
+ currentPlayer->play();
+ });
+ nextPlayer->setExternalAudio(externalAudio);
+ nextPlayer->setFile(file);
+ nextPlayer->load();
+}
--- /dev/null
+#ifndef MEDIAQTAV_H
+#define MEDIAQTAV_H
+
+#include "media.h"
+
+#include <QtAV>
+#ifndef MEDIA_AUDIOONLY
+#include <QtAVWidgets>
+#include <QtWidgets>
+#endif
+
+class MediaQtAV : public Media {
+ Q_OBJECT
+
+public:
+ MediaQtAV(QObject *parent = nullptr);
+#ifndef MEDIA_AUDIOONLY
+ void setRenderer(const QString &name);
+ QWidget *videoWidget();
+ void playSeparateAudioAndVideo(const QString &video, const QString &audio);
+ void snapshot();
+#endif
+ void setAudioOnly(bool value);
+ void init();
+
+ Media::State state() const;
+
+ void play(const QString &file);
+ void play();
+ void pause();
+ void stop();
+ void seek(qint64 ms);
+ QString file() const;
+
+ void setBufferMilliseconds(qint64 value);
+ void setUserAgent(const QString &value);
+
+ void enqueue(const QString &file);
+ void clearQueue();
+ bool hasQueue() const;
+
+ qint64 position() const;
+ qint64 duration() const;
+ qint64 remainingTime() const;
+
+ qreal volume() const;
+ void setVolume(qreal value);
+
+ bool volumeMuted() const;
+ void setVolumeMuted(bool value);
+
+ QString errorString() const;
+
+private slots:
+ void checkAboutToFinish(qint64 position);
+ void onMediaStatusChange(QtAV::MediaStatus status);
+ void onAVError(const QtAV::AVError &e);
+
+private:
+ QtAV::AVPlayer *createPlayer(bool audioOnly);
+ void connectPlayer(QtAV::AVPlayer *player);
+ void setCurrentPlayer(QtAV::AVPlayer *player);
+ void smoothSourceChange(const QString &file, const QString &externalAudio);
+
+ QtAV::AVPlayer *player1;
+ QtAV::AVPlayer *player2;
+ QtAV::AVPlayer *currentPlayer;
+
+ QQueue<QString> queue;
+ bool aboutToFinishEmitted = false;
+ QString lastErrorString;
+
+#ifndef MEDIA_AUDIOONLY
+ QtAV::VideoRendererId rendererId;
+#endif
+ bool audioOnly = false;
+};
+
+#endif // MEDIAQTAV_H
<source>Translate %1 to your native language using %2</source>
<translation>ترجم %1 إلى لغتك الأم بإستعمال %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>صمم الايقونة %1.</translation>
<source>Show Updated</source>
<translation>أظهر التّحديثات</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>لا يوجد لديك اشتراكات. استخدم رمز النجمة للإشتراك في القنوات.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>جميع الفيديوهات</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>لا يوجد تحديثات لقوائم اشتراكاتك حاليًا</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>لا يوجد لديك اشتراكات. استخدم رمز النجمة للإشتراك في القنوات.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>مسح</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 مشاهدة</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>هذه ليست سوى النسخة التجريبية من %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>يمكن تحميل الفيديو في أقل من %1 دقيقة بحيث يمكنك اختبار وظيفة التحميل.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>متابعة</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>احصل على النسخة الكاملة</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 حمل في %2</translation>
<source>&Float on Top</source>
<translation>&اطفو علي القمة</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&ضبط حجم النافذة</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&أوقف العرض التلقائي بعد الفيديو الحالي </translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>أت&حبّ %1؟ قيّمه!</translation>
<source>Update</source>
<translation>تحديث</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>الرابط سيكون صالحا لمدة محدودة.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>هذه ليست سوى النسخة التجريبية من %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>انها تتيح لك تجربة البرنامج.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>احصل على النسخة الكاملة</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>متابعة</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>جاري تحميل %1</translation>
<source>Subscribe to %1</source>
<translation>اشترك في %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>تم إلغاء متابعتك ل %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 مشاهدة</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>1% من 2% (3%) — 4%</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>جاري البحث...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>اظهر %1 المزيد</translation>
<translation>مرحبا بك في <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>أدخل</translation>
+ <source>to start watching videos.</source>
+ <translation>لبدء مشاهدة أشرطة الفيديو</translation>
</message>
<message>
<source>a keyword</source>
<translation> كلمة مفتاح</translation>
</message>
<message>
- <source>a channel</source>
- <translation>قناة</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>لبدء مشاهدة أشرطة الفيديو</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>شاهد</translation>
+ <source>Enter</source>
+ <translation>أدخل</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&عودة</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>تقدّم إلى %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>لا يمكن الحصول على دفق الفيديو %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>العالم</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>لا يمكن الحصول على دفق الفيديو %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduz %1 a la to llingua nativa usando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Iconu diseñáu por %1.</translation>
<source>Show Updated</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>All Videos</source>
<translation type="unfinished"/>
<source>There are no updated subscriptions at this time.</source>
<translation type="unfinished"/>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Llimpiar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 reproducciones</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esto ye namái la versión demo de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Namái pues descargar vídeos de duración menor que %1 minutos pa que puedas probar la función de descarga.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Siguir</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Consigui la versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descargáu en %2</translation>
<source>&Float on Top</source>
<translation>&Flotar na parte superior</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Detener tres d'esti videu</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>¿&Préstate %1? ¡Puntúalo!</translation>
<source>Update</source>
<translation>Anovar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Esto ye namái la versión de prueba de %1.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esto ye namái la versión demo de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Déxate probar l'aplicación y ver si te funciona.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Consigui la versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Siguir</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<source>Subscribe to %1</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 reproducciones</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Guetando...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Amosar %1 más</translation>
<translation>Bienveníu a <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Escribi</translation>
+ <source>to start watching videos.</source>
+ <translation>pa entamar a ver vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una pallabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>una canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>pa entamar a ver vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>ver</translation>
+ <source>Enter</source>
+ <translation>Escribi</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Atrás</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Dir a %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nun pue obtenese'l fluxu de videu pa %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Tol mundu</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nun pue obtenese'l fluxu de videu pa %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Перакладайце %1 на родную мову праз %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Дызайн іконкі выканаў %1.</translation>
<source>Show Updated</source>
<translation>Паказаць абноўленыя</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Няма жаднай падпіскі. Ужывайце зорачку, каб падпісвацца на каналы.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Усе відэа</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Падпіскі пакуль не абнаўляліся.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Няма жаднай падпіскі. Ужывайце зорачку, каб падпісвацца на каналы.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Ачысціць</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 праглядаў</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Гэта дэма-версія %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Можна пампаваць толькі відэа, каротшыя за %1 хв., што дастаткова для тэсціравання.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Працягнуць</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Атрымаць поўную версію</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 сцягнута ў %2</translation>
<source>&Float on Top</source>
<translation>&Заставацца па-над усімі вокнамі</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Спыніцца пасля гэтага відэа</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Падабаецца %1? Ацані яго!</translation>
<source>Update</source>
<translation>Абнаўленне</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Спасылка будзе заставацца слушнай абмежаваны час.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Гэта дэма-версія %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Яна дазваляе пратэсціраваць праграму і праверыць на адпаведнасць вашым задачам.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Атрымаць поўную версію</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Працягнуць</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Сцягваецца %1</translation>
<source>Subscribe to %1</source>
<translation>Падпісацца на %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 праглядаў</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 з %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Пошук...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Паказаць яшчэ %1</translation>
<translation>Ласкава запрашаем у <a href='%1'>%2</a></translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Задайце</translation>
+ <source>to start watching videos.</source>
+ <translation>, каб пачаць глядзець відэа.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ключавое слова</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>, каб пачаць глядзець відэа.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Глядзець</translation>
+ <source>Enter</source>
+ <translation>Задайце</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Назад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Наперад да %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Не ўдалося атрымаць відэапаток для %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Увесь свет</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Не ўдалося атрымаць відэа-паток для %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Преведи &1 на твоя роден език използвайки &2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Иконите са изработени от %1.</translation>
<source>Show Updated</source>
<translation>Покажи обновени</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Нямате абонаменти. Изполвай звезда за абониране към канали</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Всички видео клипове</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Няма обновени абонаменти</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Нямате абонаменти. Изполвай звезда за абониране към канали</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Изчисти</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 гледания</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Това е демо верция на %1</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Може да изтегляте видео клипове по-къси от &1 минута, за да изпробвате функцията за изтегляне.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продължи</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Пълна версия</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 изтеглено за %2</translation>
<source>&Float on Top</source>
<translation>&Залепи най отгоре</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Спри след този видео клип</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"/>
<source>Update</source>
<translation>Обнови</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Линка ще е валиден само за определено време.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Това е демо верция на %1</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Позволява ви да изпробвате програмата, за да проверите дали работи добре при вас.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Пълна версия</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продължи</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>изтеглане %1</translation>
<source>Subscribe to %1</source>
<translation>Абонирай се за %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 гледания</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 от %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Търся...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Покажи %1 повече</translation>
<translation>Добре дошли в <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Напишете</translation>
+ <source>to start watching videos.</source>
+ <translation>за гледане на видео клипове</translation>
</message>
<message>
<source>a keyword</source>
<translation>ключова дума</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>за гледане на видео клипове</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Гледай</translation>
+ <source>Enter</source>
+ <translation>Напишете</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Назад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Напред до %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation type="unfinished"/>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>На световно ниво</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduïu el %1 al vostre idioma natal utilitzant %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Creat per %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Programari de codi obert</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icona dissenyada per %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Teniu %n vídeo(s) nou(s)</numerusform><numerusform>Teniu %n vídeos nous</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Mostra actualitzats</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Tots els vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>No hi han subscripcions actualitzades.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Neteja</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>fa %n hores</numerusform><numerusform>fa %n hores</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>fa %n dies</numerusform><numerusform>fa %n dies</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>fa %n mesos</numerusform><numerusform>fa %n mesos</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualitzacions</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>fa %n setmanes</numerusform><numerusform>fa %n setmanes</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aquesta només és la versió de demostració del %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Només pot baixar vídeos de menys de %1 minuts per tal que en pugui provar aquesta funció.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Aconsegueix la versió completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descarregat en %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n baixades</numerusform><numerusform>%n baixades</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>Manté a &sobre</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajusta la mida de la finestra</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Atura després d'aquest vídeo</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Mode restringit</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
- <translation type="unfinished"/>
+ <translation>Amaga vídeos amb contingut no apropiat</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>A&maga la barra de menú</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menú</translation>
</message>
<message>
<source>&Love %1? Rate it!</source>
<source>Update</source>
<translation>Actualitza</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Encara podreu accedir a les opcions del menú mitjançant la tecla ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>L'enllaç només serà vàlid durant un temps limitat.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aquesta només és la versió de demostració del %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Us permet probar l'aplicació i veure si us va bé.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Aconseguiu la versió completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Baixant %1</translation>
<source>Subscribe to %1</source>
<translation>Subscriu-me a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>S'ha canviat a %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>De-subscrit de %1 </translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visualitzacions</translation>
+ <source>Pick a video</source>
+ <translation>Trieu un vídeo</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Cercant...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostra %1 Més</translation>
<translation>Benvinguts al <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introdueix</translation>
+ <source>to start watching videos.</source>
+ <translation>per comencar a veure vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una paraula</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>per comencar a veure vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Veure</translation>
+ <source>Enter</source>
+ <translation>Introdueix</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Enrere</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>E&ndavant</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avança a %1</translation>
<translation>Descarregant %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No es pot obtenir flux de vídeo per %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Global</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No es pot obtenir flux de vídeo per %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduïu el %1 al vostre idioma natal utilitzant %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icona dissenyada per %1.</translation>
<source>Show Updated</source>
<translation>Mostra actualitzats</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Tots els vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>No hi han subscripcions actualitzades.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No teniu subscripcions. Feu servir el símbol de l'estrella per subscríure-us als canals.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Neteja</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualitzacions</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aquesta només és la versió de demostració del %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Només pot baixar vídeos de menys de %1 minuts per tal que en pugui provar aquesta funció.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Aconsegueix la versió completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descarregat en %2</translation>
<source>&Float on Top</source>
<translation>Manté a &sobre</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Atura després d'aquest vídeo</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>T'&Agrada %1? Puntua'l!</translation>
<source>Update</source>
<translation>Actualitza</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>L'enllaç només serà vàlid durant un temps limitat.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aquesta només és la versió de demostració del %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Us permet probar l'aplicació i veure si us va bé.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Aconseguiu la versió completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Baixant %1</translation>
<source>Subscribe to %1</source>
<translation>Subscriu-me a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visualitzacions</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Cercant...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostra %1 Més</translation>
<translation>Benvinguts al <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introdueix</translation>
+ <source>to start watching videos.</source>
+ <translation>per comencar a veure vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una paraula</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>per comencar a veure vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Veure</translation>
+ <source>Enter</source>
+ <translation>Introdueix</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Enrere</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avança a %1</translation>
<translation>Descarregant %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No es pot obtenir flux de vídeo per %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Global</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No es pot obtenir flux de vídeo per %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Přeložte %1 do vašeho mateřského jazyka pomocí %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Autor ikony: %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Zobrazit aktualizace</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nemáte žádné odběry. Použijte hvězdičku k přihlásení odběru kanálů.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Všechna videa</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Nyní nejsou k dispozici žádné aktualizace odběrů.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nemáte žádné odběry. Použijte hvězdičku k přihlásení odběru kanálů.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Odstranit vše</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 zobrazení</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Toto je pouze demoverze %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Umí stahovat pouze videa délky do %1 minut, abyste mohli funkci stahování vyzkoušet</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Pokračovat</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Získat plnou verzi</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 staženo v %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Plovoucí navrchu</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Přizpůsobit velikost okna</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Zastavit po tomto videu</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Skrýt videa, která by mohla obsahovat nevhodný obsah</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Přepnout pruh s &nabídkou</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Nabídka</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Líbí se %1? Ohodnotit!</translation>
<source>Update</source>
<translation>Aktualizovat</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Stále ještě můžete přistupovat k pruhu s nabídkou stisknutím klávesy Alt</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Tento odkaz platí jen po omezenou dobu.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Toto je %1 -- demoverze.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Umožňuje vyzkoušet aplikaci, abyste ověřili, jestli pro vás funguje.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Stáhnout plnou verzi</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Pokračovat</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Je stahováno %1</translation>
<source>Subscribe to %1</source>
<translation>Přihlásit k %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Odhlášen z odběru %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 zobrazení</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 z %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Hledá se...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Zobrazit dalších %1</translation>
<translation>Vítejte v <a href='%1'>%2</a></translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Vložit</translation>
+ <source>to start watching videos.</source>
+ <translation> pro sledování videí.</translation>
</message>
<message>
<source>a keyword</source>
<translation>klíčové slovo</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanál</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation> pro sledování videí.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Sledovat</translation>
+ <source>Enter</source>
+ <translation>Vložit</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Zpět</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Předat k %1</translation>
<translation>Stahování %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nelze získat video stream pro %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Celosvětově</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nelze získat video stream pro %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Oversæt %1 til din modersmål ved hjælp af %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikon designet af %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Hent</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Show opdateret</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Du har ingen abonnementer. Brug stjernetegnet til at abonnere på kanaler.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alle videoer</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Der er ingen opdateringer i de abonnerede.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Du har ingen abonnementer. Brug stjernetegnet til at abonnere på kanaler.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Fjern</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visninger</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er kun demoversionen af %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Det kan kun hente videoer kortere end %1 minut, så du kan teste downloadfunktionaliteten.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Forsæt</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hent den fulde version</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 downloaded på %2</translation>
<source>&Float on Top</source>
<translation>&Behold øverst</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Juster vinduesstørrelse</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stop efter denne video</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Begrænset-Måde</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
+ <translation>Gem videoer der muligvis indeholder stødene indhold.</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
<translation type="unfinished"/>
</message>
<message>
<source>Update</source>
<translation>Opdatér</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Linket vil kun være gyldigt i en begrænset periode.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er kun demoversionen af %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Det giver dig mulighed for at teste programmet og se om det virker for dig.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hent den fulde version</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Forsæt</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Downloader %1</translation>
<source>Subscribe to %1</source>
<translation>Abonner på %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Abonnerer ikke længere på %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visninger</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 af %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Søger...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Vis %1 mere</translation>
<translation>Velkommen til <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Indtast</translation>
+ <source>to start watching videos.</source>
+ <translation>for at begynde at se video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>et nøgleord</translation>
</message>
<message>
- <source>a channel</source>
- <translation>en kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>for at begynde at se video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Afspil</translation>
+ <source>Enter</source>
+ <translation>Indtast</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Tilbage</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Frem til %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Kan ikke hente videostrøm for %1</translation>
+ <translation>Henter %1...</translation>
</message>
</context>
<context>
<translation>Hele verden</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Kan ikke hente videostrøm for %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Übersetzen Sie %1 in Ihre Muttersprache mit %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icons wurden gestaltet von %1.</translation>
<source>Show Updated</source>
<translation>Zeige aktualisierte</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Du hast keine Abonnements. Benutze das Stern-Symbol, um einen Kanal zu abonnieren.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alle Videos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Zurzeit gibt es nichts Neues bei den Abonnements.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Du hast keine Abonnements. Benutze das Stern-Symbol, um einen Kanal zu abonnieren.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Säubern</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 mal betrachtet</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dies ist nur die Demoversion von %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Sie kann nur Videos herunterladen, die kürzer als %1 Minuten sind, um die Funktion zum Herunterladen zu testen.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortfahren</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Die Vollversion kaufen</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 heruntergeladen nach %2</translation>
<source>&Float on Top</source>
<translation>Im Vordergrund &bleiben</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Fenstergröße anpassen</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Nach diesem Video &anhalten</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Verstecke Videos die unpassende Inhalte enthalten können</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Gefällt Ihnen %1? Bewertung abgeben!</translation>
<source>Update</source>
<translation>Aktualisierung</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Der Link wird nur eine beschränkte Zeit gültig sein.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dies ist nur die Demoversion von %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Sie erlaubt es Ihnen, die Anwendung zu testen und zu schauen, ob sie bei Ihnen läuft.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Die Vollversion kaufen</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortfahren</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1 herunterladen</translation>
<source>Subscribe to %1</source>
<translation>Abonnieren von %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Abonnement von %1 wurde aufgehoben.</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 mal betrachtet</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 von %2 (%3) – %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Suche...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Weitere %1 zeigen</translation>
<translation>Willkommen bei <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Eingeben</translation>
+ <source>to start watching videos.</source>
+ <translation>um die Wiedergabe zu starten.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ein Suchbegriff</translation>
</message>
<message>
- <source>a channel</source>
- <translation>ein Kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>um die Wiedergabe zu starten.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Anschauen</translation>
+ <source>Enter</source>
+ <translation>Eingeben</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Zurück</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Weiter zu %1</translation>
<translation>heruntergeladen %1</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Videostream für %1 konnte nicht geöffnet werden</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Weltweit</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Videostream für %1 konnte nicht geöffnet werden</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Μεταφράστε το %1 στη γλώσσα σας με χρήση του %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Σχεδιασμός εικονιδίου από %1.</translation>
<source>Show Updated</source>
<translation>Εμφάνιση ενημερωμένων</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Δεν έχετε συνδρομές. Χρησιμοποιήστε το αστέρι για να κάνετε συνδρομή σε κανάλια.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Όλα τα βίντεο</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Δεν υπάρχουν ενημερωμένες συδρομές αυτήν την στιγμή.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Δεν έχετε συνδρομές. Χρησιμοποιήστε το αστέρι για να κάνετε συνδρομή σε κανάλια.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Εκκαθάριση</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 προβολές</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Αυτή είναι απλά η δοκιμαστική έκδοση του %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Μπορεί να κάνει λήψη βίντεο μικρότερα από %1 λεπτά ώστε να δοκιμάσετε τη λειτουργία λήψης.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Συνέχεια</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Αποκτήστε την πλήρη έκδοση</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 λήφθηκε σε %2</translation>
<source>&Float on Top</source>
<translation>&Διατήρηση στην κορυφή</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Προσαρμογή του μεγέθους του παραθύρου</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Διακοπή μετά από αυτό το βίντεο</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Λατρεύετε το %1; Βαθμολογήστε το!</translation>
<source>Update</source>
<translation>Ενημέρωση</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Ο σύνδεσμος θα είναι έγκυρος για περιορισμένο χρονικό διάστημα.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Αυτή είναι απλά μια δοκιμαστική έκδοση του %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Σαε επιτρέπει να δοκιμάσετε την εφαρμογή και να δείτε αν σας κάνει.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Αποκτήστε τη πλήρη έκδοση</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Συνέχεια</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Λήψη %1</translation>
<source>Subscribe to %1</source>
<translation>Εγγραφή στο %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Διεγράφη από το %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 προβολές</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 από %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Αναζήτηση...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Εμφάνιση %1 ακόμα</translation>
<translation>Καλωσορίσατε στο <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Εισάγετε</translation>
+ <source>to start watching videos.</source>
+ <translation>για να αρχίσετε να βλέπετε βίντεο.</translation>
</message>
<message>
<source>a keyword</source>
<translation>μια λέξη-κλειδί</translation>
</message>
<message>
- <source>a channel</source>
- <translation>ένα κανάλι</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>για να αρχίσετε να βλέπετε βίντεο.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Παρακολουθήστε</translation>
+ <source>Enter</source>
+ <translation>Εισάγετε</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Επιστροφή</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Προώθηση σε %1</translation>
<translation>Λήψη %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Αδυναμία λήψης της ροής βίντεο για το %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Παγκοσμίως</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Αδυναμία λήψης της ροής βίντεο για το %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<location filename="../src/channelaggregator.cpp" line="185"/>
<source>You have %n new video(s)</source>
<translation type="unfinished">
- <numerusform>You have one new video</numerusform>
+ <numerusform>You have a new video</numerusform>
<numerusform>You have %n new videos</numerusform>
</translation>
</message>
<location filename="../src/downloadmanager.cpp" line="165"/>
<source>%n Download(s)</source>
<translation type="unfinished">
- <numerusform>One Download</numerusform>
+ <numerusform>1 Download</numerusform>
<numerusform>%n Downloads</numerusform>
</translation>
</message>
--- /dev/null
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="en_GB" version="2.1">
+<context>
+ <name>AboutView</name>
+ <message>
+ <source>There's life outside the browser!</source>
+ <translation>There's life outside the browser!</translation>
+ </message>
+ <message>
+ <source>Version %1</source>
+ <translation>Version %1</translation>
+ </message>
+ <message>
+ <source>Licensed to: %1</source>
+ <translation>Licensed to: %1</translation>
+ </message>
+ <message>
+ <source>%1 is Free Software but its development takes precious time.</source>
+ <translation>%1 is Free Software but its development takes precious time.</translation>
+ </message>
+ <message>
+ <source>Please <a href='%1'>donate</a> to support the continued development of %2.</source>
+ <translation>Please <a href='%1'>donate</a> to support the continued development of %2.</translation>
+ </message>
+ <message>
+ <source>Translate %1 to your native language using %2</source>
+ <translation>Translate %1 to your native language using %2</translation>
+ </message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Powered by %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Open-source software</translation>
+ </message>
+ <message>
+ <source>Icon designed by %1.</source>
+ <translation>Icon designed by %1.</translation>
+ </message>
+ <message>
+ <source>Released under the <a href='%1'>GNU General Public License</a></source>
+ <translation>Released under the <a href='%1'>GNU General Public License</a></translation>
+ </message>
+ <message>
+ <source>&Close</source>
+ <translation>&Close</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>About</translation>
+ </message>
+</context>
+<context>
+ <name>ActivationDialog</name>
+ <message>
+ <source>Enter your License Details</source>
+ <translation>Enter your Licence Details</translation>
+ </message>
+ <message>
+ <source>&Email:</source>
+ <translation>&Email:</translation>
+ </message>
+ <message>
+ <source>&Code:</source>
+ <translation>&Code:</translation>
+ </message>
+</context>
+<context>
+ <name>ActivationView</name>
+ <message>
+ <source>Please license %1</source>
+ <translation>Please license %1</translation>
+ </message>
+ <message>
+ <source>This demo has expired.</source>
+ <translation>This demo has expired.</translation>
+ </message>
+ <message>
+ <source>The full version allows you to watch videos without interruptions.</source>
+ <translation>The full version allows you to watch videos without interruptions.</translation>
+ </message>
+ <message>
+ <source>Without a license, the application will expire in %1 days.</source>
+ <translation>Without a licence, the application will expire in %1 days.</translation>
+ </message>
+ <message>
+ <source>By purchasing the full version, you will also support the hard work I put into creating %1.</source>
+ <translation>By purchasing the full version, you will also support the hard work I put into creating %1.</translation>
+ </message>
+ <message>
+ <source>Use Demo</source>
+ <translation>Use Demo</translation>
+ </message>
+ <message>
+ <source>Enter License</source>
+ <translation>Enter Licence</translation>
+ </message>
+ <message>
+ <source>Buy License</source>
+ <translation>Buy Licence</translation>
+ </message>
+</context>
+<context>
+ <name>AppWidget</name>
+ <message>
+ <source>Download</source>
+ <translation>Download</translation>
+ </message>
+</context>
+<context>
+ <name>ChannelAggregator</name>
+ <message>
+ <source>By %1</source>
+ <translation>By %1</translation>
+ </message>
+ <message numerus="yes">
+ <source>You have %n new video(s)</source>
+ <translation><numerusform>You have a new video</numerusform><numerusform>You have %n new videos</numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>ChannelItemDelegate</name>
+ <message>
+ <source>All Videos</source>
+ <translation>All Videos</translation>
+ </message>
+ <message>
+ <source>Unwatched Videos</source>
+ <translation>Unwatched Videos</translation>
+ </message>
+</context>
+<context>
+ <name>ChannelView</name>
+ <message>
+ <source>Name</source>
+ <translation>Name</translation>
+ </message>
+ <message>
+ <source>Last Updated</source>
+ <translation>Last Updated</translation>
+ </message>
+ <message>
+ <source>Last Added</source>
+ <translation>Last Added</translation>
+ </message>
+ <message>
+ <source>Last Watched</source>
+ <translation>Last Watched</translation>
+ </message>
+ <message>
+ <source>Most Watched</source>
+ <translation>Most Watched</translation>
+ </message>
+ <message>
+ <source>Sort by</source>
+ <translation>Sort by</translation>
+ </message>
+ <message>
+ <source>Mark all as watched</source>
+ <translation>Mark all as watched</translation>
+ </message>
+ <message>
+ <source>Show Updated</source>
+ <translation>Show Updated</translation>
+ </message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>You have no subscriptions. Use the star symbol to subscribe to channels.</translation>
+ </message>
+ <message>
+ <source>All Videos</source>
+ <translation>All Videos</translation>
+ </message>
+ <message>
+ <source>Unwatched Videos</source>
+ <translation>Unwatched Videos</translation>
+ </message>
+ <message>
+ <source>Mark as Watched</source>
+ <translation>Mark as Watched</translation>
+ </message>
+ <message>
+ <source>Unsubscribe</source>
+ <translation>Unsubscribe</translation>
+ </message>
+ <message>
+ <source>There are no updated subscriptions at this time.</source>
+ <translation>There are no updated subscriptions at this time.</translation>
+ </message>
+</context>
+<context>
+ <name>DataUtils</name>
+ <message>
+ <source>Just now</source>
+ <translation>Just now</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n hour(s) ago</source>
+ <translation><numerusform>An hour ago</numerusform><numerusform>%n hours ago</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <source>%n day(s) ago</source>
+ <translation><numerusform>Yesterday</numerusform><numerusform>%n days ago</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <source>%n month(s) ago</source>
+ <translation><numerusform>A month ago</numerusform><numerusform>%n month(s) ago</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 views</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n week(s) ago</source>
+ <translation><numerusform>A week ago</numerusform><numerusform>%n weeks ago</numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadItem</name>
+ <message>
+ <source>bytes</source>
+ <translation>bytes</translation>
+ </message>
+ <message>
+ <source>KB</source>
+ <translation>KB</translation>
+ </message>
+ <message>
+ <source>MB</source>
+ <translation>MB</translation>
+ </message>
+ <message>
+ <source>bytes/sec</source>
+ <translation>bytes/sec</translation>
+ </message>
+ <message>
+ <source>KB/sec</source>
+ <translation>KB/sec</translation>
+ </message>
+ <message>
+ <source>MB/sec</source>
+ <translation>MB/sec</translation>
+ </message>
+ <message>
+ <source>seconds</source>
+ <translation>seconds</translation>
+ </message>
+ <message>
+ <source>minutes</source>
+ <translation>minutes</translation>
+ </message>
+ <message>
+ <source>%4 %5 remaining</source>
+ <translation>%4 %5 remaining</translation>
+ </message>
+</context>
+<context>
+ <name>DownloadManager</name>
+ <message>
+ <source>%1 downloaded in %2</source>
+ <translation>%1 downloaded in %2</translation>
+ </message>
+ <message>
+ <source>Download finished</source>
+ <translation>Download finished</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n Download(s)</source>
+ <translation><numerusform>1 Download</numerusform><numerusform>%n Downloads</numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadSettings</name>
+ <message>
+ <source>Change location...</source>
+ <translation>Change location...</translation>
+ </message>
+ <message>
+ <source>Choose the download location</source>
+ <translation>Choose the download location</translation>
+ </message>
+ <message>
+ <source>Download location changed.</source>
+ <translation>Download location changed.</translation>
+ </message>
+ <message>
+ <source>Current downloads will still go in the previous location.</source>
+ <translation>Current downloads will still go into the previous location.</translation>
+ </message>
+ <message>
+ <source>Downloading to: %1</source>
+ <translation>Downloading to: %1</translation>
+ </message>
+</context>
+<context>
+ <name>DownloadView</name>
+ <message>
+ <source>Downloads</source>
+ <translation>Downloads</translation>
+ </message>
+</context>
+<context>
+ <name>Extra</name>
+ <message>
+ <source>The executable file has been tempered with, maybe by a virus.</source>
+ <translation>The executable file has been tampered with, maybe by a virus.</translation>
+ </message>
+ <message>
+ <source>%1 will not run. Try installing again.</source>
+ <translation>%1 will not run. Try installing again.</translation>
+ </message>
+ <message>
+ <source>Quit</source>
+ <translation>Quit</translation>
+ </message>
+ <message>
+ <source>Reinstall</source>
+ <translation>Reinstall</translation>
+ </message>
+</context>
+<context>
+ <name>GlobalShortcuts</name>
+ <message>
+ <source>Play</source>
+ <translation>Play</translation>
+ </message>
+ <message>
+ <source>Pause</source>
+ <translation>Pause</translation>
+ </message>
+ <message>
+ <source>Play/Pause</source>
+ <translation>Play/Pause</translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation>Stop</translation>
+ </message>
+ <message>
+ <source>Stop playing after current track</source>
+ <translation>Stop playing after current track</translation>
+ </message>
+ <message>
+ <source>Next track</source>
+ <translation>Next track</translation>
+ </message>
+ <message>
+ <source>Previous track</source>
+ <translation>Previous track</translation>
+ </message>
+ <message>
+ <source>Increase volume</source>
+ <translation>Increase volume</translation>
+ </message>
+ <message>
+ <source>Decrease volume</source>
+ <translation>Decrease volume</translation>
+ </message>
+ <message>
+ <source>Mute</source>
+ <translation>Mute</translation>
+ </message>
+ <message>
+ <source>Seek forward</source>
+ <translation>Seek forward</translation>
+ </message>
+ <message>
+ <source>Seek backward</source>
+ <translation>Seek backward</translation>
+ </message>
+</context>
+<context>
+ <name>HomeView</name>
+ <message>
+ <source>Search</source>
+ <translation>Search</translation>
+ </message>
+ <message>
+ <source>Find videos and channels by keyword</source>
+ <translation>Find videos and channels by keyword</translation>
+ </message>
+ <message>
+ <source>Browse</source>
+ <translation>Browse</translation>
+ </message>
+ <message>
+ <source>Browse videos by category</source>
+ <translation>Browse videos by category</translation>
+ </message>
+ <message>
+ <source>Subscriptions</source>
+ <translation>Subscriptions</translation>
+ </message>
+ <message>
+ <source>Channel subscriptions</source>
+ <translation>Channel subscriptions</translation>
+ </message>
+ <message>
+ <source>Make yourself comfortable</source>
+ <translation>Make yourself comfortable</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingWidget</name>
+ <message>
+ <source>Error</source>
+ <translation>Error</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <source>&Window</source>
+ <translation>&Window</translation>
+ </message>
+ <message>
+ <source>&Minimize</source>
+ <translation>&Minimise</translation>
+ </message>
+ <message>
+ <source>&Stop</source>
+ <translation>&Stop</translation>
+ </message>
+ <message>
+ <source>Stop playback and go back to the search view</source>
+ <translation>Stop playback and go back to the search view</translation>
+ </message>
+ <message>
+ <source>P&revious</source>
+ <translation>&Previous</translation>
+ </message>
+ <message>
+ <source>Go back to the previous track</source>
+ <translation>Go back to the previous track</translation>
+ </message>
+ <message>
+ <source>S&kip</source>
+ <translation>&Skip</translation>
+ </message>
+ <message>
+ <source>Skip to the next video</source>
+ <translation>Skip to the next video</translation>
+ </message>
+ <message>
+ <source>&Play</source>
+ <translation>&Play</translation>
+ </message>
+ <message>
+ <source>Resume playback</source>
+ <translation>Resume playback</translation>
+ </message>
+ <message>
+ <source>&Full Screen</source>
+ <translation>&Full Screen</translation>
+ </message>
+ <message>
+ <source>Go full screen</source>
+ <translation>Go full screen</translation>
+ </message>
+ <message>
+ <source>&Compact Mode</source>
+ <translation>&Compact Mode</translation>
+ </message>
+ <message>
+ <source>Hide the playlist and the toolbar</source>
+ <translation>Hide the playlist and the toolbar</translation>
+ </message>
+ <message>
+ <source>Open the &YouTube Page</source>
+ <translation>Open the &YouTube Page</translation>
+ </message>
+ <message>
+ <source>Go to the YouTube video page and pause playback</source>
+ <translation>Go to the YouTube video page and pause playback</translation>
+ </message>
+ <message>
+ <source>Copy the YouTube &Link</source>
+ <translation>Copy the YouTube &Link</translation>
+ </message>
+ <message>
+ <source>Copy the current video YouTube link to the clipboard</source>
+ <translation>Copy the current video YouTube link to the clipboard</translation>
+ </message>
+ <message>
+ <source>Copy the Video Stream &URL</source>
+ <translation>Copy the Video Stream &URL</translation>
+ </message>
+ <message>
+ <source>Copy the current video stream URL to the clipboard</source>
+ <translation>Copy the current video stream URL to the clipboard</translation>
+ </message>
+ <message>
+ <source>Find Video &Parts</source>
+ <translation>Find Video &Parts</translation>
+ </message>
+ <message>
+ <source>Find other video parts hopefully in the right order</source>
+ <translation>Find other video parts hopefully in the right order</translation>
+ </message>
+ <message>
+ <source>&Remove</source>
+ <translation>&Remove</translation>
+ </message>
+ <message>
+ <source>Remove the selected videos from the playlist</source>
+ <translation>Remove the selected videos from the playlist</translation>
+ </message>
+ <message>
+ <source>Move &Up</source>
+ <translation>Move &Up</translation>
+ </message>
+ <message>
+ <source>Move up the selected videos in the playlist</source>
+ <translation>Move up the selected videos in the playlist</translation>
+ </message>
+ <message>
+ <source>Move &Down</source>
+ <translation>Move &Down</translation>
+ </message>
+ <message>
+ <source>Move down the selected videos in the playlist</source>
+ <translation>Move down the selected videos in the playlist</translation>
+ </message>
+ <message>
+ <source>&Clear Recent Searches</source>
+ <translation>&Clear Recent Searches</translation>
+ </message>
+ <message>
+ <source>Clear the search history. Cannot be undone.</source>
+ <translation>Clear the search history. Cannot be undone.</translation>
+ </message>
+ <message>
+ <source>&Quit</source>
+ <translation>&Quit</translation>
+ </message>
+ <message>
+ <source>Bye</source>
+ <translation>Bye</translation>
+ </message>
+ <message>
+ <source>&Website</source>
+ <translation>&Website</translation>
+ </message>
+ <message>
+ <source>%1 on the Web</source>
+ <translation>%1 on the Web</translation>
+ </message>
+ <message>
+ <source>Make a &Donation</source>
+ <translation>Make a &Donation</translation>
+ </message>
+ <message>
+ <source>Please support the continued development of %1</source>
+ <translation>Please support the continued development of %1</translation>
+ </message>
+ <message>
+ <source>&About</source>
+ <translation>&About</translation>
+ </message>
+ <message>
+ <source>Info about %1</source>
+ <translation>Info about %1</translation>
+ </message>
+ <message>
+ <source>Search</source>
+ <translation>Search</translation>
+ </message>
+ <message>
+ <source>Mute volume</source>
+ <translation>Mute volume</translation>
+ </message>
+ <message>
+ <source>&Manually Start Playing</source>
+ <translation>&Manually Start Playing</translation>
+ </message>
+ <message>
+ <source>Manually start playing videos</source>
+ <translation>Manually start playing videos</translation>
+ </message>
+ <message>
+ <source>&Downloads</source>
+ <translation>&Downloads</translation>
+ </message>
+ <message>
+ <source>Show details about video downloads</source>
+ <translation>Show details about video downloads</translation>
+ </message>
+ <message>
+ <source>&Download</source>
+ <translation>&Download</translation>
+ </message>
+ <message>
+ <source>Download the current video</source>
+ <translation>Download the current video</translation>
+ </message>
+ <message>
+ <source>Take &Snapshot</source>
+ <translation>Take &Snapshot</translation>
+ </message>
+ <message>
+ <source>&Subscribe to Channel</source>
+ <translation>&Subscribe to Channel</translation>
+ </message>
+ <message>
+ <source>Share the current video using %1</source>
+ <translation>Share the current video using %1</translation>
+ </message>
+ <message>
+ <source>&Email</source>
+ <translation>&Email</translation>
+ </message>
+ <message>
+ <source>Email</source>
+ <translation>Email</translation>
+ </message>
+ <message>
+ <source>&Close</source>
+ <translation>&Close</translation>
+ </message>
+ <message>
+ <source>&Float on Top</source>
+ <translation>&Float on Top</translation>
+ </message>
+ <message>
+ <source>&Stop After This Video</source>
+ <translation>&Stop After This Video</translation>
+ </message>
+ <message>
+ <source>&Report an Issue...</source>
+ <translation>&Report an Issue...</translation>
+ </message>
+ <message>
+ <source>&Refine Search...</source>
+ <translation>&Refine Search...</translation>
+ </message>
+ <message>
+ <source>More...</source>
+ <translation>More...</translation>
+ </message>
+ <message>
+ <source>&Related Videos</source>
+ <translation>&Related Videos</translation>
+ </message>
+ <message>
+ <source>Watch videos related to the current one</source>
+ <translation>Watch videos related to the current one</translation>
+ </message>
+ <message>
+ <source>Open in &Browser...</source>
+ <translation>Open in &Browser...</translation>
+ </message>
+ <message>
+ <source>Restricted Mode</source>
+ <translation>Restricted Mode</translation>
+ </message>
+ <message>
+ <source>Hide videos that may contain inappropriate content</source>
+ <translation>Hide videos that may contain inappropriate content</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Toggle &Menu Bar</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
+ </message>
+ <message>
+ <source>&Love %1? Rate it!</source>
+ <translation>&Love %1? Rate it!</translation>
+ </message>
+ <message>
+ <source>Buy %1...</source>
+ <translation>Buy %1...</translation>
+ </message>
+ <message>
+ <source>&Application</source>
+ <translation>&Application</translation>
+ </message>
+ <message>
+ <source>&Playback</source>
+ <translation>&Playback</translation>
+ </message>
+ <message>
+ <source>&Playlist</source>
+ <translation>&Playlist</translation>
+ </message>
+ <message>
+ <source>&Video</source>
+ <translation>&Video</translation>
+ </message>
+ <message>
+ <source>&Share</source>
+ <translation>&Share</translation>
+ </message>
+ <message>
+ <source>&View</source>
+ <translation>&View</translation>
+ </message>
+ <message>
+ <source>&Help</source>
+ <translation>&Help</translation>
+ </message>
+ <message>
+ <source>Press %1 to raise the volume, %2 to lower it</source>
+ <translation>Press %1 to raise the volume, %2 to lower it</translation>
+ </message>
+ <message>
+ <source>Choose your content location</source>
+ <translation>Choose your content location</translation>
+ </message>
+ <message>
+ <source>Opening %1</source>
+ <translation>Opening %1</translation>
+ </message>
+ <message>
+ <source>Do you want to exit %1 with a download in progress?</source>
+ <translation>Do you want to exit %1 with a download in progress?</translation>
+ </message>
+ <message>
+ <source>If you close %1 now, this download will be cancelled.</source>
+ <translation>If you close %1 now, this download will be cancelled.</translation>
+ </message>
+ <message>
+ <source>Close and cancel download</source>
+ <translation>Close and cancel download</translation>
+ </message>
+ <message>
+ <source>Wait for download to finish</source>
+ <translation>Wait for download to finish</translation>
+ </message>
+ <message>
+ <source>Error: %1</source>
+ <translation>Error: %1</translation>
+ </message>
+ <message>
+ <source>&Pause</source>
+ <translation>&Pause</translation>
+ </message>
+ <message>
+ <source>Pause playback</source>
+ <translation>Pause playback</translation>
+ </message>
+ <message>
+ <source>&Loading...</source>
+ <translation>&Loading...</translation>
+ </message>
+ <message>
+ <source>Leave &Full Screen</source>
+ <translation>Leave &Full Screen</translation>
+ </message>
+ <message>
+ <source>Remaining time: %1</source>
+ <translation>Remaining time: %1</translation>
+ </message>
+ <message>
+ <source>Volume at %1%</source>
+ <translation>Volume at %1%</translation>
+ </message>
+ <message>
+ <source>Volume is muted</source>
+ <translation>Volume is muted</translation>
+ </message>
+ <message>
+ <source>Volume is unmuted</source>
+ <translation>Volume is unmuted</translation>
+ </message>
+ <message>
+ <source>Maximum video definition set to %1</source>
+ <translation>Maximum video definition set to %1</translation>
+ </message>
+ <message>
+ <source>Your privacy is now safe</source>
+ <translation>Your privacy is now safe</translation>
+ </message>
+ <message>
+ <source>Downloads complete</source>
+ <translation>Downloads complete</translation>
+ </message>
+ <message>
+ <source>%1 version %2 is now available.</source>
+ <translation>%1 version %2 is now available.</translation>
+ </message>
+ <message>
+ <source>Remind me later</source>
+ <translation>Remind me later</translation>
+ </message>
+ <message>
+ <source>Update</source>
+ <translation>Update</translation>
+ </message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>You can still access the menu bar by pressing the ALT key</translation>
+ </message>
+</context>
+<context>
+ <name>MediaView</name>
+ <message>
+ <source>You can now paste the YouTube link into another application</source>
+ <translation>You can now paste the YouTube link into another application</translation>
+ </message>
+ <message>
+ <source>You can now paste the video stream URL into another application</source>
+ <translation>You can now paste the video stream URL into another application</translation>
+ </message>
+ <message>
+ <source>The link will be valid only for a limited time.</source>
+ <translation>The link will be valid for a limited time only.</translation>
+ </message>
+ <message>
+ <source>Downloading %1</source>
+ <translation>Downloading %1</translation>
+ </message>
+ <message>
+ <source>of</source>
+ <comment>Used in video parts, as in '2 of 3'</comment>
+ <translation>of</translation>
+ </message>
+ <message>
+ <source>part</source>
+ <comment>This is for video parts, as in 'Cool video - part 1'</comment>
+ <translation>part</translation>
+ </message>
+ <message>
+ <source>episode</source>
+ <comment>This is for video parts, as in 'Cool series - episode 1'</comment>
+ <translation>episode</translation>
+ </message>
+ <message>
+ <source>Sent from %1</source>
+ <translation>Sent from %1</translation>
+ </message>
+ <message>
+ <source>Unsubscribe from %1</source>
+ <translation>Unsubscribe from %1</translation>
+ </message>
+ <message>
+ <source>Subscribe to %1</source>
+ <translation>Subscribe to %1</translation>
+ </message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>Switched to %1</translation>
+ </message>
+ <message>
+ <source>Unsubscribed from %1</source>
+ <translation>Unsubscribed from %1</translation>
+ </message>
+ <message>
+ <source>Subscribed to %1</source>
+ <translation>Subscribed to %1</translation>
+ </message>
+</context>
+<context>
+ <name>MessageWidget</name>
+ <message>
+ <source>A new version of %1 is available!</source>
+ <translation>A new version of %1 is available!</translation>
+ </message>
+ <message>
+ <source>%1 %2 is now available. You have %3.</source>
+ <translation>%1 %2 is now available. You have %3.</translation>
+ </message>
+ <message>
+ <source>Would you like to download it now?</source>
+ <translation>Would you like to download it now?</translation>
+ </message>
+ <message>
+ <source>Skip This Version</source>
+ <translation>Skip This Version</translation>
+ </message>
+ <message>
+ <source>Remind Me Later</source>
+ <translation>Remind Me Later</translation>
+ </message>
+ <message>
+ <source>Install Update</source>
+ <translation>Install Update</translation>
+ </message>
+</context>
+<context>
+ <name>PasteLineEdit</name>
+ <message>
+ <source>Paste</source>
+ <translation>Paste</translation>
+ </message>
+</context>
+<context>
+ <name>PickMessage</name>
+ <message>
+ <source>Pick a video</source>
+ <translation>Pick a video</translation>
+ </message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
+ <message>
+ <source>%1 of %2 (%3) — %4</source>
+ <translation>%1 of %2 (%3) — %4</translation>
+ </message>
+ <message>
+ <source>Preparing</source>
+ <translation>Preparing</translation>
+ </message>
+ <message>
+ <source>Failed</source>
+ <translation>Failed</translation>
+ </message>
+ <message>
+ <source>Completed</source>
+ <translation>Completed</translation>
+ </message>
+ <message>
+ <source>Stopped</source>
+ <translation>Stopped</translation>
+ </message>
+ <message>
+ <source>Stop downloading</source>
+ <translation>Stop downloading</translation>
+ </message>
+ <message>
+ <source>Show in %1</source>
+ <translation>Show in %1</translation>
+ </message>
+ <message>
+ <source>Open parent folder</source>
+ <translation>Open parent folder</translation>
+ </message>
+ <message>
+ <source>Restart downloading</source>
+ <translation>Restart downloading</translation>
+ </message>
+</context>
+<context>
+ <name>PlaylistModel</name>
+ <message>
+ <source>Show %1 More</source>
+ <translation>Show %1 More</translation>
+ </message>
+ <message>
+ <source>No videos</source>
+ <translation>No videos</translation>
+ </message>
+ <message>
+ <source>No more videos</source>
+ <translation>No more videos</translation>
+ </message>
+</context>
+<context>
+ <name>RefineSearchWidget</name>
+ <message>
+ <source>Sort by</source>
+ <translation>Sort by</translation>
+ </message>
+ <message>
+ <source>Relevance</source>
+ <translation>Relevance</translation>
+ </message>
+ <message>
+ <source>Date</source>
+ <translation>Date</translation>
+ </message>
+ <message>
+ <source>View Count</source>
+ <translation>View Count</translation>
+ </message>
+ <message>
+ <source>Rating</source>
+ <translation>Rating</translation>
+ </message>
+ <message>
+ <source>Anytime</source>
+ <translation>Any time</translation>
+ </message>
+ <message>
+ <source>Today</source>
+ <translation>Today</translation>
+ </message>
+ <message>
+ <source>7 Days</source>
+ <translation>7 Days</translation>
+ </message>
+ <message>
+ <source>30 Days</source>
+ <translation>30 Days</translation>
+ </message>
+ <message>
+ <source>Duration</source>
+ <translation>Duration</translation>
+ </message>
+ <message>
+ <source>All</source>
+ <translation>All</translation>
+ </message>
+ <message>
+ <source>Short</source>
+ <translation>Short</translation>
+ </message>
+ <message>
+ <source>Medium</source>
+ <translation>Medium</translation>
+ </message>
+ <message>
+ <source>Long</source>
+ <translation>Long</translation>
+ </message>
+ <message>
+ <source>Less than 4 minutes</source>
+ <translation>Less than 4 minutes</translation>
+ </message>
+ <message>
+ <source>Between 4 and 20 minutes</source>
+ <translation>Between 4 and 20 minutes</translation>
+ </message>
+ <message>
+ <source>Longer than 20 minutes</source>
+ <translation>Longer than 20 minutes</translation>
+ </message>
+ <message>
+ <source>Quality</source>
+ <translation>Quality</translation>
+ </message>
+ <message>
+ <source>High Definition</source>
+ <translation>High Definition</translation>
+ </message>
+ <message>
+ <source>720p or higher</source>
+ <translation>720p or higher</translation>
+ </message>
+ <message>
+ <source>Done</source>
+ <translation>Done</translation>
+ </message>
+</context>
+<context>
+ <name>RegionsView</name>
+ <message>
+ <source>Done</source>
+ <translation>Done</translation>
+ </message>
+</context>
+<context>
+ <name>SearchLineEdit</name>
+ <message>
+ <source>Search</source>
+ <translation>Search</translation>
+ </message>
+</context>
+<context>
+ <name>SearchView</name>
+ <message>
+ <source>Welcome to <a href='%1'>%2</a>,</source>
+ <translation>Welcome to <a href='%1'>%2</a>,</translation>
+ </message>
+ <message>
+ <source>to start watching videos.</source>
+ <translation>to start watching videos.</translation>
+ </message>
+ <message>
+ <source>a keyword</source>
+ <translation>a keyword</translation>
+ </message>
+ <message>
+ <source>Enter</source>
+ <translation>Enter</translation>
+ </message>
+ <message>
+ <source>Recent keywords</source>
+ <translation>Recent keywords</translation>
+ </message>
+ <message>
+ <source>Recent channels</source>
+ <translation>Recent channels</translation>
+ </message>
+ <message>
+ <source>Get the full version</source>
+ <translation>Get the full version</translation>
+ </message>
+</context>
+<context>
+ <name>SidebarHeader</name>
+ <message>
+ <source>&Back</source>
+ <translation>&Back</translation>
+ </message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Forward</translation>
+ </message>
+ <message>
+ <source>Forward to %1</source>
+ <translation>Forward to %1</translation>
+ </message>
+ <message>
+ <source>Back to %1</source>
+ <translation>Back to %1</translation>
+ </message>
+</context>
+<context>
+ <name>SidebarWidget</name>
+ <message>
+ <source>Refine Search</source>
+ <translation>Refine Search</translation>
+ </message>
+ <message>
+ <source>Did you mean: %1</source>
+ <translation>Did you mean: %1?</translation>
+ </message>
+</context>
+<context>
+ <name>SnapshotSettings</name>
+ <message>
+ <source>Change location...</source>
+ <translation>Change location...</translation>
+ </message>
+ <message>
+ <source>Snapshot saved to %1</source>
+ <translation>Snapshot saved to %1</translation>
+ </message>
+ <message>
+ <source>Snapshots location changed.</source>
+ <translation>Snapshot location changed.</translation>
+ </message>
+</context>
+<context>
+ <name>StandardFeedsView</name>
+ <message>
+ <source>Most Popular</source>
+ <translation>Most Popular</translation>
+ </message>
+</context>
+<context>
+ <name>UpdateDialog</name>
+ <message>
+ <source>Downloading update...</source>
+ <translation>Downloading update...</translation>
+ </message>
+ <message>
+ <source>Downloading %1...</source>
+ <translation>Downloading %1...</translation>
+ </message>
+</context>
+<context>
+ <name>YTRegions</name>
+ <message>
+ <source>Algeria</source>
+ <translation>Algeria</translation>
+ </message>
+ <message>
+ <source>Argentina</source>
+ <translation>Argentina</translation>
+ </message>
+ <message>
+ <source>Australia</source>
+ <translation>Australia</translation>
+ </message>
+ <message>
+ <source>Belgium</source>
+ <translation>Belgium</translation>
+ </message>
+ <message>
+ <source>Brazil</source>
+ <translation>Brazil</translation>
+ </message>
+ <message>
+ <source>Canada</source>
+ <translation>Canada</translation>
+ </message>
+ <message>
+ <source>Chile</source>
+ <translation>Chile</translation>
+ </message>
+ <message>
+ <source>Colombia</source>
+ <translation>Colombia</translation>
+ </message>
+ <message>
+ <source>Czech Republic</source>
+ <translation>Czech Republic</translation>
+ </message>
+ <message>
+ <source>Egypt</source>
+ <translation>Egypt</translation>
+ </message>
+ <message>
+ <source>France</source>
+ <translation>France</translation>
+ </message>
+ <message>
+ <source>Germany</source>
+ <translation>Germany</translation>
+ </message>
+ <message>
+ <source>Ghana</source>
+ <translation>Ghana</translation>
+ </message>
+ <message>
+ <source>Greece</source>
+ <translation>Greece</translation>
+ </message>
+ <message>
+ <source>Hong Kong</source>
+ <translation>Hong Kong</translation>
+ </message>
+ <message>
+ <source>Hungary</source>
+ <translation>Hungary</translation>
+ </message>
+ <message>
+ <source>India</source>
+ <translation>India</translation>
+ </message>
+ <message>
+ <source>Indonesia</source>
+ <translation>Indonesia</translation>
+ </message>
+ <message>
+ <source>Ireland</source>
+ <translation>Ireland</translation>
+ </message>
+ <message>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <source>Italy</source>
+ <translation>Italy</translation>
+ </message>
+ <message>
+ <source>Japan</source>
+ <translation>Japan</translation>
+ </message>
+ <message>
+ <source>Jordan</source>
+ <translation>Jordan</translation>
+ </message>
+ <message>
+ <source>Kenya</source>
+ <translation>Kenya</translation>
+ </message>
+ <message>
+ <source>Malaysia</source>
+ <translation>Malaysia</translation>
+ </message>
+ <message>
+ <source>Mexico</source>
+ <translation>Mexico</translation>
+ </message>
+ <message>
+ <source>Morocco</source>
+ <translation>Morocco</translation>
+ </message>
+ <message>
+ <source>Netherlands</source>
+ <translation>Netherlands</translation>
+ </message>
+ <message>
+ <source>New Zealand</source>
+ <translation>New Zealand</translation>
+ </message>
+ <message>
+ <source>Nigeria</source>
+ <translation>Nigeria</translation>
+ </message>
+ <message>
+ <source>Peru</source>
+ <translation>Peru</translation>
+ </message>
+ <message>
+ <source>Philippines</source>
+ <translation>Philippines</translation>
+ </message>
+ <message>
+ <source>Poland</source>
+ <translation>Poland</translation>
+ </message>
+ <message>
+ <source>Russia</source>
+ <translation>Russia</translation>
+ </message>
+ <message>
+ <source>Saudi Arabia</source>
+ <translation>Saudi Arabia</translation>
+ </message>
+ <message>
+ <source>Singapore</source>
+ <translation>Singapore</translation>
+ </message>
+ <message>
+ <source>South Africa</source>
+ <translation>South Africa</translation>
+ </message>
+ <message>
+ <source>South Korea</source>
+ <translation>South Korea</translation>
+ </message>
+ <message>
+ <source>Spain</source>
+ <translation>Spain</translation>
+ </message>
+ <message>
+ <source>Sweden</source>
+ <translation>Sweden</translation>
+ </message>
+ <message>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <source>Tunisia</source>
+ <translation>Tunisia</translation>
+ </message>
+ <message>
+ <source>Turkey</source>
+ <translation>Turkey</translation>
+ </message>
+ <message>
+ <source>Uganda</source>
+ <translation>Uganda</translation>
+ </message>
+ <message>
+ <source>United Arab Emirates</source>
+ <translation>United Arab Emirates</translation>
+ </message>
+ <message>
+ <source>United Kingdom</source>
+ <translation>United Kingdom</translation>
+ </message>
+ <message>
+ <source>Yemen</source>
+ <translation>Yemen</translation>
+ </message>
+ <message>
+ <source>Worldwide</source>
+ <translation>World Wide</translation>
+ </message>
+</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Cannot retrieve video stream for %1</translation>
+ </message>
+</context>
+</TS>
\ No newline at end of file
</message>
<message>
<source>Translate %1 to your native language using %2</source>
- <translation>Traduzca %1 a su idioma natal usando %2</translation>
+ <translation>Traduzca %1 a su idioma usando %2</translation>
+ </message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
</message>
<message>
<source>Icon designed by %1.</source>
<source>Show Updated</source>
<translation>Mostrar actualizados</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No se ha suscrito a ningún canal. Use el símbolo de la estrella para suscribirse a los canales.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos los vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>No hay suscripciones actualizadas en este momento.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No se ha suscrito a ningún canal. Use el símbolo de la estrella para suscribirse a los canales.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Vaciar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 reproducciones</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es solo la versión de prueba de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Solo puede descargar vídeos de duración menor que %1 minutos para que pueda probar la función de descarga.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descargados en %2</translation>
<source>&Float on Top</source>
<translation>&Flotar en la parte superior</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajustar tamaño de la ventana</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Detener tras este vídeo</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ocultar videos que puedan contener contenido inapropiado</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Apagar &Barra de menú</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menú</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>¿&Le gusta %1? ¡Valórelo!</translation>
<source>Update</source>
<translation>Actualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Por abrir la barra de menú puede presionar la tecla Alt.</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>El enlace es válido solo por un tiempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esto es solo la versión de prueba de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Le permite probar la aplicación y ver si le funciona.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<source>Subscribe to %1</source>
<translation>Suscribirse a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Desuscrito de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 reproducciones</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Buscando…</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar %1 más</translation>
<translation>Bienvenido a <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Escriba</translation>
+ <source>to start watching videos.</source>
+ <translation>para empezar a ver vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una palabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para empezar a ver vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Escriba</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Atrás</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Reenviar a %1</translation>
<translation>Descargando %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No se puede obtener el flujo de vídeo para %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Todo el mundo</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No se puede obtener el stream de vídeo para %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduce %1 a tu idioma natal usando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Iconos diseñados por %1.</translation>
<source>Show Updated</source>
<translation>Show actualizado</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No tiene suscripciones. Haga click en el ícono de la estrella para suscribirse a un canal</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos los videos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>No hay actualizaciones a sus suscripciones en este momento</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No tiene suscripciones. Haga click en el ícono de la estrella para suscribirse a un canal</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpiar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visitas</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es sólo una versión de demostración de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Sólo se pueden bajar videos de menos de %1 minutos, para probar la funcionalidad de descarga.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Consigue la versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descargado en %2</translation>
<source>&Float on Top</source>
<translation>&Siempre Visible</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>Ajustar el tamaño de la ventana</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Finalizar Después de este Video</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ocultar videos que puedan tener contenido inapropiado</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>¿Amas %1? ¡Califícalo!</translation>
<source>Update</source>
<translation>Actualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>El enlace va a ser válido sólo por un tiempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es sólo la versión de demostración de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Te permite probar la aplicación y ver si te funciona.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Conseguir la versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<source>Subscribe to %1</source>
<translation>Suscribirse a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Desubscripto de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visitas</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 of %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Buscando...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar %1 más</translation>
<translation>Bienvenido a <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Escribir</translation>
+ <source>to start watching videos.</source>
+ <translation>para empezar a ver videos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una palabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para empezar a ver videos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Escribir</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Atrás</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avanzar a %1</translation>
<translation>Descargando %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No puedo obtener el stream de video de %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Todo el mundo</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No puedo obtener el stream de video de %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traducir %1 a tu idioma utilizando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icono diseñado por %1.</translation>
<source>Show Updated</source>
<translation>Mostrar actualizados</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No tienes ninguna subscripción. Usa la estrella para subscribirte a los canales.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos los videos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>No existen subscripciones actualizadas en este momento.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No tienes ninguna subscripción. Usa la estrella para subscribirte a los canales.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpiar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 vistas</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es la versión de prueba de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Sólo se pueden descargar vídeos inferiores a %1 minutos. Así podrá probar la funcionalidad de descarga.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descargado en %2</translation>
<source>&Float on Top</source>
<translation>&Siempre Visible</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajustar el tamaños de la ventana</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Detener después de este vídeo</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ocultar video que puedan poseer contenido inapropiado</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&¿Te encanta %1? ¡Puntúalo!</translation>
<source>Update</source>
<translation>Actualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>El enlace será válido sólo por un plazo de tiempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es la versión de prueba de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Esta versión le permite probar la aplicación y ver si le sirve.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<source>Subscribe to %1</source>
<translation>Subscribirse a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Desubscripto de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 vistas</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Buscando...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar %1 más</translation>
<translation>Bienvenidos a <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introducir</translation>
+ <source>to start watching videos.</source>
+ <translation>para empezar a ver vídeos</translation>
</message>
<message>
<source>a keyword</source>
<translation>una palabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para empezar a ver vídeos</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Introducir</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Volver</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Hacia adelante %1</translation>
<translation>Descargando %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No se puede obtener el flujo de vídeo para %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Mundial</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No se puede obtener el flujo de vídeo para %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traducir %1 a tu idioma nativo usando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ícono diseñado por %1</translation>
</message>
<message>
<source>&Code:</source>
- <translation>&Códico</translation>
+ <translation>&Código</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Mostrar actualizados</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>No tienes suscripciones. Usa el símbolo de estrella para suscribirte a los canales.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos los vídeos</translation>
</message>
<message>
<source>Unsubscribe</source>
- <translation>Quitar suscripción</translation>
+ <translation>Cancelar suscripción</translation>
</message>
<message>
<source>There are no updated subscriptions at this time.</source>
<translation>No hay suscripciones actualizadas</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>No tienes suscripciones. Usa el símbolo de estrella para suscribirte a los canales.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpiar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 vistas</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es la versión demostrativa de %1</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Sólo puede descargar videos con duración menor a %1 minutos asi que puedes probar la funcionalidad de descarga</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>Descargados %1 en %2</translation>
</message>
<message>
<source>&Float on Top</source>
- <translation>%Mantener arriba</translation>
- </message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajustar tamaño de ventana</translation>
+ <translation>&Mantener arriba</translation>
</message>
<message>
<source>&Stop After This Video</source>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ocultar vídeos que pueden tener contenido inapropiado</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Amas %1? Calíficalo!</translation>
<source>Update</source>
<translation>Actualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>El vínculo será válido sólo por un tiempo limitado</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta es la versión demostrativa de %1</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Le permite probar la aplicación y ver si funciona para ti.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtener la versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<source>Subscribe to %1</source>
<translation>Suscribir a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Desuscribir de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 vistas</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) - %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Buscando...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar %1 más</translation>
<translation>Bienvenido a <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introduzca</translation>
+ <source>to start watching videos.</source>
+ <translation>para ver vídeos</translation>
</message>
<message>
<source>a keyword</source>
<translation>una palabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para ver vídeos</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Introduzca</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Retroceder</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avanzar a %1 </translation>
<translation>Descargando %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>No se puede obtener el vídeo para %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Mundial</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>No se puede obtener el vídeo para %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Käännä %1 äidinkielellesi käyttämällä %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Avoimen lähdekoodin ohjelmisto</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Kuvakkeen suunnitteli %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Sinulle on %n uusi video</numerusform><numerusform>Sinulle on %n uutta videota</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Näytä päivitetyt</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Kaikki videot</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Päivitettyjä tilauksia ei ole tällä hetkellä.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Tyhjennä</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n tunti sitten</numerusform><numerusform>%n tuntia sitten</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n päivä sitten</numerusform><numerusform>%n päivää sitten</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>%n kuukausi sitten</numerusform><numerusform>%n kuukautta sitten</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>Katsottu %1 kertaa</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>%n viikko sitten</numerusform><numerusform>%n viikkoa sitten</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Tämä on vain %1-kokeiluversio.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Voit ladata vain videoita jotka ovat lyhyempiä kuin %1 minuuttia, jotta voit testata latausominaisuutta.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Jatka</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hanki täysi versio</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 ladattu ajassa %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n lataus</numerusform><numerusform>%n latausta</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Pysy päällimmäisenä</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Muuta ikkunan kokoa</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Py&säytä toisto tämän videon jälkeen</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Rajoitettu tila</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
- <translation type="unfinished"/>
+ <translation>Piilota videot, jotka saattavat sisältää soveltumatonta sisältöä</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Näytä/piilota &valikkopalkki</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Valikko</translation>
</message>
<message>
<source>&Love %1? Rate it!</source>
<source>Update</source>
<translation>Päivitä</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Saat valikkopalkin näkyviin painamalla ALT-näppäintä</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Osoite on käytössä vain rajoitetun ajan.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Tämä on vain %1n kokeiluversio.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Voit kokeilla ohjelmaa nähdäksesi, toimiiko se.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hanki täysi versio</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Jatka</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Ladataan %1ta/tä</translation>
<source>Subscribe to %1</source>
<translation>Tilaa kanava %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Kohteen %1 tilaus lopetettu</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>Katsottu %1 kertaa</translation>
+ <source>Pick a video</source>
+ <translation>Valitse video</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 / %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Etsitään...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Näytä %1 lisää</translation>
<translation>Tervetuloa <a href='%1'>%2en</a></translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Syötä</translation>
+ <source>to start watching videos.</source>
+ <translation>aloittaaksesi videoiden katselu.</translation>
</message>
<message>
<source>a keyword</source>
<translation>hakusana</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanava</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>aloittaaksesi videoiden katselu.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Katso</translation>
+ <source>Enter</source>
+ <translation>Syötä</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Takaisin</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Eteenpäin</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Eteenpäin kohteeseen %1</translation>
<translation>Ladataan %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Videostriimiä ei saada kohteelle %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Maailmanlaajuinen</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Videostriimiä ei saada kohteelle %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Käännä %1 äidinkielellesi käyttämällä %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Kuvakkeen suunnitteli %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Lataa</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Näytä päivitetyt</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Kaikki videot</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Päivitettyjä tilauksia ei ole tällä hetkellä.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Sinulla ei ole tilauksia. Käytä tähtisymbolia tilataksesi kanavia.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Tyhjennä</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>Katsottu %1 kertaa</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Tämä on vain %1-kokeiluversio.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Voit ladata vain videoita jotka ovat lyhyempiä kuin %1 minuuttia, jotta voit testata latausominaisuutta.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Jatka</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hanki täysi versio</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 ladattu ajassa %2</translation>
<source>&Float on Top</source>
<translation>&Pysy päällimmäisenä</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Muuta ikkunan kokoa</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Py&säytä toisto tämän videon jälkeen</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Näytä/piilota &valikkopalkki</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Valikko</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Pidätkö %1sta? Arvostele se!</translation>
<source>Update</source>
<translation>Päivitä</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Saat valikkopalkin näkyviin painamalla ALT-näppäintä</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Osoite on käytössä vain rajoitetun ajan.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Tämä on vain %1n kokeiluversio.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Voit kokeilla ohjelmaa nähdäksesi, toimiiko se.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Hanki täysi versio</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Jatka</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Ladataan %1ta/tä</translation>
<source>Subscribe to %1</source>
<translation>Tilaa kanava %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Lopeta kohteen %1 tilaaminen</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>Katsottu %1 kertaa</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 / %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Etsitään...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Näytä %1 lisää</translation>
<translation>Tervetuloa <a href='%1'>%2en</a></translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Syötä</translation>
+ <source>to start watching videos.</source>
+ <translation>aloittaaksesi videoiden katselu.</translation>
</message>
<message>
<source>a keyword</source>
<translation>hakusana</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanava</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>aloittaaksesi videoiden katselu.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Katso</translation>
+ <source>Enter</source>
+ <translation>Syötä</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Takaisin</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Eteenpäin kohteeseen %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Videostriimiä ei saada kohteelle %1</translation>
+ <translation>Ladataan %1...</translation>
</message>
</context>
<context>
<translation>Maailmanlaajuinen</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Videostriimiä ei saada kohteelle %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduisez %1 dans votre langue native en utilisant %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Logiciel open-source</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icône dessinée par %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Vous avez %n nouvelle vidéo</numerusform><numerusform>Vous avez %n nouvelles vidéos</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Afficher les mises à jours</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Vous n'avez pas d'abonnements. Utilisez le symbole en forme d'étoile pour vous abonner à des chaines,</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Toutes les vidéos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Il n'y a pas d'abonnements mis à jour en ce moment.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Vous n'avez pas d'abonnements. Utilisez le symbole en forme d'étoile pour vous abonner à des chaines,</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Nettoyer </translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Il y a une heure</numerusform><numerusform>Il y a %n heures</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Il y a %n jour</numerusform><numerusform>Il y a %n jours</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>Il y a %n mois</numerusform><numerusform>Il y a %n mois</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 vues</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>Il y a %n semaine</numerusform><numerusform>Il y a %n semaines</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Il s'agit seulement de la version de démonstration de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Vous ne pouvez télécharger que des vidéos plus courtes que %1 minutes de sorte que vous puissiez tester la fonctionnalité de téléchargement.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuer</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtenir la version complète</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 téléchargé sur %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n téléchargement</numerusform><numerusform>%n téléchargements</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Laisser au dessus</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajuster la taille de la fenètre</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Arrêter après cette vidéo</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Masquer les vidéos qui peuvent contenir un contenu inapproprié</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Bascule la barre de &Menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Aimer %1? Notez-le !</translation>
<source>Update</source>
<translation>Mettre à jour</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Vous pouvez continuer d'accéder à la barre de menu en appuyant sur la touche ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Le lien ne sera valide que pour un temps limité.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>C'est juste la version démo de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Cela vous permet de tester l'application et voir si cela fonctionne pour vous.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obtenir la version complète</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuer</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1 Téléchargement</translation>
<source>Subscribe to %1</source>
<translation>S'abonner à %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Se désabonner de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 vues</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Recherche en cours…</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Afficher %1 de plus</translation>
<translation>Bienvenue sur <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Entrer</translation>
+ <source>to start watching videos.</source>
+ <translation>pour commencer à regarder des vidéos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>un mot-clé</translation>
</message>
<message>
- <source>a channel</source>
- <translation>une chaîne</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>pour commencer à regarder des vidéos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Regarder</translation>
+ <source>Enter</source>
+ <translation>Entrer</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Retour</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Continuer à %1</translation>
<translation>Téléchargement de %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Impossible d'obtenir le flux vidéo de %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Monde entier</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Impossible d'obtenir le flux vidéo de %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traducir %1 ao seu idioma empregando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Coa tecnoloxía de %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Software de fontes abertas</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icona deseñada por %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Descarga</translation>
</message>
</context>
<context>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Dispón dun novo vídeo</numerusform><numerusform>Dispón de %n novos vídeos</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Amosar a actualización</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Non ten subscricións. Utilice o símbolo da estrela para subscribirse ás canles.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos os vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Neste momento non ten subscricións de actualización.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Non ten subscricións. Utilice o símbolo da estrela para subscribirse ás canles.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<message>
<source>Just now</source>
- <translation type="unfinished"/>
+ <translation>Agora mesmo</translation>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Hai %n hora</numerusform><numerusform>Hai %n horas</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Hai %n dia</numerusform><numerusform>Hai %n dias</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>Hai %n mes</numerusform><numerusform>Hai %n meses</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 vistas</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>Hai %n semana</numerusform><numerusform>Hai %n semanas</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Isto é só a versión demo de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Só se poden descargar vídeos curtos de menos de %1 minutos para que poida probar a utilidade de descargas.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versión completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descargado en %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n descarga</numerusform><numerusform>%n descargas</numerusform></translation>
</message>
</context>
<context>
<name>Extra</name>
<message>
<source>The executable file has been tempered with, maybe by a virus.</source>
- <translation type="unfinished"/>
+ <translation>O ficheiro executable foi manipulado, quizais por un virus.</translation>
</message>
<message>
<source>%1 will not run. Try installing again.</source>
- <translation type="unfinished"/>
+ <translation>%1 non se executará. Probe a instalalo de novo.</translation>
</message>
<message>
<source>Quit</source>
- <translation type="unfinished"/>
+ <translation>Saír</translation>
</message>
<message>
<source>Reinstall</source>
- <translation type="unfinished"/>
+ <translation>Volver instalar</translation>
</message>
</context>
<context>
</message>
<message>
<source>Show details about video downloads</source>
- <translation>Mostrar os detalles sobre as descargas de vídeo</translation>
+ <translation>Amosar os detalles sobre as descargas de vídeo</translation>
</message>
<message>
<source>&Download</source>
<source>&Float on Top</source>
<translation>&Flotante e arriba</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Deter despois deste vídeo</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Modo restrinxido</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
- <translation type="unfinished"/>
+ <translation>Agochar os vídeos que poidan conter contido inadecuado</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Alternar a barra do &menú</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menú</translation>
</message>
<message>
<source>&Love %1? Rate it!</source>
</message>
<message>
<source>&Loading...</source>
- <translation type="unfinished"/>
+ <translation>&Cargando...</translation>
</message>
<message>
<source>Leave &Full Screen</source>
<source>Update</source>
<translation>Actualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Aínda pode acceder á barra de menús premendo a tecla ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>A ligazón ten validez só por un tempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Isto é só a versión demo de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Permítelle probar o aplicativo e comprobar se vai ao seu xeito.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versión completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descargando %1</translation>
<message>
<source>part</source>
<comment>This is for video parts, as in 'Cool video - part 1'</comment>
- <translation>peza</translation>
+ <translation>parte</translation>
</message>
<message>
<source>episode</source>
</message>
<message>
<source>Sent from %1</source>
- <translation>Enviado desde %1</translation>
+ <translation>Enviado dende %1</translation>
</message>
<message>
<source>Unsubscribe from %1</source>
<source>Subscribe to %1</source>
<translation>Subscribirse a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>Cambiado a %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
- <translation type="unfinished"/>
+ <translation>Non está subscrito a %1</translation>
</message>
<message>
<source>Subscribed to %1</source>
- <translation type="unfinished"/>
+ <translation>Subscrito a %1</translation>
</message>
</context>
<context>
</message>
<message>
<source>Would you like to download it now?</source>
- <translation>Queres descargala agora?</translation>
+ <translation>Quere descargala agora?</translation>
</message>
<message>
<source>Skip This Version</source>
- <translation>Saltar esta versión</translation>
+ <translation>Omitir esta versión</translation>
</message>
<message>
<source>Remind Me Later</source>
- <translation>Acórdamo máis adiante</translation>
+ <translation>Lembremo máis adiante</translation>
</message>
<message>
<source>Install Update</source>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 vistas</translation>
+ <source>Pick a video</source>
+ <translation>Deleccione un vídeo</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</message>
<message>
<source>Show in %1</source>
- <translation>Mostrar en %1</translation>
+ <translation>Amosar en %1</translation>
</message>
<message>
<source>Open parent folder</source>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Buscando...</translation>
- </message>
<message>
<source>Show %1 More</source>
- <translation>Mostrar %1 máis</translation>
+ <translation>Amosar %1 máis</translation>
</message>
<message>
<source>No videos</source>
<translation>Benvido a <a href='%1'>%2</a,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introduza</translation>
+ <source>to start watching videos.</source>
+ <translation>para comezar a ver vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>unha palabra clave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>unha canle</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para comezar a ver vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Introduza</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Atrás</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>A&diante</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Ir a %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Non é posíbel obter o fluxo de vídeo de %1</translation>
+ <translation>Descargando %1...</translation>
</message>
</context>
<context>
<translation>Todo o mundo</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Non é posíbel obter o fluxo de vídeo de %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>ניתן לתרגם את %1 לשפת אמך באמצעות %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>מופעל על ידי %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>תכנה בקוד פתוח</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>הסמל עוצב על ידי %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>הורד</translation>
</message>
</context>
<context>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>יש לך סרטון חדש</numerusform><numerusform>יש לך שני סרטונים חדשים</numerusform><numerusform>יש לך %n סרטונים חדשים</numerusform><numerusform>יש לך %n סרטונים חדשים</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>הצג עדכונים</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>אין לך מנויים. השתמש בסמל הכוכב על מנת להיות מנוי לערוצים.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>כל הסרטונים</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>אין עדכונים בערוצים שאתה מנוי עליהם כרגע.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>אין לך מנויים. השתמש בסמל הכוכב על מנת להיות מנוי לערוצים.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>מחיקה</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<message>
<source>Just now</source>
- <translation type="unfinished"/>
+ <translation>זה עתה</translation>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>לפני שעה</numerusform><numerusform>לפני שעתיים</numerusform><numerusform>לפני %n שעות</numerusform><numerusform>לפני %n שעות</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>אתמול</numerusform><numerusform>שלשום</numerusform><numerusform>לפני %n ימים</numerusform><numerusform>לפני %n ימים</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>לפני חודש</numerusform><numerusform>לפני חודשיים</numerusform><numerusform>לפני %n חודשים</numerusform><numerusform>לפני %n חודשים</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>ק׳</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>מ׳</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>ב׳</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 צפיות</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>לפני שבוע</numerusform><numerusform>לפני שבועיים</numerusform><numerusform>לפני %n שבועות</numerusform><numerusform>לפני %n שבועות</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>זוהי רק גרסת ההדגמה של %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>באמצעות גרסה זו ניתן להוריד קטעי וידאו שאורכם אינו עולה על %1 דקות כדי שתהיה באפשרותך לבחור את אפשרות ההורדה.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>המשך</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>קבלת הגרסה המלאה</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 התקבל במהירות של %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>הורדה אחת</numerusform><numerusform>שתי הורדות</numerusform><numerusform>%n הורדות</numerusform><numerusform>%n הורדות</numerusform></translation>
</message>
</context>
<context>
<name>Extra</name>
<message>
<source>The executable file has been tempered with, maybe by a virus.</source>
- <translation type="unfinished"/>
+ <translation>הקובץ שונה,אולי על ידי וירוס.</translation>
</message>
<message>
<source>%1 will not run. Try installing again.</source>
- <translation type="unfinished"/>
+ <translation>%1 לא ניתן להפעלה,נסה להתקין מחדש.</translation>
</message>
<message>
<source>Quit</source>
- <translation type="unfinished"/>
+ <translation>יציאה</translation>
</message>
<message>
<source>Reinstall</source>
- <translation type="unfinished"/>
+ <translation>התקן מחדש</translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&ציפה מלמעלה</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>ל&עצור לאחר וידאו זה</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>מצב מוגבל</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
- <translation type="unfinished"/>
+ <translation>הסתר סרטונים שעשויים להכיל תוכן לא הולם</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>החלפת מ&צב סרגל תפריט</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>תפריט</translation>
</message>
<message>
<source>&Love %1? Rate it!</source>
</message>
<message>
<source>&Loading...</source>
- <translation type="unfinished"/>
+ <translation>%טוען...</translation>
</message>
<message>
<source>Leave &Full Screen</source>
<source>Update</source>
<translation>עדכון</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>עדיין ניתן לגשת לסרגל התפריט עם לחיצה על ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>הקישור יהיה תקף לזמן מוגבל בלבד.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>זוהי רק גרסת ההדגמה של %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>גרסה זו מאפשרת לך לבחון את היישום ולראות האם הוא מתאים לצרכיך.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>קבלת הגרסה המלאה</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>המשך</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1 מתקבל</translation>
<source>Subscribe to %1</source>
<translation>מנוי ל %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>הוחלף אל %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
- <translation type="unfinished"/>
+ <translation>בוטלה הרשמה מ%1</translation>
</message>
<message>
<source>Subscribed to %1</source>
- <translation type="unfinished"/>
+ <translation>בוצע רישום ל%1</translation>
</message>
</context>
<context>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 צפיות</translation>
+ <source>Pick a video</source>
+ <translation>נא לבחור סרטון</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 מתוך %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>בהליכי חיפוש...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>הצגת %1 נוספים</translation>
<translation>ברוך בואך אל <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>הזנה</translation>
+ <source>to start watching videos.</source>
+ <translation>כדי להתחיל לצפות בסרטונים.</translation>
</message>
<message>
<source>a keyword</source>
<translation>מילת מפתח</translation>
</message>
<message>
- <source>a channel</source>
- <translation>ערוץ</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>כדי להתחיל לצפות בסרטונים.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>צפייה</translation>
+ <source>Enter</source>
+ <translation>הזנה</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>הקודם</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>ה&עברה</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>העבר אל %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>לא ניתן לקבל את תזרים הווידאו עבור %1</translation>
+ <translation>מוריד %1...</translation>
</message>
</context>
<context>
<translation>כל העולם</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>לא ניתן לקבל את תזרים הווידאו עבור %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Prevedite %1 na svoj jezik koristeći %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Dizajn ikone %1.</translation>
<source>Show Updated</source>
<translation>Prikaži ažurirano</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Niste pretplaćeni na ni jedan kanal. Za pretplatu kliknite na zvijezdicu.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Svi videozapisi</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Trenutno nema dostupnih ažuriranja pretplata</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Niste pretplaćeni na ni jedan kanal. Za pretplatu kliknite na zvijezdicu.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Obriši</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 pregleda</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ovo je samo probna verzija %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Može preuzeti samo video kraći od %1 minuta tako da možete testirati mogućnost preuzimanja.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Nastavi</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Preuzmi punu verziju</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 preuzet u %2</translation>
<source>&Float on Top</source>
<translation>&Budi na vrhu</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stani nakon ovog videa</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"/>
<source>Update</source>
<translation>Ažuriraj</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Link će biti valjan samo ograničeno vrijeme.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ovo je samo demo verzija %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Omogućava Vam da testirate program i vidite da li Vam odgovara.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Preuzmi punu verziju</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Nastavi</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Preuzimam %1</translation>
<source>Subscribe to %1</source>
<translation>Pretplata na %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 pregleda</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 od %2 (%3) - %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Pretražujem...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Prikaži %1 više</translation>
<translation>Dobrodošli u <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Unesi</translation>
+ <source>to start watching videos.</source>
+ <translation>da počnete gledati video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ključna riječ</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>da počnete gledati video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Gledaj</translation>
+ <source>Enter</source>
+ <translation>Unesi</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Nazad</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Naprijed na %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Ne mogu naći video stream za %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Širom svijeta</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Ne mogu naći video stream za %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Fordítsa le a %1 programot az anyanyelvére a következővel: %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikon tervezője: %1</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Letöltés</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Frissítések mutatása</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nincsenek feliratkozások. A csillag szimbólumot kell használni a csatornákra való feliratkozáshoz.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Összes videó</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Nincs frissítés a feliratkozott csatornákon.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nincsenek feliratkozások. A csillag szimbólumot kell használni a csatornákra való feliratkozáshoz.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Törlés</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 néző</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ez csak a demó verziója a %1 programnak.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Csak %1 percnél rövidebb videók tölthetők le vele a letöltési funkciók teszteléséhez.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Folytatás</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Teljes verzió beszerzése</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 letöltve ide: %2</translation>
<source>&Float on Top</source>
<translation>&Többi ablak fölött</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ablak méretének beállítása</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Videó után leállítás</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Korlátozott mód</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
+ <translation>Esetleg nem megfelelő tartalmú videók elrejtése</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
<translation type="unfinished"/>
</message>
<message>
<source>Update</source>
<translation>Frissítés</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>A hivatkozás csak korlátozott ideig lesz érvényben.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ez csak a demó verziója a %1 programnak.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Kipróbálhatja az alkalmazást, hogy megfelel-e az igényeinek.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Teljes verzió beszerzése</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Folytatás</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Letöltés: %1</translation>
<source>Subscribe to %1</source>
<translation>Feliratkozás az alábbi csatornára: %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Leiratkozva %1-ról/ről.</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 néző</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 %2 közül (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Keresés...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>További %1 elem megjelenítése</translation>
<translation>Üdvözli a <a href='%1'>%2</a> program,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Írjon be</translation>
+ <source>to start watching videos.</source>
+ <translation>a videók megtekintéséhez.</translation>
</message>
<message>
<source>a keyword</source>
<translation>egy kulcsszót</translation>
</message>
<message>
- <source>a channel</source>
- <translation>egy csatornát</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>a videók megtekintéséhez.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Megtekintés</translation>
+ <source>Enter</source>
+ <translation>Írjon be</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Vissza</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Tovább a %1-re</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nem található videó adatfolyam a következőhöz: %1</translation>
+ <translation>Letöltés %1...</translation>
</message>
</context>
<context>
<translation>Világszerte</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nem található videó adatfolyam a következőhöz: %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Terjemahkan %1 ke bahasa aslimu dengan menggunakan %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Desain ikon oleh %1.</translation>
<source>Show Updated</source>
<translation>Acara Terbarui</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Anda tidak punya langganan. Gunakan simbol bintang untuk berlangganan ke saluran.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Semua video</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Tidak ada langganan yang diperbarui saat ini</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Anda tidak punya langganan. Gunakan simbol bintang untuk berlangganan ke saluran.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Bersih</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 tampilan</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ini adalah hanya versi demo dari %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Itu bisanya hanya download video yang pendek daripada %1 menit sehingga kamu bisa menguji fungsinya downloadnya.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Teruskan</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Dapatkan versi komplitnya</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 diunduh dalam %2</translation>
<source>&Float on Top</source>
<translation>&Mengambang ke puncak</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&berhenti setelah video ini</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Love %1? Beri Nilai!</translation>
<source>Update</source>
<translation>Pembaruan data</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Link akan berlaku hanya untuk waktu yang terbatas.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>This is just the demo version of %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ini memungkinkan Anda untuk menguji aplikasi dan melihat apakah ia bekerja untuk Anda.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Get the full version</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continue</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Downloading %1</translation>
<source>Subscribe to %1</source>
<translation>Berlangganan ke %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 tampilan</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 dari %2 (%3) --- %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Sedang mencari...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Tampilkan lebih banyak %1</translation>
<translation>Selamat datang ke <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Masukkan</translation>
+ <source>to start watching videos.</source>
+ <translation>to start watching videos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>sebuah kata kunci</translation>
</message>
<message>
- <source>a channel</source>
- <translation>a channel</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>to start watching videos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Watch</translation>
+ <source>Enter</source>
+ <translation>Masukkan</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Kembali</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Maju sampai %1</translation>
<translation>Mengunduh %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Tidak bisa mendapatkan video stream untuk %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Dunia</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Tidak bisa mendapatkan video stream untuk %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduci %1 nella tua lingua usando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Utilizza %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Software open-source</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Icona disegnata da %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>C'è un nuovo video</numerusform><numerusform>Ci sono %n nuovi video</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Mostra aggiornati</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Non hai iscrizioni. Usa il simbolo della stella per sottoscrivere i canali.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Tutti i video</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Non ci sono iscrizioni aggiornate in questo momento.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Non hai iscrizioni. Usa il simbolo della stella per sottoscrivere i canali.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Cancella</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Un'ora fa</numerusform><numerusform>%n ore fa</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Ieri</numerusform><numerusform>%n giorni fa</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>Un mese fa</numerusform><numerusform>%n mesi fa</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualizzazioni</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>Una settimana fa</numerusform><numerusform>%n settimane fa</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Questa è solo la versione demo di %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Puoi scaricare solo video più corti di %1 minuti, così puoi testare la funzionalità dei download.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Compra la versione completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 scaricato in %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Un download</numerusform><numerusform>%n download</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Fluttua in alto</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Adatta le dimensioni della finestra</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Ferma dopo questo video</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Nascondi contenuti inappropriati</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Mostra/Nascondi &Menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>Ti piace %1?</translation>
<source>Update</source>
<translation>Aggiorna</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Puoi accedere di nuovo al menu premendo ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Il link rimarrà valido per un periodo di tempo limitato.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Questa è solo la versione demo di %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ti permette di testare l'applicazione e verificare che funzioni sul tuo computer.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Compra la versione completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continua</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Scarica in: %1</translation>
<source>Subscribe to %1</source>
<translation>Iscriviti a %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>Passato a %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Iscrizione a %1 annullata</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visualizzazioni</translation>
+ <source>Pick a video</source>
+ <translation>Scegli un video</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 di %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Ricerca...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostra altri %1</translation>
<translation>Benvenuto su <a href="%1">%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Scrivi</translation>
+ <source>to start watching videos.</source>
+ <translation>per iniziare a guardare i video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>una parola chiave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canale</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>per iniziare a guardare i video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Guarda</translation>
+ <source>Enter</source>
+ <translation>Scrivi</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Indietro</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Avanti</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avanza a %1</translation>
<translation>Download di %1</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Impossibile ottenere il flusso video per %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Tutto il mondo</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Impossibile ottenere il flusso video per %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>%2を使って、%1をあなたの言語に翻訳してください</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>アイコンは%1さんのデザインです。</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>ダウンロード</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>動画の更新を確認</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>購読済みのものがありません。チャンネルを購読するには星マークをクリックします。</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>全ての動画</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>購読したチャンネルに更新はありません。</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>購読済みのものがありません。チャンネルを購読するには星マークをクリックします。</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>クリア</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1回 閲覧</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>これは%1 の試用版です。</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>動画を%1分程度でダウンロードできる機能を試すことができます。</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>続ける</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>製品版を入手する</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1を%2にダウンロード中</translation>
<source>&Float on Top</source>
<translation>最前面に表示(&F)</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&ウィンドウの大きさを調節</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>再生終了後に停止(&S)</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>%1を評価(&L)</translation>
<source>Update</source>
<translation>更新</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>リンクは制限時間内のみ有効です。</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>これは%1 の試用版です。</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>アプリケーションのテストや動作確認にご利用いただけます。</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>製品版を入手する</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>続ける</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1をダウンロード中</translation>
<source>Subscribe to %1</source>
<translation>%1を購読する</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>%1の購読を解除しました</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1回 閲覧</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1は%2(%3)%4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>検索中...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>さらに%1件表示</translation>
<translation>ようこそ<a href='%1'>%2</a>へ</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>キーワードを入力して</translation>
+ <source>to start watching videos.</source>
+ <translation>を検索する。</translation>
</message>
<message>
<source>a keyword</source>
<translation>動画</translation>
</message>
<message>
- <source>a channel</source>
- <translation>チャンネル</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>を検索する。</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>検索</translation>
+ <source>Enter</source>
+ <translation>キーワードを入力して</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>戻る(&B)</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>%1 に進む</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>%1の動画を取得できませんでした</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>全世界</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>%1の動画を取得できませんでした</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
</message>
<message>
<source>Please <a href='%1'>donate</a> to support the continued development of %2.</source>
- <translation>%2ì\9d\98 ê³\84ì\86\8dë\90\9c ê°\9cë°\9cì\9d\84 ì\9c\84í\95´ <a href='%1'>기ë¶\80</a>룰 í\95´ì£¼ì\9a\94...</translation>
+ <translation>%2ì\9d\98 ê³\84ì\86\8dë\90\9c ê°\9cë°\9cì\9d\84 ì\9c\84í\95´ <a href='%1'>기ë¶\80</a>룰 í\95´ì£¼ì\84¸ì\9a\94.</translation>
</message>
<message>
<source>Translate %1 to your native language using %2</source>
<translation>%2을(를) 사용해서 %1를 사용자의 언어로 번역 하세요.</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>아이콘 디자인: %1</translation>
</message>
<message>
<source>This demo has expired.</source>
- <translation>데모 만료!</translation>
+ <translation>데모버전이 만료되었습니다!</translation>
</message>
<message>
<source>The full version allows you to watch videos without interruptions.</source>
</message>
<message>
<source>By purchasing the full version, you will also support the hard work I put into creating %1.</source>
- <translation>구입하면, 제가 %1를 만드는데 드는 노력을 지원 합니다.</translation>
+ <translation>구입하면, 개발자가 %1를 만드는데 드는 소중한 노력을 지원 합니다.</translation>
</message>
<message>
<source>Use Demo</source>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>다운로드</translation>
</message>
</context>
<context>
</message>
<message>
<source>Unwatched Videos</source>
- <translation>ì\95\88 본 비디오</translation>
+ <translation>ì\8b\9cì²í\95\98ì§\80 ì\95\8aì\9d\80 비디오</translation>
</message>
</context>
<context>
</message>
<message>
<source>Mark all as watched</source>
- <translation>모두 본 걸로 표시</translation>
+ <translation>모두 시청함으로 표시</translation>
</message>
<message>
<source>Show Updated</source>
<translation>업데이트 표시</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>구독 항목이 없습니다. 채널을 구독 하려면 별 아이콘을 사용 하세요.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>모든 비디오</translation>
</message>
<message>
<source>Unwatched Videos</source>
- <translation>ì\95\88 본 비디오</translation>
+ <translation>ì\8b\9cì²í\95\98ì§\80 ì\95\8aì\9d\80 비디오</translation>
</message>
<message>
<source>Mark as Watched</source>
- <translation>본 ë¹\84ë\94\94ì\98¤ë¡\9c ë§\88í\81¬</translation>
+ <translation>모ë\91\90 ì\8b\9cì²í\95¨ì\9c¼ë¡\9c í\91\9cì\8b\9c</translation>
</message>
<message>
<source>Unsubscribe</source>
<source>There are no updated subscriptions at this time.</source>
<translation>현재 업데이트된 구독이 없습니다.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>구독 항목이 없습니다. 채널을 구독 하려면 별 아이콘을 사용 하세요.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>지우기</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<message>
<source>Just now</source>
- <translation type="unfinished"/>
+ <translation>지금</translation>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 조회수</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>%1의 데모 버전 입니다.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>사용자가 다운로드 기능을 테스트 할수 있도록 %1보다 짧은 비디오만 다운로드 됩니다.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>계속</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>풀 버전 구입</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 downloaded in %2</translation>
<name>Extra</name>
<message>
<source>The executable file has been tempered with, maybe by a virus.</source>
- <translation type="unfinished"/>
+ <translation>실행 파일이 바이러스 또는 다른 외부 원인에 의해 손상되었습니다.</translation>
</message>
<message>
<source>%1 will not run. Try installing again.</source>
- <translation type="unfinished"/>
+ <translation>%1을 실행할 수 없습니다. 다시 설치하시기 바랍니다.</translation>
</message>
<message>
<source>Quit</source>
- <translation type="unfinished"/>
+ <translation>종료</translation>
</message>
<message>
<source>Reinstall</source>
- <translation type="unfinished"/>
+ <translation>재설치</translation>
</message>
</context>
<context>
</message>
<message>
<source>Mute</source>
- <translation>무음</translation>
+ <translation>음소거</translation>
</message>
<message>
<source>Seek forward</source>
- <translation>앞으로찾기</translation>
+ <translation>되감기</translation>
</message>
<message>
<source>Seek backward</source>
- <translation>ë\92¤ë¡\9cì°¾기</translation>
+ <translation>빨리ê°\90기</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<source>&Window</source>
- <translation type="unfinished"/>
+ <translation>창 (&W)</translation>
</message>
<message>
<source>&Minimize</source>
- <translation type="unfinished"/>
+ <translation>최소화 (&M)</translation>
</message>
<message>
<source>&Stop</source>
</message>
<message>
<source>Stop playback and go back to the search view</source>
- <translation>재생을 멈추고 검색 보기로 이동</translation>
+ <translation>재생을 멈추고 검색 창로 이동</translation>
</message>
<message>
<source>P&revious</source>
</message>
<message>
<source>Go back to the previous track</source>
- <translation>이전트랙으로 이동</translation>
+ <translation>이전 트랙으로 이동</translation>
</message>
<message>
<source>S&kip</source>
</message>
<message>
<source>Move &Down</source>
- <translation>아래로이동(&D)</translation>
+ <translation>아래로 이동(&D)</translation>
</message>
<message>
<source>Move down the selected videos in the playlist</source>
</message>
<message>
<source>Bye</source>
- <translation>안녕...</translation>
+ <translation>안녕히 가세요.</translation>
</message>
<message>
<source>&Website</source>
<source>&Float on Top</source>
<translation>위에 떠있기(&F)</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>이 비디오 재생 후 정지(&S)</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>제한된 보기</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
+ <translation>부적절한 내용을 포함한 동영상 숨기기</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
<translation type="unfinished"/>
</message>
<message>
</message>
<message>
<source>&Loading...</source>
- <translation type="unfinished"/>
+ <translation>로딩중 (&L)</translation>
</message>
<message>
<source>Leave &Full Screen</source>
</message>
<message>
<source>Volume is muted</source>
- <translation>볼륨 소거됨</translation>
+ <translation>볼륨 ì\9d\8cì\86\8cê±°ë\90¨</translation>
</message>
<message>
<source>Volume is unmuted</source>
- <translation>볼륨 소거 해제됨</translation>
+ <translation>볼륨 ì\9d\91ì\86\8cê±° í\95´ì \9cë\90¨</translation>
</message>
<message>
<source>Maximum video definition set to %1</source>
<source>Update</source>
<translation>업데이트</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>해당 링크는 제한된 시간 동안만 유효 합니다.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>%1의 데모 버전 입니다.%1의 데모 버전 입니다.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>이 버전으로 프로그램이 사용자 필요에 맞는지 테스트 할수 있습니다.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>풀 버전 구입</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>계속</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1 다운로드</translation>
<message>
<source>of</source>
<comment>Used in video parts, as in '2 of 3'</comment>
- <translation>/</translation>
+ <translation>of</translation>
</message>
<message>
<source>part</source>
<translation>%1 구독</translation>
</message>
<message>
- <source>Unsubscribed from %1</source>
+ <source>Switched to %1</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Unsubscribed from %1</source>
+ <translation>%1 구독 해지됨</translation>
+ </message>
<message>
<source>Subscribed to %1</source>
- <translation type="unfinished"/>
+ <translation>%1 구독됨</translation>
</message>
</context>
<context>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 views</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 of %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>검색중...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>%1 추가 보기</translation>
<translation><a href='%1'>%2</a>에 오신걸 환영 합니다!</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>언터</translation>
+ <source>to start watching videos.</source>
+ <translation>비디오 보기 시작</translation>
</message>
<message>
<source>a keyword</source>
<translation>키워드</translation>
</message>
<message>
- <source>a channel</source>
- <translation>채널</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>비디오 보기 시작</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>감상</translation>
+ <source>Enter</source>
+ <translation>언터</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>뒤로(&B)</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>%1(으)로 이동</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>%1의 비디오 가져올수 없음</translation>
+ <translation>%1 다운로드중</translation>
</message>
</context>
<context>
</message>
<message>
<source>Worldwide</source>
- <translation>전세계적</translation>
+ <translation>전세계</translation>
+ </message>
+</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>%1의 비디오 가져올수 없음</translation>
</message>
</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>%2 аркылуу %1'ду өз эне тилиңизге которуңуз</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Иконканын автору %1.</translation>
<source>Show Updated</source>
<translation>Жаңыланганын көрсөтүү</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Сизде жазылуулар жок. Каналдарга жазылуу үчүн жылдызча символун колдонуңуз.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Бардык видеолор</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Учурда жаңыланган жазылуулар жок.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Сизде жазылуулар жок. Каналдарга жазылуу үчүн жылдызча символун колдонуңуз.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Тазалоо</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 көрүү</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Бул жөн эле %1'дун демо-версиясы.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Жүктөө функционалдуулугун текшерүү үчүн, бул %1 минутадан кыскараак видеолорду гана жүктөп бере алат.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Улантуу</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Толук жоромолун алуу</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%2 жерине %1 файлы жүктөлдү</translation>
<source>&Float on Top</source>
<translation>Үстүнөн &калкытуу</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Бул видеодон кийин &токтотуу</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"/>
<source>Update</source>
<translation>Жаңылоо</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Чакан убакытка чейин гана шилтеме анык болот.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Бул жөн эле %1'дун демо-версиясы.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Бул тиркемени сынап көргөнгө мүмкүндүк берет.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Толук версиясын алуу</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Улантуу</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>%1 жүктөп алынууда</translation>
<source>Subscribe to %1</source>
<translation>%1 каналына жазылуу</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 көрүү</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 / %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Изделүүдө...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Дагы %1 видеону көрсөтүү</translation>
<translation><a href='%1'>%2</a>'га кош келиңиз,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Видеолорду</translation>
+ <source>to start watching videos.</source>
+ <translation>менен табып көрүү.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ачкыч сөз</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>менен табып көрүү.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Көрүү</translation>
+ <source>Enter</source>
+ <translation>Видеолорду</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Артка</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>%1 алга</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>%1 үчүн видео агымын алуу мүмкүн эмес</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Бүткүл дүйнө</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>%1 үчүн видео агымын алуу мүмкүн эмес</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
VPATH += $$PWD
# ls -1 *.ts | tr '\n' ' '
-TRANSLATIONS += ar.ts ast.ts be.ts bg_BG.ts ca.ts ca_ES.ts cs_CZ.ts da.ts de_DE.ts el.ts en.ts es.ts es_AR.ts es_ES.ts es_MX.ts fi.ts fi_FI.ts fr.ts gl.ts he_IL.ts hr.ts hu.ts id.ts it.ts ja_JP.ts ko_KR.ts ky.ts ms_MY.ts nb.ts nl.ts nn.ts pl.ts pl_PL.ts pt.ts pt_BR.ts ro.ts ru.ts sk.ts sl.ts sq.ts sr.ts sv_SE.ts th.ts tr.ts uk.ts uk_UA.ts vi.ts zh_CN.ts zh_TW.ts
+TRANSLATIONS += ar.ts ast.ts be.ts bg_BG.ts ca.ts ca_ES.ts cs_CZ.ts da.ts de_DE.ts el.ts en.ts en_GB.ts es.ts es_AR.ts es_ES.ts es_MX.ts fi.ts fi_FI.ts fr.ts gl.ts he_IL.ts hr.ts hu.ts id.ts it.ts ja_JP.ts ko_KR.ts ky.ts ms_MY.ts nb.ts nl.ts nn.ts pl.ts pl_PL.ts pt.ts pt_BR.ts pt_PT.ts ro.ts ru.ts sk.ts sl.ts sq.ts sr.ts sv_SE.ts th.ts tr.ts uk.ts uk_UA.ts vi.ts zh_CN.ts zh_TW.ts
isEmpty(QMAKE_LRELEASE) {
win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\lrelease.exe
else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease
<source>Translate %1 to your native language using %2</source>
<translation>Terjemah %1 kepada bahasa ibunda anda menggunakan %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Diperkasakan oleh %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Perisian sumber-terbuka</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikon direka oleh %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>Anda mempunyai %n video baharu</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Papar Dikemaskini</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Anda tidak mempunyai langganan. Gunakan simbol bintang untuk melanggan saluran.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Semua Video</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Tiada langganan dikemaskini buat masa ini.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Anda tidak mempunyai langganan. Gunakan simbol bintang untuk melanggan saluran.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Kosongkan</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n jam yang lalu</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n hari yang lalu</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>%n bulan yang lalu</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>ditonton %1 kali</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>%n minggu yang lalu</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ini hanyalah versi demo %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Ia hanya boleh muat turun video kurang daripada %1 minit supaya anda boleh menguji kefungsian muat turunnya.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Teruskan</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Dapatkan versi penuh</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 dimuat turun dalam %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n Muat Turun</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>Te&rapung Diatas</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Laras Saiz Tetingkap</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Henti Selepas Video Ini</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Sembunyi video yang mengandungi kandungan tidak senonoh</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Togol Palang &Menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Suka %1? Beri penarafan!</translation>
<source>Update</source>
<translation>Kemaskini</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Anda masih boleh mencapai palang menu dengan menekan kekunci ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Pautan hanya sah untuk masa yang terhad.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ini hanyalah versi demo %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ia membolehkan anda uji aplikasi dan lihat jika ia berfungsi untuk anda.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Dapatkan versi penuh</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Teruskan</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Memuat turun %1</translation>
<source>Subscribe to %1</source>
<translation>Langgan ke %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>Beralih ke %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Nyahlanggan daripada %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>ditonton %1 kali</translation>
+ <source>Pick a video</source>
+ <translation>Pilih satu video</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 daripada %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Menggelintar...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Papar %1 Lagi</translation>
<translation>Selamat datang ke <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Masukkan</translation>
+ <source>to start watching videos.</source>
+ <translation>untuk menonton video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>kata kunci</translation>
</message>
<message>
- <source>a channel</source>
- <translation>saluran</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>untuk menonton video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Tonton</translation>
+ <source>Enter</source>
+ <translation>Masukkan</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Undur</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Maju</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Maju ke %1</translation>
<translation>Memuat turun %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Tidak dapat strim video untuk %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Seluruh Dunia</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Tidak dapat strim video untuk %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Oversett %1 til ditt morsmål ved hjelp av %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikonet er designet av %1.</translation>
<source>Show Updated</source>
<translation>Program Oppdatert</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Du har ingen abonnement. Bruk stjernesymbolet for å abonnemere på kanaler.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alle Videoer</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Det er ingen oppdaterte abonnement for øyeblikket</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Du har ingen abonnement. Bruk stjernesymbolet for å abonnemere på kanaler.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Nullstill</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visninger</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er kun demo-versjonen av %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Den kan kun laste ned videoer på under %1 minutter, for at du skal kunne prøve ut nedlastingsfunksjonen.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortsett</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Kjøp fullversjonen</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 nedlastet på %2</translation>
<source>&Float on Top</source>
<translation>&Vis over andre</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Tilpass Vindusstørrelse</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stopp etter denne videoen</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Skjul videoer som kan inneholde upassende innhold</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Liker du %1? Ranger den!</translation>
<source>Update</source>
<translation>Oppdater</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Denne linken vil kun være gyldig i en begrenset tid.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er kun demoversjonen av %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Dette gir deg muligheten til å prøve ut applikasjonen og se om du den er noe for deg.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Kjøp fullversjonen</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortsett</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Nedlasting %1</translation>
<source>Subscribe to %1</source>
<translation>Abonnér på %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Du er utmeldt fra %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visninger</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 av %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Søker...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Vis %1 Mer</translation>
<translation>Velkommen til <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Skriv</translation>
+ <source>to start watching videos.</source>
+ <translation>for å begynne avspilling av video</translation>
</message>
<message>
<source>a keyword</source>
<translation>ett nøkkelord</translation>
</message>
<message>
- <source>a channel</source>
- <translation>en kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>for å begynne avspilling av video</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Se</translation>
+ <source>Enter</source>
+ <translation>Skriv</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Tilbake</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Fremover til %1</translation>
<translation>Laster ned %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Kan ikke hente mediastrøm for %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Over hele verden</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Kan ikke hente mediastrøm for %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Vertaal %1 naar uw moedertaal met behulp van %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Pictogram ontworpen door %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Downloaden</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Toon bijgewerkte</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>U heeft geen abonnementen. Gebruik het ster-symbool om te abonneren op kanalen.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alle video's</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Er zijn op dit moment geen bijgewerkte abonnementen.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>U heeft geen abonnementen. Gebruik het ster-symbool om te abonneren op kanalen.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Wis</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 bekeken</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dit is slechts de demoversie van %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Het kan alleen maar videos downloaden korter dan %1 minuten zodat u de downloadfunctionaliteit kunt testen.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Ga door</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Verkrijg de volledige versie</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 gedownload in %2</translation>
<name>Extra</name>
<message>
<source>The executable file has been tempered with, maybe by a virus.</source>
- <translation type="unfinished"/>
+ <translation>Het uitvoerbare bestand is aangepast, misschien door een virus.</translation>
</message>
<message>
<source>%1 will not run. Try installing again.</source>
- <translation type="unfinished"/>
+ <translation>%1 kan niet starten. Probeer opnieuw te installeren.</translation>
</message>
<message>
<source>Quit</source>
<source>&Float on Top</source>
<translation>&Zweef erboven</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stop na deze video</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>&Menubalk weergeven/verbergen</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>Vindt u %1 te &gek? Waardeer het!</translation>
<source>Update</source>
<translation>Werk bij</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>U kunt de menubalk nog steeds benaderen door op ALT te drukken</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>De link zal maar een beperkte tijd geldig zijn.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dit is slechts de demoversie van %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Het biedt de mogelijkheid de applicatie te testen en te beoordelen.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Verkrijg de volledige versie</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Ga door</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Bezig met downloaden van %1</translation>
<source>Subscribe to %1</source>
<translation>Abonneer op %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 bekeken</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 van %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Bezig met zoeken...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Toon %1 meer</translation>
<translation>Welkom bij <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Typ</translation>
+ <source>to start watching videos.</source>
+ <translation>om te beginnen met het bekijken van video's.</translation>
</message>
<message>
<source>a keyword</source>
<translation>een zoekwoord</translation>
</message>
<message>
- <source>a channel</source>
- <translation>een kanaal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>om te beginnen met het bekijken van video's.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Bekijk</translation>
+ <source>Enter</source>
+ <translation>Typ</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Terug</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Spoel vooruit naar %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Kan de videostream niet verkrijgen voor %1</translation>
+ <translation>Downloaden van %1...</translation>
</message>
</context>
<context>
<translation>Wereldwijd</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Kan de videostream niet verkrijgen voor %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Omset %1 til morsmålet ditt med %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikonet er utforma av %1.</translation>
<source>Show Updated</source>
<translation>Vis oppdaterte</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Du har ingen tingingar. Bruk stjernesymbolet for å tinga kanalar.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alle videoar</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Ingen oppdaterte tingingar enno.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Du har ingen tingingar. Bruk stjernesymbolet for å tinga kanalar.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Nullstill</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visingar</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er berre demoutgåva av %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Han kan berre lasta ned videoar på under %1 minutt, for at du skal kunna prøva ut nedlastingsfunksjonen.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Hald fram</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Kjøp fullversjonen</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 lasta ned på %2</translation>
<source>&Float on Top</source>
<translation>&Vis over andre</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stopp etter denne videoen</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"/>
<source>Update</source>
<translation>Oppdater</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Denne lenkja vil berre vera gyldig i ei avgrensa tid.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Dette er berre demoutgåva av %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Dette lèt prøva ut programmet og sjå om det er noko for deg.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Kjøp fullversjonen</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Hald fram</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Lastar ned %1</translation>
<source>Subscribe to %1</source>
<translation>Ting %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visingar</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 av %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Søkjer …</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Vis %1 til</translation>
<translation>Velkomen til <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Skriv</translation>
+ <source>to start watching videos.</source>
+ <translation>for å å sjå videoar.</translation>
</message>
<message>
<source>a keyword</source>
<translation>eit nøkkelord</translation>
</message>
<message>
- <source>a channel</source>
- <translation>ein kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>for å å sjå videoar.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Snurr film</translation>
+ <source>Enter</source>
+ <translation>Skriv</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Tilbake</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Fram til %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Kan ikkje henta videostraumen til %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Heile verda</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Kan ikkje henta videostraumen til %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Przetłumacz %1 na swój język używając %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikony zaprojektowane przez %1.</translation>
<source>Show Updated</source>
<translation>Pokaż nowości</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nie masz żadnych subskrypcji. Użyj symbolu gwiazdy do subskrybowania kanałów. </translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Wszystkie filmy </translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Obecnie nie ma żadnych aktualizacji subskrypcji.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nie masz żadnych subskrypcji. Użyj symbolu gwiazdy do subskrybowania kanałów. </translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Wyczyść</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>Wyświetleń: %1</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To jest jedynie wersja demonstracyjna %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Funkcja testowa - można pobierać filmy krótsze niż %1 minut.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Kontynuuj</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Pobierz pełną wersję</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 pobrane w %2</translation>
<source>&Float on Top</source>
<translation>&Zawsze na wierzchu </translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>Dostosuj wielkość okn&a</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Zatrzymaj odtwarzanie, po obejrzeniu tego filmu </translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ukryj filmy mogące zawierać nieodpowiednie treści</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Przełącz pasek &menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Kochasz %1? Oceń to!</translation>
<source>Update</source>
<translation>Aktualizuj</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Wciąz masz dostęp do paska menu poprzez przyciśnięcie klawisza ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Link będzie ważny tylko przez ograniczony czas.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To jest jedynie wersja demonstracyjna %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Pozwala ci to na testowanie i sprawdzenie działania aplikacji.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Uzyskaj pełną wersję</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Kontynuuj</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Pobieranie %1</translation>
<source>Subscribe to %1</source>
<translation>Subskrybuj %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Zakończono subskrypcję %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>Wyświetleń: %1</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 z %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Wyszukiwanie ...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Pokaż kolejne %1</translation>
<translation>Witaj w <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Zatwierdź</translation>
+ <source>to start watching videos.</source>
+ <translation>aby rozpocząć oglądanie</translation>
</message>
<message>
<source>a keyword</source>
<translation>słowo kluczowe</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanał</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>aby rozpocząć oglądanie</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Oglądaj</translation>
+ <source>Enter</source>
+ <translation>Zatwierdź</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Wstecz</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Przewiń do %1</translation>
<translation>Pobieranie %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nie można uzyskać dostępu do %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Ogólnoświatowy</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nie można uzyskać dostępu do %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Przetłumacz %1 na swój język używając %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikony zaprojektowane przez %1.</translation>
<source>Show Updated</source>
<translation>Pokaż zaktualizowane</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nie masz żadnej subskrypcji. Użyj symbolu gwiazdki, aby subskrybować kanały.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Wszystkie filmy</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Brak zaktualizowanych subskrypcji. </translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nie masz żadnej subskrypcji. Użyj symbolu gwiazdki, aby subskrybować kanały.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Wyczyść</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1widziany</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To jest tylko wersja demo %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Może pobierać jedynie filmy krótsze niż %1 minut, dla przetestowania funkcji pobierania.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Dalej</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Pobierz pełną wersję</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 ściągnięte w %2</translation>
<source>&Float on Top</source>
<translation>&Ustaw na wierzchu</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Dopasuj rozmiar okna</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Zatrzymaj po tym filmie</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Ukryj filmy mogące zawierać nieodpowiednie treści</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Przełącz pasek &menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>Uwie&lbiasz %1? Oceń to!</translation>
<source>Update</source>
<translation>Zaktualizuj</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Dostęp do paska menu można uzyskać naciskając klawisz ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Link będzie ważny tylko przez ograniczony czas.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To jest tylko wersja demo %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Pozwala przetestować aplikację, i zobaczyć czy Ci odpowiada.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Zdobądź pełną wersję</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Dalej</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Pobieranie %1</translation>
<source>Subscribe to %1</source>
<translation>Subskrybuj %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Zaprzestano subskrybować %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1widziany</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 z %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Szukanie...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Pokaż kolejne %1</translation>
<translation>Witaj w <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Zatwierdź</translation>
+ <source>to start watching videos.</source>
+ <translation>aby rozpocząć oglądanie</translation>
</message>
<message>
<source>a keyword</source>
<translation>słowo kluczowe</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanał</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>aby rozpocząć oglądanie</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Oglądaj</translation>
+ <source>Enter</source>
+ <translation>Zatwierdź</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Wstecz</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Idź do %1</translation>
<translation>Pobieranie %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Strumieniowanie %1 nie powiodło się</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Ogólnoświatowy</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nie można uzyskać dostępu do %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Ajude a traduzir o %1 através do %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ícone criado por %1.</translation>
<source>Show Updated</source>
<translation>Mostrar atualizados</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Ainda não possui subscrições. Utilize a estrela para subscrever os canais.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos os vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Não existem atualizações de subscrições.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Ainda não possui subscrições. Utilize a estrela para subscrever os canais.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualizações</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta é uma versão de demonstração do %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Apenas pode transferir vídeos mais curtos que %1 minuto(s) de forma a testar a funcionalidade de transferência.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versão completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 transferência em %2</translation>
<source>&Float on Top</source>
<translation>&Flutuante na frente</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajuste o tamanho da janela</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Parar após es&te vídeo</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Gosta? %1? Avalie!</translation>
<source>Update</source>
<translation>Atualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>A ligação será válida por tempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta é uma versão de demonstração do %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Permite-lhe testar ea aplicação e verificar se é do seu agrado.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versão completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Transferência: %1</translation>
<source>Subscribe to %1</source>
<translation>Subscrever %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Não subscrito de %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visualizações</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Procura...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar mais %1</translation>
<translation>Bem-vindo ao <a href='%1'>%2</a></translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introduza</translation>
+ <source>to start watching videos.</source>
+ <translation>para começar a visualizar os vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>uma palavra-chave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>um canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para começar a visualizar os vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Ver</translation>
+ <source>Enter</source>
+ <translation>Introduza</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Recuar</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avançar para %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Não é possível obter a emissão de %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Global</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Não é possível obter a emissão de %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Traduza %1 para seu idioma nativo usando %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>Produzido por %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Software open-source</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ícone desenhado por %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>Você tem %n vídeo(s) novo(s)</numerusform><numerusform>Você tem %n vídeo(s) novo(s)</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Mostrar Atualização</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Você não tem assinaturas. Use o símbolo da estrela para assinar canais.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Todos Os Vídeos</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Não há assinaturas atualizadas neste momento.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Você não tem assinaturas. Use o símbolo da estrela para assinar canais.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Limpar</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n hora(s) atrás</numerusform><numerusform>%n hora(s) atrás</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n dia(s) atrás</numerusform><numerusform>%n dia(s) atrás</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>%n mes(es) atrás</numerusform><numerusform>%n mes(es) atrás</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualizações</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>%n semana(s) atrás</numerusform><numerusform>%n semana(s) atrás</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta é apenas a versão demonstração de %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Só pode fazer download de vídeos menores que %1 minutos para que você possa testar a funcionalidade de download.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versão completa</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 baixados em %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n Downloads</numerusform><numerusform>%n Download(s)</numerusform></translation>
</message>
</context>
<context>
</message>
<message>
<source>Reinstall</source>
- <translation>Reinstale</translation>
+ <translation>Reinstalar</translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Sempre Acima</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajuste o tamanho da janela</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Parar Após Este Vídeo</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
- <translation>Ocultar vídeos com conteúdo inapropriado</translation>
+ <translation>Ocultar vídeos que podem conter conteúdo inapropriado</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>Mostrar Barra De &Menu</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menu</translation>
</message>
<message>
<source>&Love %1? Rate it!</source>
<source>Update</source>
<translation>Atualizar</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>Você ainda pode acessar a barra de menu pressionando a tecla ALT</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>O link só será válido por um tempo limitado.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Esta é apenas a versão demonstração de %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ele permite que você teste o aplicativo e veja se ele funciona para você.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obter a versão completa</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuar</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Baixando %1</translation>
<source>Subscribe to %1</source>
<translation>Assinar %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>Mudar para %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
- <translation>Desinscrito de %1</translation>
+ <translation>Não subscrito de %1</translation>
</message>
<message>
<source>Subscribed to %1</source>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visualizações</translation>
+ <source>Pick a video</source>
+ <translation>Escolher um vídeo</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 de %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Pesquisando...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Mostrar Mais %1</translation>
<translation>Bem-vindo ao <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Digite</translation>
+ <source>to start watching videos.</source>
+ <translation>para começar a assistir vídeos.</translation>
</message>
<message>
<source>a keyword</source>
<translation>uma palavra-chave</translation>
</message>
<message>
- <source>a channel</source>
- <translation>um canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>para começar a assistir vídeos.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Assistir</translation>
+ <source>Enter</source>
+ <translation>Digite</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Voltar</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Avançar</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avançar para %1</translation>
<translation>Baixando %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Não foi possível obter stream de vídeo de %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Mundial</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Não foi possível obter stream de vídeo de %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pt_PT" version="2.1">
+<context>
+ <name>AboutView</name>
+ <message>
+ <source>There's life outside the browser!</source>
+ <translation>Existe vida para além do browser!</translation>
+ </message>
+ <message>
+ <source>Version %1</source>
+ <translation>Versão %1</translation>
+ </message>
+ <message>
+ <source>Licensed to: %1</source>
+ <translation>Licenciado a:</translation>
+ </message>
+ <message>
+ <source>%1 is Free Software but its development takes precious time.</source>
+ <translation>%1 é Software Gratuito mas o seu desenvolvimento leva tempo precioso.</translation>
+ </message>
+ <message>
+ <source>Please <a href='%1'>donate</a> to support the continued development of %2.</source>
+ <translation>Por favor <a href='%1'>doe</a> para suportar o desenvolvimento continuado do %2.</translation>
+ </message>
+ <message>
+ <source>Translate %1 to your native language using %2</source>
+ <translation>Traduza %1 para a sua língua utilizando %2</translation>
+ </message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Icon designed by %1.</source>
+ <translation>Ícone desenhado por %1.</translation>
+ </message>
+ <message>
+ <source>Released under the <a href='%1'>GNU General Public License</a></source>
+ <translation>Lançado nos termos da <a href='"%1'>GNU General Public License</a></translation>
+ </message>
+ <message>
+ <source>&Close</source>
+ <translation>&Fechar</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>Sobre</translation>
+ </message>
+</context>
+<context>
+ <name>ActivationDialog</name>
+ <message>
+ <source>Enter your License Details</source>
+ <translation>Introduza os detalhes da sua Licença</translation>
+ </message>
+ <message>
+ <source>&Email:</source>
+ <translation>&Email:</translation>
+ </message>
+ <message>
+ <source>&Code:</source>
+ <translation>&Código:</translation>
+ </message>
+</context>
+<context>
+ <name>ActivationView</name>
+ <message>
+ <source>Please license %1</source>
+ <translation>Por favor licencie %1</translation>
+ </message>
+ <message>
+ <source>This demo has expired.</source>
+ <translation>Esta demonstração expirou.</translation>
+ </message>
+ <message>
+ <source>The full version allows you to watch videos without interruptions.</source>
+ <translation>A versão completa permite-lhe ver vídeos sem interrupções. </translation>
+ </message>
+ <message>
+ <source>Without a license, the application will expire in %1 days.</source>
+ <translation>Sem uma licença, a aplicação irá expirar em %1 dias.</translation>
+ </message>
+ <message>
+ <source>By purchasing the full version, you will also support the hard work I put into creating %1.</source>
+ <translation>Ao comprar a versão completa, também irá apoiar o trabalho árduo que pus %1.</translation>
+ </message>
+ <message>
+ <source>Use Demo</source>
+ <translation>Usar Demo</translation>
+ </message>
+ <message>
+ <source>Enter License</source>
+ <translation>Introduzir Licença</translation>
+ </message>
+ <message>
+ <source>Buy License</source>
+ <translation>Comprar Licença</translation>
+ </message>
+</context>
+<context>
+ <name>AppWidget</name>
+ <message>
+ <source>Download</source>
+ <translation>Descarregar</translation>
+ </message>
+</context>
+<context>
+ <name>ChannelAggregator</name>
+ <message>
+ <source>By %1</source>
+ <translation>Por %1</translation>
+ </message>
+ <message numerus="yes">
+ <source>You have %n new video(s)</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>ChannelItemDelegate</name>
+ <message>
+ <source>All Videos</source>
+ <translation>Todos os Vídeos</translation>
+ </message>
+ <message>
+ <source>Unwatched Videos</source>
+ <translation>Vídeos não visualizados</translation>
+ </message>
+</context>
+<context>
+ <name>ChannelView</name>
+ <message>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <source>Last Updated</source>
+ <translation>Actualizado ultimamente</translation>
+ </message>
+ <message>
+ <source>Last Added</source>
+ <translation>Adicionado ultimamente</translation>
+ </message>
+ <message>
+ <source>Last Watched</source>
+ <translation>Visualizado ultimamente</translation>
+ </message>
+ <message>
+ <source>Most Watched</source>
+ <translation>Mais visualizados</translation>
+ </message>
+ <message>
+ <source>Sort by</source>
+ <translation>Ordenar por</translation>
+ </message>
+ <message>
+ <source>Mark all as watched</source>
+ <translation>Marcar todos com vistos</translation>
+ </message>
+ <message>
+ <source>Show Updated</source>
+ <translation>Mostrar actualizados</translation>
+ </message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Você não tem subscrições. Use o símbolo estrela para subscrever canais.</translation>
+ </message>
+ <message>
+ <source>All Videos</source>
+ <translation>Todos os Vídeos</translation>
+ </message>
+ <message>
+ <source>Unwatched Videos</source>
+ <translation>Vídeos não visualizados</translation>
+ </message>
+ <message>
+ <source>Mark as Watched</source>
+ <translation>Marcar como visto</translation>
+ </message>
+ <message>
+ <source>Unsubscribe</source>
+ <translation>Anular subscrição</translation>
+ </message>
+ <message>
+ <source>There are no updated subscriptions at this time.</source>
+ <translation>Não há subscrições actualizadas de momento.</translation>
+ </message>
+</context>
+<context>
+ <name>DataUtils</name>
+ <message>
+ <source>Just now</source>
+ <translation>Agora mesmo</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n hour(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <source>%n day(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <source>%n month(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visualizações</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n week(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadItem</name>
+ <message>
+ <source>bytes</source>
+ <translation>bytes</translation>
+ </message>
+ <message>
+ <source>KB</source>
+ <translation>KB</translation>
+ </message>
+ <message>
+ <source>MB</source>
+ <translation>MB</translation>
+ </message>
+ <message>
+ <source>bytes/sec</source>
+ <translation>bytes/seg</translation>
+ </message>
+ <message>
+ <source>KB/sec</source>
+ <translation>KB/seg</translation>
+ </message>
+ <message>
+ <source>MB/sec</source>
+ <translation>MB/seg</translation>
+ </message>
+ <message>
+ <source>seconds</source>
+ <translation>segundos</translation>
+ </message>
+ <message>
+ <source>minutes</source>
+ <translation>minutos</translation>
+ </message>
+ <message>
+ <source>%4 %5 remaining</source>
+ <translation>%4 %5 restante</translation>
+ </message>
+</context>
+<context>
+ <name>DownloadManager</name>
+ <message>
+ <source>%1 downloaded in %2</source>
+ <translation>%1 transferido em %2</translation>
+ </message>
+ <message>
+ <source>Download finished</source>
+ <translation>Transferência concluída</translation>
+ </message>
+ <message numerus="yes">
+ <source>%n Download(s)</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+</context>
+<context>
+ <name>DownloadSettings</name>
+ <message>
+ <source>Change location...</source>
+ <translation>Mudar local...</translation>
+ </message>
+ <message>
+ <source>Choose the download location</source>
+ <translation>Escolha o local de downloads</translation>
+ </message>
+ <message>
+ <source>Download location changed.</source>
+ <translation>Local de downloads mudado</translation>
+ </message>
+ <message>
+ <source>Current downloads will still go in the previous location.</source>
+ <translation>Transferências actuais ainda irão para o local prévio</translation>
+ </message>
+ <message>
+ <source>Downloading to: %1</source>
+ <translation>A transferir para: %1</translation>
+ </message>
+</context>
+<context>
+ <name>DownloadView</name>
+ <message>
+ <source>Downloads</source>
+ <translation>Transferências</translation>
+ </message>
+</context>
+<context>
+ <name>Extra</name>
+ <message>
+ <source>The executable file has been tempered with, maybe by a virus.</source>
+ <translation>O ficheiro executável foi alterado ilegalmente, provavelmente por um vírus.</translation>
+ </message>
+ <message>
+ <source>%1 will not run. Try installing again.</source>
+ <translation>%1 não irá correr. Tente instalar outra vez.</translation>
+ </message>
+ <message>
+ <source>Quit</source>
+ <translation>Sair</translation>
+ </message>
+ <message>
+ <source>Reinstall</source>
+ <translation>Reinstalar</translation>
+ </message>
+</context>
+<context>
+ <name>GlobalShortcuts</name>
+ <message>
+ <source>Play</source>
+ <translation>Reproduzir</translation>
+ </message>
+ <message>
+ <source>Pause</source>
+ <translation>Pausa</translation>
+ </message>
+ <message>
+ <source>Play/Pause</source>
+ <translation>Reproduzir/Pausa</translation>
+ </message>
+ <message>
+ <source>Stop</source>
+ <translation>Parar</translation>
+ </message>
+ <message>
+ <source>Stop playing after current track</source>
+ <translation>Parar reprodução após faixa actual</translation>
+ </message>
+ <message>
+ <source>Next track</source>
+ <translation>Próxima faixa</translation>
+ </message>
+ <message>
+ <source>Previous track</source>
+ <translation>Faixa anterior</translation>
+ </message>
+ <message>
+ <source>Increase volume</source>
+ <translation>Aumentar volume</translation>
+ </message>
+ <message>
+ <source>Decrease volume</source>
+ <translation>Baixar volume</translation>
+ </message>
+ <message>
+ <source>Mute</source>
+ <translation>Desactivar som</translation>
+ </message>
+ <message>
+ <source>Seek forward</source>
+ <translation>Avançar</translation>
+ </message>
+ <message>
+ <source>Seek backward</source>
+ <translation>Retroceder</translation>
+ </message>
+</context>
+<context>
+ <name>HomeView</name>
+ <message>
+ <source>Search</source>
+ <translation>Pesquisar</translation>
+ </message>
+ <message>
+ <source>Find videos and channels by keyword</source>
+ <translation>Encontrar vídeos e canais por palavra-chave</translation>
+ </message>
+ <message>
+ <source>Browse</source>
+ <translation>Navegar</translation>
+ </message>
+ <message>
+ <source>Browse videos by category</source>
+ <translation>Navegar vídeos por categoria</translation>
+ </message>
+ <message>
+ <source>Subscriptions</source>
+ <translation>Subscrições</translation>
+ </message>
+ <message>
+ <source>Channel subscriptions</source>
+ <translation>Subscrições de canais</translation>
+ </message>
+ <message>
+ <source>Make yourself comfortable</source>
+ <translation>Sinta-se confortável</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingWidget</name>
+ <message>
+ <source>Error</source>
+ <translation>Erro</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <source>&Window</source>
+ <translation>&Janela</translation>
+ </message>
+ <message>
+ <source>&Minimize</source>
+ <translation>&Minimizar</translation>
+ </message>
+ <message>
+ <source>&Stop</source>
+ <translation>&Parar</translation>
+ </message>
+ <message>
+ <source>Stop playback and go back to the search view</source>
+ <translation>Parar reprodução e voltar para à vista de pesquisa</translation>
+ </message>
+ <message>
+ <source>P&revious</source>
+ <translation>A&nterior</translation>
+ </message>
+ <message>
+ <source>Go back to the previous track</source>
+ <translation>Retroceder para a faixa anterior</translation>
+ </message>
+ <message>
+ <source>S&kip</source>
+ <translation>S&altar</translation>
+ </message>
+ <message>
+ <source>Skip to the next video</source>
+ <translation>Saltar para o próximo vídeo</translation>
+ </message>
+ <message>
+ <source>&Play</source>
+ <translation>&Reproduzir</translation>
+ </message>
+ <message>
+ <source>Resume playback</source>
+ <translation>Retomar reprodução</translation>
+ </message>
+ <message>
+ <source>&Full Screen</source>
+ <translation>&Ecrã Inteiro</translation>
+ </message>
+ <message>
+ <source>Go full screen</source>
+ <translation>Ir para ecrã inteiro</translation>
+ </message>
+ <message>
+ <source>&Compact Mode</source>
+ <translation>&Modo compacto</translation>
+ </message>
+ <message>
+ <source>Hide the playlist and the toolbar</source>
+ <translation>Esconder a lista de reprodução e a barra de ferramentas</translation>
+ </message>
+ <message>
+ <source>Open the &YouTube Page</source>
+ <translation>Abrir a &Página do YouTube</translation>
+ </message>
+ <message>
+ <source>Go to the YouTube video page and pause playback</source>
+ <translation>Ir para a página de vídeos do YouTube e pausar reprodução</translation>
+ </message>
+ <message>
+ <source>Copy the YouTube &Link</source>
+ <translation>Copiar a &ligação do YouTube</translation>
+ </message>
+ <message>
+ <source>Copy the current video YouTube link to the clipboard</source>
+ <translation>Copiar o URL do video actual do YouTube para a área de transferência</translation>
+ </message>
+ <message>
+ <source>Copy the Video Stream &URL</source>
+ <translation>Copiar o &URL da Stream do Vídeo</translation>
+ </message>
+ <message>
+ <source>Copy the current video stream URL to the clipboard</source>
+ <translation>Copiar o URL da emissão de vídeo actual para a área de transferência</translation>
+ </message>
+ <message>
+ <source>Find Video &Parts</source>
+ <translation>Encontrar &Partes do Vídeo</translation>
+ </message>
+ <message>
+ <source>Find other video parts hopefully in the right order</source>
+ <translation>Procurar outras partes do video, com esperança de estarem na ordenação correcta</translation>
+ </message>
+ <message>
+ <source>&Remove</source>
+ <translation>&Remover</translation>
+ </message>
+ <message>
+ <source>Remove the selected videos from the playlist</source>
+ <translation>Remover os vídeos selecionados da lista de reprodução</translation>
+ </message>
+ <message>
+ <source>Move &Up</source>
+ <translation>Mover &Para cima</translation>
+ </message>
+ <message>
+ <source>Move up the selected videos in the playlist</source>
+ <translation>Mover para cima os vídeos selecionados na lista de reprodução</translation>
+ </message>
+ <message>
+ <source>Move &Down</source>
+ <translation>Mover &Para baixo</translation>
+ </message>
+ <message>
+ <source>Move down the selected videos in the playlist</source>
+ <translation>Mover para baixo os vídeos selecionados na lista de reprodução</translation>
+ </message>
+ <message>
+ <source>&Clear Recent Searches</source>
+ <translation>&Limpar as Pesquisas Recentes</translation>
+ </message>
+ <message>
+ <source>Clear the search history. Cannot be undone.</source>
+ <translation>Limpar o histórico de pesquisa. Não poderá ser desfeito.</translation>
+ </message>
+ <message>
+ <source>&Quit</source>
+ <translation>&Sair</translation>
+ </message>
+ <message>
+ <source>Bye</source>
+ <translation>Adeus</translation>
+ </message>
+ <message>
+ <source>&Website</source>
+ <translation>&Website</translation>
+ </message>
+ <message>
+ <source>%1 on the Web</source>
+ <translation>%1 na Web</translation>
+ </message>
+ <message>
+ <source>Make a &Donation</source>
+ <translation>Faça um &Donativo</translation>
+ </message>
+ <message>
+ <source>Please support the continued development of %1</source>
+ <translation>Por favor apoie o desenvolvimento contínuo do %1</translation>
+ </message>
+ <message>
+ <source>&About</source>
+ <translation>&Sobre</translation>
+ </message>
+ <message>
+ <source>Info about %1</source>
+ <translation>Info sobre %1</translation>
+ </message>
+ <message>
+ <source>Search</source>
+ <translation>Procurar</translation>
+ </message>
+ <message>
+ <source>Mute volume</source>
+ <translation>Desactivar som</translation>
+ </message>
+ <message>
+ <source>&Manually Start Playing</source>
+ <translation>&Começar a Reproduzir Manualmente</translation>
+ </message>
+ <message>
+ <source>Manually start playing videos</source>
+ <translation>Manualmente começar a reproduzir vídeos</translation>
+ </message>
+ <message>
+ <source>&Downloads</source>
+ <translation>&Transferências</translation>
+ </message>
+ <message>
+ <source>Show details about video downloads</source>
+ <translation>Mostrar detalhes sobre transferências de vídeos</translation>
+ </message>
+ <message>
+ <source>&Download</source>
+ <translation>&Download</translation>
+ </message>
+ <message>
+ <source>Download the current video</source>
+ <translation>Transferir o vídeo actual</translation>
+ </message>
+ <message>
+ <source>Take &Snapshot</source>
+ <translation>&Captura de ecrã</translation>
+ </message>
+ <message>
+ <source>&Subscribe to Channel</source>
+ <translation>&Subscrever ao Canal</translation>
+ </message>
+ <message>
+ <source>Share the current video using %1</source>
+ <translation>Partilhar o video actual usando %1</translation>
+ </message>
+ <message>
+ <source>&Email</source>
+ <translation>&Email</translation>
+ </message>
+ <message>
+ <source>Email</source>
+ <translation>Email</translation>
+ </message>
+ <message>
+ <source>&Close</source>
+ <translation>&Fechar</translation>
+ </message>
+ <message>
+ <source>&Float on Top</source>
+ <translation>&Flutuar no Topo</translation>
+ </message>
+ <message>
+ <source>&Stop After This Video</source>
+ <translation>&Parar Após Este Vídeo</translation>
+ </message>
+ <message>
+ <source>&Report an Issue...</source>
+ <translation>&Comunicar um Problema...</translation>
+ </message>
+ <message>
+ <source>&Refine Search...</source>
+ <translation>&Redefinir Pesquisa...</translation>
+ </message>
+ <message>
+ <source>More...</source>
+ <translation>Mais...</translation>
+ </message>
+ <message>
+ <source>&Related Videos</source>
+ <translation>&Vídeos Relacionados</translation>
+ </message>
+ <message>
+ <source>Watch videos related to the current one</source>
+ <translation>Ver vídeos relacionados com o actual</translation>
+ </message>
+ <message>
+ <source>Open in &Browser...</source>
+ <translation>Abrir no &Browser</translation>
+ </message>
+ <message>
+ <source>Restricted Mode</source>
+ <translation>Modo Restrito</translation>
+ </message>
+ <message>
+ <source>Hide videos that may contain inappropriate content</source>
+ <translation>Ocultar vídeos que possam conter conteúdo inapropriado</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>&Love %1? Rate it!</source>
+ <translation>&Gosta do %1? Classifique-o!</translation>
+ </message>
+ <message>
+ <source>Buy %1...</source>
+ <translation>Comprar %1...</translation>
+ </message>
+ <message>
+ <source>&Application</source>
+ <translation>&Aplicação</translation>
+ </message>
+ <message>
+ <source>&Playback</source>
+ <translation>&Reprodução</translation>
+ </message>
+ <message>
+ <source>&Playlist</source>
+ <translation>&Lista de reprodução</translation>
+ </message>
+ <message>
+ <source>&Video</source>
+ <translation>&Vídeo</translation>
+ </message>
+ <message>
+ <source>&Share</source>
+ <translation>&Partilhar</translation>
+ </message>
+ <message>
+ <source>&View</source>
+ <translation>&Visualizar</translation>
+ </message>
+ <message>
+ <source>&Help</source>
+ <translation>&Ajuda</translation>
+ </message>
+ <message>
+ <source>Press %1 to raise the volume, %2 to lower it</source>
+ <translation>Pressione %1 para aumentar o volume, %2 para baixá-lo</translation>
+ </message>
+ <message>
+ <source>Choose your content location</source>
+ <translation>Escolha o local do seu conteúdo</translation>
+ </message>
+ <message>
+ <source>Opening %1</source>
+ <translation>A abrir %1</translation>
+ </message>
+ <message>
+ <source>Do you want to exit %1 with a download in progress?</source>
+ <translation>Deseja sair %1 com uma transferência activa?</translation>
+ </message>
+ <message>
+ <source>If you close %1 now, this download will be cancelled.</source>
+ <translation>Se fechar %1 agora, a transferência será cancelada.</translation>
+ </message>
+ <message>
+ <source>Close and cancel download</source>
+ <translation>Fechar e cancelar transferência</translation>
+ </message>
+ <message>
+ <source>Wait for download to finish</source>
+ <translation>Aguarde para que a transferência termine</translation>
+ </message>
+ <message>
+ <source>Error: %1</source>
+ <translation>Erro: %1</translation>
+ </message>
+ <message>
+ <source>&Pause</source>
+ <translation>&Pausar</translation>
+ </message>
+ <message>
+ <source>Pause playback</source>
+ <translation>Pausar reprodução</translation>
+ </message>
+ <message>
+ <source>&Loading...</source>
+ <translation>&A carregar...</translation>
+ </message>
+ <message>
+ <source>Leave &Full Screen</source>
+ <translation>Sair do &Ecrã Completo</translation>
+ </message>
+ <message>
+ <source>Remaining time: %1</source>
+ <translation>Tempo restante: %1</translation>
+ </message>
+ <message>
+ <source>Volume at %1%</source>
+ <translation>Volume a %1%</translation>
+ </message>
+ <message>
+ <source>Volume is muted</source>
+ <translation>O volume está silenciado</translation>
+ </message>
+ <message>
+ <source>Volume is unmuted</source>
+ <translation>O volume não está silenciado</translation>
+ </message>
+ <message>
+ <source>Maximum video definition set to %1</source>
+ <translation>Definição de vídeo máxima definida para %1</translation>
+ </message>
+ <message>
+ <source>Your privacy is now safe</source>
+ <translation>A sua privacidade está agora segura</translation>
+ </message>
+ <message>
+ <source>Downloads complete</source>
+ <translation>Transferência completa</translation>
+ </message>
+ <message>
+ <source>%1 version %2 is now available.</source>
+ <translation>%1 versão %2 está agora disponível.</translation>
+ </message>
+ <message>
+ <source>Remind me later</source>
+ <translation>Relembrar-me mais tarde</translation>
+ </message>
+ <message>
+ <source>Update</source>
+ <translation>Actualizar</translation>
+ </message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MediaView</name>
+ <message>
+ <source>You can now paste the YouTube link into another application</source>
+ <translation>Pode agora colar a ligação do Youtube para outra aplicação</translation>
+ </message>
+ <message>
+ <source>You can now paste the video stream URL into another application</source>
+ <translation>Agora você pode colar o URL do vídeo noutra aplicação</translation>
+ </message>
+ <message>
+ <source>The link will be valid only for a limited time.</source>
+ <translation>A ligação será válida apenas por um tempo limitado.</translation>
+ </message>
+ <message>
+ <source>Downloading %1</source>
+ <translation>A transferir %1</translation>
+ </message>
+ <message>
+ <source>of</source>
+ <comment>Used in video parts, as in '2 of 3'</comment>
+ <translation>de</translation>
+ </message>
+ <message>
+ <source>part</source>
+ <comment>This is for video parts, as in 'Cool video - part 1'</comment>
+ <translation>parte</translation>
+ </message>
+ <message>
+ <source>episode</source>
+ <comment>This is for video parts, as in 'Cool series - episode 1'</comment>
+ <translation>episódio</translation>
+ </message>
+ <message>
+ <source>Sent from %1</source>
+ <translation>Enviado de %1</translation>
+ </message>
+ <message>
+ <source>Unsubscribe from %1</source>
+ <translation>Anular subscrição de %1</translation>
+ </message>
+ <message>
+ <source>Subscribe to %1</source>
+ <translation>Subscrever %1</translation>
+ </message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Unsubscribed from %1</source>
+ <translation>Anular subscrição de %1</translation>
+ </message>
+ <message>
+ <source>Subscribed to %1</source>
+ <translation>Subscrito a %1</translation>
+ </message>
+</context>
+<context>
+ <name>MessageWidget</name>
+ <message>
+ <source>A new version of %1 is available!</source>
+ <translation>Uma nova versão do %1 está disponível!</translation>
+ </message>
+ <message>
+ <source>%1 %2 is now available. You have %3.</source>
+ <translation>%1 %2 está agora disponível. Você tem %3.</translation>
+ </message>
+ <message>
+ <source>Would you like to download it now?</source>
+ <translation>Gostaria de descarregar agora?</translation>
+ </message>
+ <message>
+ <source>Skip This Version</source>
+ <translation>Saltar Esta Versão</translation>
+ </message>
+ <message>
+ <source>Remind Me Later</source>
+ <translation>Relembrar-me Mais Tarde</translation>
+ </message>
+ <message>
+ <source>Install Update</source>
+ <translation>Instalar Actualização</translation>
+ </message>
+</context>
+<context>
+ <name>PasteLineEdit</name>
+ <message>
+ <source>Paste</source>
+ <translation>Colar</translation>
+ </message>
+</context>
+<context>
+ <name>PickMessage</name>
+ <message>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
+ <message>
+ <source>%1 of %2 (%3) — %4</source>
+ <translation>%1 de %2 (%3) — %4</translation>
+ </message>
+ <message>
+ <source>Preparing</source>
+ <translation>A preparar</translation>
+ </message>
+ <message>
+ <source>Failed</source>
+ <translation>Falhou</translation>
+ </message>
+ <message>
+ <source>Completed</source>
+ <translation>Completado</translation>
+ </message>
+ <message>
+ <source>Stopped</source>
+ <translation>Parado</translation>
+ </message>
+ <message>
+ <source>Stop downloading</source>
+ <translation>Parar de transferir</translation>
+ </message>
+ <message>
+ <source>Show in %1</source>
+ <translation>Mostrar em %1</translation>
+ </message>
+ <message>
+ <source>Open parent folder</source>
+ <translation>Abrir a pasta-mãe</translation>
+ </message>
+ <message>
+ <source>Restart downloading</source>
+ <translation>Recomeçar a transferir</translation>
+ </message>
+</context>
+<context>
+ <name>PlaylistModel</name>
+ <message>
+ <source>Show %1 More</source>
+ <translation>Mostrar Mais %1</translation>
+ </message>
+ <message>
+ <source>No videos</source>
+ <translation>Sem vídeos</translation>
+ </message>
+ <message>
+ <source>No more videos</source>
+ <translation>Sem mais vídeos</translation>
+ </message>
+</context>
+<context>
+ <name>RefineSearchWidget</name>
+ <message>
+ <source>Sort by</source>
+ <translation>Ordenar por</translation>
+ </message>
+ <message>
+ <source>Relevance</source>
+ <translation>Relevância</translation>
+ </message>
+ <message>
+ <source>Date</source>
+ <translation>Data</translation>
+ </message>
+ <message>
+ <source>View Count</source>
+ <translation>Contador de visualizações</translation>
+ </message>
+ <message>
+ <source>Rating</source>
+ <translation>Classificação</translation>
+ </message>
+ <message>
+ <source>Anytime</source>
+ <translation>Qualquer altura</translation>
+ </message>
+ <message>
+ <source>Today</source>
+ <translation>Hoje</translation>
+ </message>
+ <message>
+ <source>7 Days</source>
+ <translation>7 Dias</translation>
+ </message>
+ <message>
+ <source>30 Days</source>
+ <translation>30 Dias</translation>
+ </message>
+ <message>
+ <source>Duration</source>
+ <translation>Duração</translation>
+ </message>
+ <message>
+ <source>All</source>
+ <translation>Todos</translation>
+ </message>
+ <message>
+ <source>Short</source>
+ <translation>Curto</translation>
+ </message>
+ <message>
+ <source>Medium</source>
+ <translation>Médio</translation>
+ </message>
+ <message>
+ <source>Long</source>
+ <translation>Longo</translation>
+ </message>
+ <message>
+ <source>Less than 4 minutes</source>
+ <translation>Menos de 4 minutos</translation>
+ </message>
+ <message>
+ <source>Between 4 and 20 minutes</source>
+ <translation>Entre 4 a 20 minutos</translation>
+ </message>
+ <message>
+ <source>Longer than 20 minutes</source>
+ <translation>Mais de 20 minutos</translation>
+ </message>
+ <message>
+ <source>Quality</source>
+ <translation>Qualidade</translation>
+ </message>
+ <message>
+ <source>High Definition</source>
+ <translation>Alta-Definição</translation>
+ </message>
+ <message>
+ <source>720p or higher</source>
+ <translation>720p ou mais alto</translation>
+ </message>
+ <message>
+ <source>Done</source>
+ <translation>Feito</translation>
+ </message>
+</context>
+<context>
+ <name>RegionsView</name>
+ <message>
+ <source>Done</source>
+ <translation>Feito</translation>
+ </message>
+</context>
+<context>
+ <name>SearchLineEdit</name>
+ <message>
+ <source>Search</source>
+ <translation>Pesquisar</translation>
+ </message>
+</context>
+<context>
+ <name>SearchView</name>
+ <message>
+ <source>Welcome to <a href='%1'>%2</a>,</source>
+ <translation>Bem-vindo ao <a href='%1'>%2</a>,</translation>
+ </message>
+ <message>
+ <source>to start watching videos.</source>
+ <translation>para começar a ver vídeos.</translation>
+ </message>
+ <message>
+ <source>a keyword</source>
+ <translation>uma palava-chave</translation>
+ </message>
+ <message>
+ <source>Enter</source>
+ <translation>Insira</translation>
+ </message>
+ <message>
+ <source>Recent keywords</source>
+ <translation>Palavras-chave recentes</translation>
+ </message>
+ <message>
+ <source>Recent channels</source>
+ <translation>Canais recentes</translation>
+ </message>
+ <message>
+ <source>Get the full version</source>
+ <translation>Obter a versão completa</translation>
+ </message>
+</context>
+<context>
+ <name>SidebarHeader</name>
+ <message>
+ <source>&Back</source>
+ <translation>&Atrás</translation>
+ </message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Forward to %1</source>
+ <translation>Avançar até %1</translation>
+ </message>
+ <message>
+ <source>Back to %1</source>
+ <translation>Retroceder até %1</translation>
+ </message>
+</context>
+<context>
+ <name>SidebarWidget</name>
+ <message>
+ <source>Refine Search</source>
+ <translation>Redefinir Pesquisa</translation>
+ </message>
+ <message>
+ <source>Did you mean: %1</source>
+ <translation>Queria dizer: %1</translation>
+ </message>
+</context>
+<context>
+ <name>SnapshotSettings</name>
+ <message>
+ <source>Change location...</source>
+ <translation>Mudar local...</translation>
+ </message>
+ <message>
+ <source>Snapshot saved to %1</source>
+ <translation>Captura de ecrã guardada em %1</translation>
+ </message>
+ <message>
+ <source>Snapshots location changed.</source>
+ <translation>Local de capturas de ecrã mudado.</translation>
+ </message>
+</context>
+<context>
+ <name>StandardFeedsView</name>
+ <message>
+ <source>Most Popular</source>
+ <translation>Mais Popular</translation>
+ </message>
+</context>
+<context>
+ <name>UpdateDialog</name>
+ <message>
+ <source>Downloading update...</source>
+ <translation>A transferir actualização...</translation>
+ </message>
+ <message>
+ <source>Downloading %1...</source>
+ <translation>A transferir %1...</translation>
+ </message>
+</context>
+<context>
+ <name>YTRegions</name>
+ <message>
+ <source>Algeria</source>
+ <translation>Argélia</translation>
+ </message>
+ <message>
+ <source>Argentina</source>
+ <translation>Argentina</translation>
+ </message>
+ <message>
+ <source>Australia</source>
+ <translation>Austrália</translation>
+ </message>
+ <message>
+ <source>Belgium</source>
+ <translation>Bélgica</translation>
+ </message>
+ <message>
+ <source>Brazil</source>
+ <translation>Brasil</translation>
+ </message>
+ <message>
+ <source>Canada</source>
+ <translation>Canadá</translation>
+ </message>
+ <message>
+ <source>Chile</source>
+ <translation>Chile</translation>
+ </message>
+ <message>
+ <source>Colombia</source>
+ <translation>Colômbia</translation>
+ </message>
+ <message>
+ <source>Czech Republic</source>
+ <translation>República Checa</translation>
+ </message>
+ <message>
+ <source>Egypt</source>
+ <translation>Egipto</translation>
+ </message>
+ <message>
+ <source>France</source>
+ <translation>França</translation>
+ </message>
+ <message>
+ <source>Germany</source>
+ <translation>Alemanha</translation>
+ </message>
+ <message>
+ <source>Ghana</source>
+ <translation>Gana</translation>
+ </message>
+ <message>
+ <source>Greece</source>
+ <translation>Grécia</translation>
+ </message>
+ <message>
+ <source>Hong Kong</source>
+ <translation>Hong Kong</translation>
+ </message>
+ <message>
+ <source>Hungary</source>
+ <translation>Hungria</translation>
+ </message>
+ <message>
+ <source>India</source>
+ <translation>Índia</translation>
+ </message>
+ <message>
+ <source>Indonesia</source>
+ <translation>Indonésia</translation>
+ </message>
+ <message>
+ <source>Ireland</source>
+ <translation>Irlanda</translation>
+ </message>
+ <message>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <source>Italy</source>
+ <translation>Itália</translation>
+ </message>
+ <message>
+ <source>Japan</source>
+ <translation>Japão</translation>
+ </message>
+ <message>
+ <source>Jordan</source>
+ <translation>Jordânia</translation>
+ </message>
+ <message>
+ <source>Kenya</source>
+ <translation>Quénia</translation>
+ </message>
+ <message>
+ <source>Malaysia</source>
+ <translation>Malásia</translation>
+ </message>
+ <message>
+ <source>Mexico</source>
+ <translation>México</translation>
+ </message>
+ <message>
+ <source>Morocco</source>
+ <translation>Marrocos</translation>
+ </message>
+ <message>
+ <source>Netherlands</source>
+ <translation>Holanda</translation>
+ </message>
+ <message>
+ <source>New Zealand</source>
+ <translation>Nova Zelândia</translation>
+ </message>
+ <message>
+ <source>Nigeria</source>
+ <translation>Nigéria</translation>
+ </message>
+ <message>
+ <source>Peru</source>
+ <translation>Perú</translation>
+ </message>
+ <message>
+ <source>Philippines</source>
+ <translation>Filipinas</translation>
+ </message>
+ <message>
+ <source>Poland</source>
+ <translation>Polónia</translation>
+ </message>
+ <message>
+ <source>Russia</source>
+ <translation>Rússia </translation>
+ </message>
+ <message>
+ <source>Saudi Arabia</source>
+ <translation>Arábia Saudita</translation>
+ </message>
+ <message>
+ <source>Singapore</source>
+ <translation>Singapura</translation>
+ </message>
+ <message>
+ <source>South Africa</source>
+ <translation>África do Sul</translation>
+ </message>
+ <message>
+ <source>South Korea</source>
+ <translation>Coreia do Sul</translation>
+ </message>
+ <message>
+ <source>Spain</source>
+ <translation>Espanha</translation>
+ </message>
+ <message>
+ <source>Sweden</source>
+ <translation>Suécia</translation>
+ </message>
+ <message>
+ <source>Taiwan</source>
+ <translation>Ilha Formosa</translation>
+ </message>
+ <message>
+ <source>Tunisia</source>
+ <translation>Tunísia</translation>
+ </message>
+ <message>
+ <source>Turkey</source>
+ <translation>Turquia</translation>
+ </message>
+ <message>
+ <source>Uganda</source>
+ <translation>Uganda</translation>
+ </message>
+ <message>
+ <source>United Arab Emirates</source>
+ <translation>Emirados Árabes Unidos</translation>
+ </message>
+ <message>
+ <source>United Kingdom</source>
+ <translation>Reino Unido</translation>
+ </message>
+ <message>
+ <source>Yemen</source>
+ <translation>Iémen</translation>
+ </message>
+ <message>
+ <source>Worldwide</source>
+ <translation>Mundial</translation>
+ </message>
+</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Não é possível obter a stream do video para %1</translation>
+ </message>
+</context>
+</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Tradu %1 în limba proprie folosind %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Iconița a fost concepută de %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Descarcă</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Arata actualizeazările</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nu esti abonat la nimic. Ca sa te abonezi la canale, foloseste simbolul stea.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Toate videoclipurile</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Nici unul din canalele la care esti abonat nu are actualizari momentan.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nu esti abonat la nimic. Ca sa te abonezi la canale, foloseste simbolul stea.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Șterge</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 vizionări</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aceasta este doar o versiune demo a %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Poate să descarce doar videoclipurile mai mici de %1 minute astfel încât să puteți testa funcționalitatea de descărcare.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuă</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>ObțineIa versiunea integrală</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 descărcat în %2</translation>
<source>&Float on Top</source>
<translation>&Detașează</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Ajustați dimensiunea ferestrei</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Oprește După Acest Videoclip</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Mod restricționat</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
+ <translation>Ascundeți videoclipuri care pot conține conținut neadecvat</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
<translation type="unfinished"/>
</message>
<message>
<source>Update</source>
<translation>Actualizează</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Adresa va fi validă doar pentru o perioadă limitată de timp.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Aceasta este doar o versiune demo a %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Vă permite să testați aplicația și să vedeți dacă funcționează.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Obține versiunea integrală</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Continuă</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Descărcare %1</translation>
<source>Subscribe to %1</source>
<translation>Aboneaza-te de la %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Dezabonează-te de la %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 vizionări</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 din %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Căutare...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Afișează încă %1</translation>
<translation>Bine ați venit la <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Introduceți</translation>
+ <source>to start watching videos.</source>
+ <translation>pentru a începe să vizionați videoclipuri.</translation>
</message>
<message>
<source>a keyword</source>
<translation>un cuvânt cheie</translation>
</message>
<message>
- <source>a channel</source>
- <translation>un canal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>pentru a începe să vizionați videoclipuri.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Urmărește</translation>
+ <source>Enter</source>
+ <translation>Introduceți</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Î&napoi</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Avanseaza la %1</translation>
</message>
<message>
<source>Snapshots location changed.</source>
- <translation type="unfinished"/>
+ <translation>Locația instantaneelor a fost modificată.</translation>
</message>
</context>
<context>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nu poate fi accesat fluxul video pentru %1</translation>
+ <translation>Se descarcă %1</translation>
</message>
</context>
<context>
<translation>Global</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nu poate fi accesat fluxul video pentru %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Перевести %1 на ваш родной язык с помощью %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Автор значка %1.</translation>
<source>Show Updated</source>
<translation>Показать обновленные</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>У вас нет подписок. Используйте символ звездочки чтобы подписаться на каналы.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Все видео</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>В настоящее время нет обновлений подписок.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>У вас нет подписок. Используйте символ звездочки чтобы подписаться на каналы.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Очистить</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 просмотров</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Это всего лишь демо версия %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Можно загружать только видео не длиннее %1 минут, для проверки функциональности загрузчика. </translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продолжить</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Получить полную версию</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 загружен в %2</translation>
<source>&Float on Top</source>
<translation>&Поверх всех окон</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Подстраивать размер окна</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Ост&ановить после этого видео</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Скрыть видео, содержащие недопустимый контент </translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Нравится %1? Оцени!</translation>
<source>Update</source>
<translation>Обновление</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Адрес будет существовать ограниченное время.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Данная программа является демо-версией %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Она позволяет вам оценить приложение.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Купить полную версию</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продолжить</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Загружаю %1</translation>
<source>Subscribe to %1</source>
<translation>Подписаться на %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Отписаны от %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 просмотров</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 из %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Идет поиск...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Показать ещё %1</translation>
<translation>Добро пожаловать в <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Введите</translation>
+ <source>to start watching videos.</source>
+ <translation>чтобы начать просмотр.</translation>
</message>
<message>
<source>a keyword</source>
<translation>запрос</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>чтобы начать просмотр.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Смотреть</translation>
+ <source>Enter</source>
+ <translation>Введите</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Н&азад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Вперед к %1</translation>
<translation>Загрузка %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Не удалось получить видео поток для %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Во всем мире</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Не удалось получить видео поток для %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Prelož %1 do svojho materinského jazyka cez %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikonu nadizajnoval %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Zobraziť aktualizované</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nemáš žiadne subskripcie. Použi symbol hviezdy pre odoberanie kanálov.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Všetky videá</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Nie sú k dispozícii žiadne aktualizované subskripcie.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nemáš žiadne subskripcie. Použi symbol hviezdy pre odoberanie kanálov.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Vyčisiť</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 prezretí</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ide o demoverziu %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Umožní ti stiahnuť iba videý kratšie ako %1 minút, takže aspoň môžeš otestovať túto funkcionalitu.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Pokračuj</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Získať plnú verziu</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 stiahnuté za %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Vždy na vrchu</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Upraviť veľkosť okna</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Zastav po tomto videu</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>%Páči sa vám %1? ohodnoťte ho!</translation>
<source>Update</source>
<translation>Aktualizácia</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Odkaz bude platný len obmedzenú dobu.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ide o demoverziu %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Umožní ti aplikáciu vyskúšať a pohodlne otestovať.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Získať plnú verziu</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Pokračuj</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Sťahujem %1.</translation>
<source>Subscribe to %1</source>
<translation>Odoberať z %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Bol zrušený odber kanála %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 prezretí</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 z %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Hľadám...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Zobraz %1 viac</translation>
<translation>Vitaj v aplikácii <a href='%1>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Vlož</translation>
+ <source>to start watching videos.</source>
+ <translation>pre spustenie sledovania.</translation>
</message>
<message>
<source>a keyword</source>
<translation>kľúčové slovo</translation>
</message>
<message>
- <source>a channel</source>
- <translation>názov kanálu</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>pre spustenie sledovania.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Pozerať</translation>
+ <source>Enter</source>
+ <translation>Vlož</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Späť</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Vpred k %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nedostupný video stream pre %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Celosvetovo</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nedostupný video stream pre %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Prevedite %1 v vaš jezik z uporabo programa %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikone je izrisal %1.</translation>
<name>AppWidget</name>
<message>
<source>Download</source>
- <translation type="unfinished"/>
+ <translation>Prenesi</translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Pokaži posodobitve</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nimate naročnin. Uporabite zvezdo da se naročite na kanale.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Vsi videoposnetki</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Za zdaj ni nobenih novih posnetkov, na katere ste prijavljeni</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nimate naročnin. Uporabite zvezdo da se naročite na kanale.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Počisti</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 predvajanj</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To je samo preizkusna različica programa %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Prenese lahko samo posnetke krajše od %1 minut, da lahko preverite delovanje funkcije prenosa.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Nadaljuj</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Pridobi popolno različico</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 preneseno v %2</translation>
<source>&Float on Top</source>
<translation>Lebdeče na vrhu</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Prilagodi velikost okna</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>U&stavi za tem posnetkom</translation>
</message>
<message>
<source>Restricted Mode</source>
- <translation type="unfinished"/>
+ <translation>Omejeni način</translation>
</message>
<message>
<source>Hide videos that may contain inappropriate content</source>
+ <translation>Skriti videoposnetki lahko vsebujejo neprimerno vsebino.</translation>
+ </message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
<translation type="unfinished"/>
</message>
<message>
<source>Update</source>
<translation>Posodobitev</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Povezava bo delovala le za omejen čas.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>To je samo demo različica programa %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Dovoli vam testiranje aplikacije in preverjanje delovanja,</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Pridobi celotno različico</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Nadaljuj</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Prenašanje %1</translation>
<source>Subscribe to %1</source>
<translation>Naroči se na %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Odjavljeni od %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 predvajanj</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 od %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Iskanje ...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Pokaži %1 več</translation>
<translation>Pozdravljeni v <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Vnesite</translation>
+ <source>to start watching videos.</source>
+ <translation>in začnite gledati posnetke.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ključno besedo</translation>
</message>
<message>
- <source>a channel</source>
- <translation>kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>in začnite gledati posnetke.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Glejte</translation>
+ <source>Enter</source>
+ <translation>Vnesite</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Na&zaj</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Naprej na %1</translation>
</message>
<message>
<source>Downloading %1...</source>
- <translation type="unfinished"/>
- </message>
-</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Za %1 ni mogoče pridobiti video pretoka</translation>
+ <translation>Prenašanje %1 ...</translation>
</message>
</context>
<context>
<translation>Svetovno</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Za %1 ni mogoče pridobiti video pretoka</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Perkthe %1 ne gjuhen e tuaj ame duke perdorur %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Dizajnimi i ikones u be nga %1</translation>
<source>Show Updated</source>
<translation>Shfaq arrnimin</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Nuk keni abonime . Perdor simbolin yll për tu abonuar te kanalet</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>T'gjitha videot</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Nuk ka abonime të freskuar për momentin</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Nuk keni abonime . Perdor simbolin yll për tu abonuar te kanalet</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Paster</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 e shikimeve</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ky eshte vetem version per demonstrim i %1</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Mund te shkarkoj vetem video me te shkurta se %1 minut ne menyr qe te testoni funksionimin e shkarkuesit. </translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Vazhdon</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Merrni versionin e plote</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 shkarkuar në %2</translation>
<source>&Float on Top</source>
<translation>&Nxjerr ne Krye</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Ndalo pas kesaj video</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation type="unfinished"/>
<source>Update</source>
<translation>Arrnim</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Linku do te jet i vlefshem per nje kohe te kufizuar</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ky eshte version vetem per demonstrim i %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ju lejon qe te provoni programin dhe te shifni se a funksionon per ju .</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Merrni versionin e plote</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Vazhdim</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Duke shkarkuar %1</translation>
<source>Subscribe to %1</source>
<translation>Abonohu në %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 e shikimeve</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 e %2 (%3)--%4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Duke kerkuar</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Shfaq %1 më shumë</translation>
<translation>Mire se erdhet ne <h href="%1">%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Hyr</translation>
+ <source>to start watching videos.</source>
+ <translation>Per te filluar shikimin e videove.</translation>
</message>
<message>
<source>a keyword</source>
<translation>Nje fjal kyqe</translation>
</message>
<message>
- <source>a channel</source>
- <translation>Nje kanal </translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>Per te filluar shikimin e videove.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Shiko</translation>
+ <source>Enter</source>
+ <translation>Hyr</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Prapa</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Përpara në %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Nuk mund te merr rrjedhen e videos per %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Mbar Bota</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Nuk mund te merr rrjedhen e videos per %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Преведите %1 на ваш матерњи језик помоћу %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Иконицу дизајниро %1.</translation>
<source>Show Updated</source>
<translation>Прикажи ажурирано</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Немате претплате. Користи звезду да се претплатиш.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Сви видеи</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Нема ажурираних субскрипција тренутно</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Немате претплате. Користи звезду да се претплатиш.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Очисти</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 прегледа</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ово је тек демо верзија %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Може преузети само снимке краће од %1 минута тако да можете испробати употребљивост преузимања.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Настави</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Преузмите пуну верзију</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 преузето у %2</translation>
<source>&Float on Top</source>
<translation>&Флутај на врху</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Прилагоди величину прозора</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Зау&стави након овог видеа</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Обожавате %1? Оцените!</translation>
<source>Update</source>
<translation>Ажурирај</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Веза је исправна само одређено време.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Ово је тек демо верзија %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Омогућава вам да испробате програм и увидите да ли вам одговара.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Преузмите пуну верзију</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Настави</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>преузимам %1</translation>
<source>Subscribe to %1</source>
<translation>Претплати се на %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Ун-претплаћен од %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 прегледа</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 од %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Тражим...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Прикажи још %1</translation>
<translation>Добродошли у <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>унесите</translation>
+ <source>to start watching videos.</source>
+ <translation>да би започели преглед видео снимака.</translation>
</message>
<message>
<source>a keyword</source>
<translation>кључну реч</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>да би започели преглед видео снимака.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Гледај</translation>
+ <source>Enter</source>
+ <translation>унесите</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Назад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Напред до %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>не могу да добавим видео ток за %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Широм света</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>не могу да добавим видео ток за %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Översätt %1 till ditt modersmål med %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Ikon designad av %1</translation>
<source>Show Updated</source>
<translation>Visa Uppdaterat</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Du har inga prenumerationer. Använd stjärnsymbolen för att prenumerera på kanaler.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Alla Videor</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Det finns inga uppdaterade prenumerationer just nu.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Du har inga prenumerationer. Använd stjärnsymbolen för att prenumerera på kanaler.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Rensa</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 visningar</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Detta är bara en demoversion av %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Den kan bara ladda ner filmer kortare än %1 minuter så att du kan testa ladda-ned funktionen.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortsätt</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Skaffa den fullständiga versionen</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 nedladdad i %2</translation>
<source>&Float on Top</source>
<translation>&Flyt ovanpå</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Justera Fönster Storleken</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Stoppa efter denna video</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Älskar du %1? Betygsätt den!</translation>
<source>Update</source>
<translation>Uppdatera</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Länken kommer att gälla endast under en begränsad tid.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Detta är bara en demoversion av %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Den tillåter dig att testa programmet och se om det fungerar för dig.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Skaffa den fullständiga versionen</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Fortsätt</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Hämtar %1</translation>
<source>Subscribe to %1</source>
<translation>Prenumerera på %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Prenumeration avbruten från %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 visningar</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 av %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Söker...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Visa %1 Till</translation>
<translation>Välkommen till <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Ange</translation>
+ <source>to start watching videos.</source>
+ <translation>för att börja titta på video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>ett sökord</translation>
</message>
<message>
- <source>a channel</source>
- <translation>en kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>för att börja titta på video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Titta</translation>
+ <source>Enter</source>
+ <translation>Ange</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Tillbaka</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Framåt till %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Kan inte få videoström för %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Från Hela Världen</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Kan inte få videoström för %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>แปล %1 สุ่ภาษาของคุณโดยใช้ %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>ออกแบบไอคอนโดย %1</translation>
<source>Show Updated</source>
<translation>แสดงอัพเดต</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>คุณไม่มีการสมัครติดตาม ใช้สัญลักษณ์ดาวเพื่อสมัครติดตามช่อง</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>วิดีโอทั้งหมด</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>ไม่มีการสมัครติดตามที่อัพเดต ณ เวลานี้</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>คุณไม่มีการสมัครติดตาม ใช้สัญลักษณ์ดาวเพื่อสมัครติดตามช่อง</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>ล้าง</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>ดู %1 ครั้ง </translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>นี้เป็นแค่เวอร์ชั่นทดลองใช้ของ %1</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>สามารถดาวน์โหลดวิดึโอส้ันๆกว่า %1 นาที เพื่อทดสอบฟังชั่นดาวน์โหลด</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>ต่อไป</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>รับเวอร์ชั่นเต็ม</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 ถูกดาวน์โหลดใน %2</translation>
<source>&Float on Top</source>
<translation>&วางอยู่บนสุด</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&หยุดหลังจากวิดีโอนี้</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&ชอบ %1 มั้ย? ให้คะแนนมันหน่อย!</translation>
<source>Update</source>
<translation>อัพเดต</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>ลิงค์จะใช้ได้ในระยะเวลาที่จำกัดเท่านั้น</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>นี้เป็นแค่เวอร์ชั่นทดลองใช้ %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>มันช่วยให้คุณทดสอบโปรแกรมและดูว่ามันใช้ได้ผลกับคุณหรือเปล่า</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>ทำให้เป็นเวอร์ชั่นเต็ม</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>ต่อไป</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>กำลังดาวน์โหลด %1</translation>
<source>Subscribe to %1</source>
<translation>สมัครติดตามสู่ %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>ดู %1 ครั้ง </translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 ของ %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>กำลังค้นหา...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>แสดง %1 เพิ่มเติม</translation>
<translation>ยินดีต้อนรับสู่ <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>ตกลง</translation>
+ <source>to start watching videos.</source>
+ <translation>เพื่อเริ่มรับชมวิดีโอ</translation>
</message>
<message>
<source>a keyword</source>
<translation>คำสำคัญ</translation>
</message>
<message>
- <source>a channel</source>
- <translation>ช่อง</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>เพื่อเริ่มรับชมวิดีโอ</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>รับชม</translation>
+ <source>Enter</source>
+ <translation>ตกลง</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&กลับ</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>เดินหน้าสู่ %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>ไม่สามารถรับกระแสวิดีโอของ %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>ทั่วโลก</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>ไม่สามารถรับกระแสวิดีโอของ %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>%1'i, %2 kullanarak kendi dilinize çevirin</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>% 1 tarafından desteklenmektedir</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>Açık kaynaklı yazılım</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Simge %1 tarafından tasarlandı.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>%n yeni videonuz var</numerusform><numerusform>%n yeni videonuz var</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Güncellenenleri Göster</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Hiç aboneliğiniz yok. Kanallara abone olmak için yıldız simgesini kullanın.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Tüm Videolar</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Şu anda güncellenen abonelik yok.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Hiç aboneliğiniz yok. Kanallara abone olmak için yıldız simgesini kullanın.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Temizle</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>&n saat önce</numerusform><numerusform>&n saat önce</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>&n gün önce</numerusform><numerusform>&n gün önce</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>&n ay önce</numerusform><numerusform>&n ay önce</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 görüntülenme</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>&n hafta önce</numerusform><numerusform>&n hafta önce</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Bu sadece %'in demo sürümüdür.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Bu sadece %1 dakikadan kısa videoları indirebilir, indirme özelliğini böylece test edebilirsiniz.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Devam</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Tam sürüme geç</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%2 de %1 indirildi</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation><numerusform>&n İndirme</numerusform><numerusform>&n İndirme</numerusform></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<source>&Window</source>
- <translation>Pencere</translation>
+ <translation>&Pencere</translation>
</message>
<message>
<source>&Minimize</source>
- <translation>Küçült</translation>
+ <translation>&Küçült</translation>
</message>
<message>
<source>&Stop</source>
<source>&Float on Top</source>
<translation>Üstte Sabitle</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Pencere Boyutunu Ayarla</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Bu Videodan &Sonra Durdur</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Uygunsuz içerik içerebilecek videoları gizle</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>&Menü Çubuğunu Aç/Kapat</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>Menü</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>%1 &seviyor musunuz? Değerlendirin!</translation>
<source>Update</source>
<translation>Güncelle</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>ALT tuşuna basarak hala menü çubuğuna erişebilirsiniz</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Bağlantı kısıtlı bir süre için geçerli olacak.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Bu sadece %1'in demo sürümüdür.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Bu, uygulamayı test etmenizi ve çalışıp çalışmadığını görmenizi sağlar.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Tam sürüme geç</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Devam</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>İndiriliyor %1</translation>
<source>Subscribe to %1</source>
<translation>%1 Abone Ol</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>% 1 olarak değiştirildi</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>%1 aboneliğinden çık</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 görüntülenme</translation>
+ <source>Pick a video</source>
+ <translation>Bir video seç</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 of %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Aranıyor...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>%1 Tane Daha Göster</translation>
<translation><a href='%1'>%2</a>'a Hoşgeldiniz</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Giriş yapın</translation>
+ <source>to start watching videos.</source>
+ <translation>ve videoları izlemeye başlayın.</translation>
</message>
<message>
<source>a keyword</source>
<translation>bir anahtar kelime</translation>
</message>
<message>
- <source>a channel</source>
- <translation>bir kanal</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>ve videoları izlemeye başlayın.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>İzle</translation>
+ <source>Enter</source>
+ <translation>Giriş yapın</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Geri</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&İleri</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>%1 Yönlendir</translation>
<translation>%1 indiriliyor...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>%1 için video akışı alınamıyor.</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Dünya Çapında</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>%1 için video akışı alınamıyor.</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Перекласти %1 Вашою рідною мовою за допомогою %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Розробник піктоґрам %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Показати оновлені</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Усі видива</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Наразі оновлень підписок немає.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Очистити</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 переглядів</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Це демонстраційна версія %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Із метою тестування, Ви можете завантажити видиво тривалістю до %1 хв. </translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продовжити</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Отримати повну версію</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 завантажений до %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Згори всіх вікон</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Змінити розмір вікна</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Зу&пинити після цього видиво</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>Приховати відео, які можуть містити небажаний контент</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Подобається %1? Оцініть !</translation>
<source>Update</source>
<translation>Оновлення</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Посилання буде дійсне лише протягом обмеженого часу.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>>Це демонстраційна версія %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ви маєте змогу протестувати проґраму та перевірити працездатність.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Отримати повну версію</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продовжити</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Завантаження %1</translation>
<source>Subscribe to %1</source>
<translation>Підписуватися на %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Відписані від %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 переглядів</translation>
+ <source>Pick a video</source>
+ <translation>Вибрати відео</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 з %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Пошук...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Наступні %1 </translation>
<translation>Ласкаво просимо до <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Уведіть</translation>
+ <source>to start watching videos.</source>
+ <translation>аби розпочати перегляд.</translation>
</message>
<message>
<source>a keyword</source>
<translation>запит</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>аби розпочати перегляд.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Дивитися</translation>
+ <source>Enter</source>
+ <translation>Уведіть</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Н&азад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>&Вперед</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Уперед до %1</translation>
<translation>Завантаження %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Не вдалося отримати видивопотік для %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Світовий</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Не вдалося отримати видиво потік для %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Перекласти %1 Вашою рідною мовою за допомогою %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Розробник піктоґрам %1.</translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>Показати оновлені</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Усі видива</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Наразі оновлень підписок немає.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>У Вас немає підписок. Використовуйте символ зірочки, аби підписуватися на канали.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Очистити</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 переглядів</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Це демонстраційна версія %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Із метою тестування, Ви можете завантажити видиво тривалістю до %1 хв. </translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продовжити</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Отримати повну версію</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 завантажений до %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
+ <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform><numerusform></numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>&Згори всіх вікон</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>&Змінити розмір вікна</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>Зу&пинити після цього видиво</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Подобається %1? Оцініть !</translation>
<source>Update</source>
<translation>Оновлення</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Посилання буде дійсне лише протягом обмеженого часу.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>>Це демонстраційна версія %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Ви маєте змогу протестувати проґраму та перевірити працездатність.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Отримати повну версію</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Продовжити</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Завантаження %1</translation>
<source>Subscribe to %1</source>
<translation>Підписуватися на %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>Відписані від %1</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 переглядів</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 з %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Пошук...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Наступні %1 </translation>
<translation>Ласкаво просимо до <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Уведіть</translation>
+ <source>to start watching videos.</source>
+ <translation>аби розпочати перегляд.</translation>
</message>
<message>
<source>a keyword</source>
<translation>запит</translation>
</message>
<message>
- <source>a channel</source>
- <translation>канал</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>аби розпочати перегляд.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Дивитися</translation>
+ <source>Enter</source>
+ <translation>Уведіть</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>Н&азад</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Уперед до %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Не вдалося отримати видивопотік для %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Світовий</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Не вдалося отримати видивопотік для %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>Chuyển ngữ %1 sang ngôn ngữ của bạn bằng cách sử dụng %2</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>Biểu tượng được thiết kế bởi %1.</translation>
<source>Show Updated</source>
<translation>Hiển thị Cập Nhật</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>Bạn không có phần đăng ký theo dõi nào. Sử dụng biểu tượng ngôi sao để đăng ký theo dõi một kênh nào đó.</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>Tất cả các video</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>Hiện không có phần theo dõi đăng ký nào được cập nhật vào thời điểm này.</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>Bạn không có phần đăng ký theo dõi nào. Sử dụng biểu tượng ngôi sao để đăng ký theo dõi một kênh nào đó.</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>Dọn dẹp phần nội dung</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 lượt xem</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Đây là phiên bản dùng thử của %1.</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>Phần này có thể được tải về các video ngắn hơn %1 phút để bạn có thể thử nghiệm tính năng tải về.</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Tiếp tục</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Nhận phiên bản đầy đủ</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 đã tải xuống trong %2</translation>
<source>&Float on Top</source>
<translation>&Nổi lên trên cùng</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation type="unfinished"/>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>&Dừng phát sau video này</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation type="unfinished"/>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>&Yêu thích %1? Hãy đánh giá!</translation>
<source>Update</source>
<translation>Cập nhật</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>Liên kết này chỉ hợp lệ trong một khoảng thời gian hạn định.</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>Đây chỉ là phần dùng thử của phiên bản %1.</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>Cho phép bạn thử nghiệm ứng dụng và xét độ hiệu quả của ứng dụng đối với bạn.</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>Nhận phiên bản đầy đủ</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>Tiếp tục</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>Đang tải về %1</translation>
<source>Subscribe to %1</source>
<translation>Đăng ký theo dõi đối với %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 lượt xem</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 của %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>Đang tìm kiếm...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>Hiển thị %1 thêm</translation>
<translation>Chào mừng đến với <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>Nhập vào</translation>
+ <source>to start watching videos.</source>
+ <translation>để bắt đầu xem các đoạn video.</translation>
</message>
<message>
<source>a keyword</source>
<translation>một từ khóa</translation>
</message>
<message>
- <source>a channel</source>
- <translation>một kênh</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>để bắt đầu xem các đoạn video.</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>Xem</translation>
+ <source>Enter</source>
+ <translation>Nhập vào</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>&Quay lại</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>Chuyển tới %1</translation>
<translation type="unfinished"/>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>Không thể tiếp nhận luồng video từ %1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>Toàn thế giới</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>Không thể tiếp nhận luồng video từ %1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>使用 %2 将 %1 翻译为您的母语</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>图标设计:%1。</translation>
<source>Show Updated</source>
<translation>显示更新</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>你没有任何订阅,使用星形符号订阅频道</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>所有视频</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>当前没有订阅更新</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>你没有任何订阅,使用星形符号订阅频道</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>清除</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
+ <source>%n month(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>%1 人次观看</translation>
+ </message>
<message numerus="yes">
- <source>%n month(s) ago</source>
+ <source>%n week(s) ago</source>
<translation type="unfinished"><numerusform></numerusform></translation>
</message>
</context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>这只是 %1 的演示版。</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>本版本只能下载 %1 分钟以下的视频,仅用于测试下载功能。</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>继续</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>获取完整版</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 已下载(%2)</translation>
<source>&Float on Top</source>
<translation>窗口置顶(&F)</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>调整窗口大小(&A)</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>该视频后停止播放(&S)</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>隐藏可能含有不恰当内容的视频</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>喜欢 %1? 为其评分!</translation>
<source>Update</source>
<translation>更新</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation type="unfinished"/>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>此链接仅能保持短时间的有效性。</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>这仅是 %1 的演示版。</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>本版本允许您测试,以确认本应用是否适合您。</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>获取完整版</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>继续</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>正在下载 %1</translation>
<source>Subscribe to %1</source>
<translation>订阅 %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>从 %1 退订</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>%1 人次观看</translation>
+ <source>Pick a video</source>
+ <translation type="unfinished"/>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 之 %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>搜索中……</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>再多显示 %1</translation>
<translation>欢迎使用<a href='%1'>%2</a>!</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>输入</translation>
+ <source>to start watching videos.</source>
+ <translation>开始观看视频。</translation>
</message>
<message>
<source>a keyword</source>
<translation>关键字</translation>
</message>
<message>
- <source>a channel</source>
- <translation>频道名称</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>开始观看视频。</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>观看</translation>
+ <source>Enter</source>
+ <translation>输入</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>后退(_B)</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation type="unfinished"/>
+ </message>
<message>
<source>Forward to %1</source>
<translation>前进至 %1</translation>
<translation>正在下载 %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>无法获得视频流。可能原因:%1</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>全球</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>无法获得视频流。可能原因:%1</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<source>Translate %1 to your native language using %2</source>
<translation>使用 %2 翻譯 %1 介面成為您的本國語言</translation>
</message>
+ <message>
+ <source>Powered by %1</source>
+ <translation>威力本源 %1</translation>
+ </message>
+ <message>
+ <source>Open-source software</source>
+ <translation>開放原始碼軟體</translation>
+ </message>
<message>
<source>Icon designed by %1.</source>
<translation>圖示由 %1 所設計。 </translation>
</message>
<message numerus="yes">
<source>You have %n new video(s)</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>您有 %n 個新影片</numerusform></translation>
</message>
</context>
<context>
<source>Show Updated</source>
<translation>顯示更新</translation>
</message>
+ <message>
+ <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
+ <translation>您暫時沒有任何訂閱。使用星星符號訂閱頻道。</translation>
+ </message>
<message>
<source>All Videos</source>
<translation>全部影片</translation>
<source>There are no updated subscriptions at this time.</source>
<translation>目前沒有更新的訂閱。</translation>
</message>
- <message>
- <source>You have no subscriptions. Use the star symbol to subscribe to channels.</source>
- <translation>您暫時沒有任何訂閱。使用星星符號訂閱頻道。</translation>
- </message>
-</context>
-<context>
- <name>ClearButton</name>
- <message>
- <source>Clear</source>
- <translation>清除</translation>
- </message>
</context>
<context>
<name>DataUtils</name>
</message>
<message numerus="yes">
<source>%n hour(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n 小時前</numerusform></translation>
</message>
<message numerus="yes">
<source>%n day(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n 天前</numerusform></translation>
</message>
<message numerus="yes">
- <source>%n weeks(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <source>%n month(s) ago</source>
+ <translation><numerusform>%n 個月前</numerusform></translation>
+ </message>
+ <message>
+ <source>K</source>
+ <comment>K as in Kilo, i.e. thousands</comment>
+ <translation>K</translation>
+ </message>
+ <message>
+ <source>M</source>
+ <comment>M stands for Millions</comment>
+ <translation>M</translation>
+ </message>
+ <message>
+ <source>B</source>
+ <comment>B stands for Billions</comment>
+ <translation>B</translation>
+ </message>
+ <message>
+ <source>%1 views</source>
+ <translation>瀏覽次數:%1 次</translation>
</message>
<message numerus="yes">
- <source>%n month(s) ago</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <source>%n week(s) ago</source>
+ <translation><numerusform>%n 週前</numerusform></translation>
</message>
</context>
<context>
</context>
<context>
<name>DownloadManager</name>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>這僅僅是展示版的 %1。</translation>
- </message>
- <message>
- <source>It can only download videos shorter than %1 minutes so you can test the download functionality.</source>
- <translation>它只能下載影片少於 %1 分鐘,使您可以測試下載功能。</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>繼續</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>取得完整版</translation>
- </message>
<message>
<source>%1 downloaded in %2</source>
<translation>%1 下載在 %2</translation>
</message>
<message numerus="yes">
<source>%n Download(s)</source>
- <translation type="unfinished"><numerusform></numerusform></translation>
+ <translation><numerusform>%n 次下載</numerusform></translation>
</message>
</context>
<context>
<source>&Float on Top</source>
<translation>浮在上面(&F)</translation>
</message>
- <message>
- <source>&Adjust Window Size</source>
- <translation>調整視窗大小 (&A)</translation>
- </message>
<message>
<source>&Stop After This Video</source>
<translation>在這個影片播完之後停止(&S)</translation>
<source>Hide videos that may contain inappropriate content</source>
<translation>隱藏可能包含不適當內容的影片</translation>
</message>
+ <message>
+ <source>Toggle &Menu Bar</source>
+ <translation>切換選單列(&M)</translation>
+ </message>
+ <message>
+ <source>Menu</source>
+ <translation>選單</translation>
+ </message>
<message>
<source>&Love %1? Rate it!</source>
<translation>喜歡 %1 ?為它評分!(&L)</translation>
<source>Update</source>
<translation>更新</translation>
</message>
+ <message>
+ <source>You can still access the menu bar by pressing the ALT key</source>
+ <translation>您還是可以透過按下 ALT 鍵存取選單列</translation>
+ </message>
</context>
<context>
<name>MediaView</name>
<source>The link will be valid only for a limited time.</source>
<translation>這個連結將只在有限的時間內有效。</translation>
</message>
- <message>
- <source>This is just the demo version of %1.</source>
- <translation>這僅僅是展示版的 %1。</translation>
- </message>
- <message>
- <source>It allows you to test the application and see if it works for you.</source>
- <translation>它可以讓您測試應用程式,看它是否適合您。</translation>
- </message>
- <message>
- <source>Get the full version</source>
- <translation>取得完整版</translation>
- </message>
- <message>
- <source>Continue</source>
- <translation>繼續</translation>
- </message>
<message>
<source>Downloading %1</source>
<translation>正在下載 %1</translation>
<source>Subscribe to %1</source>
<translation>訂閱 %1</translation>
</message>
+ <message>
+ <source>Switched to %1</source>
+ <translation>切換至 %1</translation>
+ </message>
<message>
<source>Unsubscribed from %1</source>
<translation>從 %1 取消訂閱</translation>
</message>
</context>
<context>
- <name>PlaylistItemDelegate</name>
+ <name>PickMessage</name>
<message>
- <source>%1 views</source>
- <translation>瀏覽次數:%1 次</translation>
+ <source>Pick a video</source>
+ <translation>挑選影片</translation>
</message>
+</context>
+<context>
+ <name>PlaylistItemDelegate</name>
<message>
<source>%1 of %2 (%3) — %4</source>
<translation>%1 / %2 (%3) — %4</translation>
</context>
<context>
<name>PlaylistModel</name>
- <message>
- <source>Searching...</source>
- <translation>搜尋中...</translation>
- </message>
<message>
<source>Show %1 More</source>
<translation>顯示再多 %1個影片</translation>
<translation>歡迎使用 <a href='%1'>%2</a>,</translation>
</message>
<message>
- <source>Enter</source>
- <extracomment>"Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"</extracomment>
- <translation>輸入</translation>
+ <source>to start watching videos.</source>
+ <translation>以開始觀看影片。</translation>
</message>
<message>
<source>a keyword</source>
<translation>一個關鍵字</translation>
</message>
<message>
- <source>a channel</source>
- <translation>一個頻道</translation>
- </message>
- <message>
- <source>to start watching videos.</source>
- <translation>以開始觀看影片。</translation>
- </message>
- <message>
- <source>Watch</source>
- <translation>觀看</translation>
+ <source>Enter</source>
+ <translation>輸入</translation>
</message>
<message>
<source>Recent keywords</source>
<source>&Back</source>
<translation>後退(&B)</translation>
</message>
+ <message>
+ <source>&Forward</source>
+ <translation>前進(&F)</translation>
+ </message>
<message>
<source>Forward to %1</source>
<translation>向前到 %1</translation>
<translation>正在下載 %1...</translation>
</message>
</context>
-<context>
- <name>Video</name>
- <message>
- <source>Cannot get video stream for %1</source>
- <translation>無法為 %1 獲得影片串流</translation>
- </message>
-</context>
<context>
<name>YTRegions</name>
<message>
<translation>全世界</translation>
</message>
</context>
+<context>
+ <name>YTVideo</name>
+ <message>
+ <source>Cannot get video stream for %1</source>
+ <translation>無法為 %1 取得影片串流</translation>
+ </message>
+</context>
</TS>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
-<application>
- <id type="desktop">minitube.desktop</id>
+<component>
+ <name>Minitube</name>
+ <id>org.tordini.flavio.minitube</id>
<metadata_license>CC0-1.0</metadata_license>
- <license>GPL-3.0+</license>
+ <project_license>GPL-3.0+</project_license>
<summary>YouTube app</summary>
<description>
<p>
- Minitube is a YouTube desktop application.
+ Minitube is a YouTube desktop application. It is written in C++ using the Qt framework.
</p>
</description>
<url type="homepage">http://flavio.tordini.org/minitube</url>
<screenshots>
- <screenshot type="default">http://flavio.tordini.org/files/minitube/minitube-04.jpg</screenshot>
- <screenshot>http://flavio.tordini.org/files/minitube/minitube-03.jpg</screenshot>
- <screenshot>http://flavio.tordini.org/files/minitube/minitube-02.jpg</screenshot>
- <screenshot>http://flavio.tordini.org/files/minitube/minitube-01.jpg</screenshot>
+ <screenshot type="default"><image>http://flavio.tordini.org/files/minitube/minitube-04.jpg</image></screenshot>
+ <screenshot><image>http://flavio.tordini.org/files/minitube/minitube-03.jpg</image></screenshot>
+ <screenshot><image>http://flavio.tordini.org/files/minitube/minitube-02.jpg</image></screenshot>
+ <screenshot><image>http://flavio.tordini.org/files/minitube/minitube-01.jpg</image></screenshot>
</screenshots>
-</application>
+</component>
-CONFIG += release c++11
-CONFIG -= rtti exceptions
+CONFIG += c++14 exceptions_off rtti_off optimize_full
+
TEMPLATE = app
-VERSION = 2.9
+VERSION = 3.1
DEFINES += APP_VERSION="$$VERSION"
APP_NAME = Minitube
APP_UNIX_NAME = minitube
DEFINES += APP_UNIX_NAME="$$APP_UNIX_NAME"
-DEFINES += APP_PHONON
-DEFINES += APP_PHONON_SEEK
DEFINES += APP_SNAPSHOT
message(Building $${APP_NAME} $${VERSION})
QT += widgets network sql qml
+include(lib/http/http.pri)
+include(lib/idle/idle.pri)
+
+DEFINES += MEDIA_MPV
+include(lib/media/media.pri)
+
include(src/qtsingleapplication/qtsingleapplication.pri)
-include(src/http/http.pri)
-include(src/idle/idle.pri)
HEADERS += src/video.h \
- src/searchlineedit.h \
src/spacer.h \
src/constants.h \
src/playlistitemdelegate.h \
src/searchparams.h \
src/minisplitter.h \
src/loadingwidget.h \
- src/videoareawidget.h \
src/autocomplete.h \
src/videodefinition.h \
src/fontutils.h \
src/yt3.h \
src/paginatedvideosource.h \
src/searchwidget.h \
- src/exlineedit.h \
src/channellistview.h \
src/httputils.h \
src/appwidget.h \
src/clickablelabel.h \
src/ytvideo.h \
src/toolbarmenu.h \
- src/sharetoolbar.h
+ src/sharetoolbar.h \
+ src/videoarea.h \
+ src/searchlineedit.h
SOURCES += src/main.cpp \
- src/searchlineedit.cpp \
src/spacer.cpp \
src/video.cpp \
src/videomimedata.cpp \
src/searchparams.cpp \
src/minisplitter.cpp \
src/loadingwidget.cpp \
- src/videoareawidget.cpp \
src/autocomplete.cpp \
src/videodefinition.cpp \
src/constants.cpp \
src/ytchannel.cpp \
src/yt3.cpp \
src/paginatedvideosource.cpp \
- src/exlineedit.cpp \
src/channellistview.cpp \
src/httputils.cpp \
src/appwidget.cpp \
src/clickablelabel.cpp \
src/ytvideo.cpp \
src/toolbarmenu.cpp \
- src/sharetoolbar.cpp
+ src/sharetoolbar.cpp \
+ src/videoarea.cpp \
+ src/searchlineedit.cpp
+
RESOURCES += resources.qrc
+RESOURCES += $$files(icons/*.png, true)
+
DESTDIR = build/target/
OBJECTS_DIR = build/obj/
MOC_DIR = build/moc/
# Tell Qt Linguist that we use UTF-8 strings in our sources
CODECFORTR = UTF-8
CODECFORSRC = UTF-8
+
include(locale/locale.pri)
# deploy
DISTFILES += CHANGES COPYING
unix:!mac {
DEFINES += APP_LINUX
- LIBS += -lphonon4qt5
- INCLUDEPATH += /usr/include/phonon4qt5
QT += dbus
HEADERS += src/gnomeglobalshortcutbackend.h
SOURCES += src/gnomeglobalshortcutbackend.cpp
+
isEmpty(PREFIX):PREFIX = /usr
+
BINDIR = $$PREFIX/bin
INSTALLS += target
target.path = $$BINDIR
+
DATADIR = $$PREFIX/share
PKGDATADIR = $$DATADIR/minitube
DEFINES += DATADIR=\\\"$$DATADIR\\\" \
PKGDATADIR=\\\"$$PKGDATADIR\\\"
+
INSTALLS += translations \
+ sounds \
desktop \
appdata \
iconsvg \
icon512
translations.path = $$PKGDATADIR
translations.files += $$DESTDIR/locale
+ sounds.path = $$PKGDATADIR
+ sounds.files += sounds/
desktop.path = $$DATADIR/applications
desktop.files += minitube.desktop
appdata.path = $$DATADIR/appdata
icon512.path = $$DATADIR/icons/hicolor/512x512/apps
icon512.files += data/512x512/minitube.png
}
+
mac|win32|contains(DEFINES, APP_UBUNTU):include(local/local.pri)
<RCC>
<qresource prefix="/">
<file>images/app.png</file>
- <file>images/refine-search.png</file>
- <file>images/search-time.png</file>
- <file>images/search-sortBy.png</file>
- <file>images/search-quality.png</file>
- <file>images/search-duration.png</file>
<file>flags/dz.png</file>
<file>flags/ar.png</file>
<file>flags/au.png</file>
<file>flags/gb.png</file>
<file>flags/ye.png</file>
<file>style.css</file>
- <file>images/worldwide.png</file>
- <file>images/show-updated.png</file>
- <file>images/sort.png</file>
- <file>images/mark-watched.png</file>
- <file>images/channels.png</file>
- <file>images/unwatched.png</file>
- <file>sounds/snapshot.wav</file>
<file>images/app@2x.png</file>
- <file>images/sort@2x.png</file>
- <file>images/unwatched@2x.png</file>
- <file>images/channels@2x.png</file>
- <file>images/mark-watched@2x.png</file>
- <file>images/show-updated@2x.png</file>
- <file>images/worldwide@2x.png</file>
- <file>images/refine-search@2x.png</file>
- <file>images/search-duration@2x.png</file>
- <file>images/search-quality@2x.png</file>
- <file>images/search-time@2x.png</file>
- <file>images/search-sortBy@2x.png</file>
- <file>images/audio-volume-high.png</file>
- <file>images/audio-volume-high@2x.png</file>
- <file>images/audio-volume-muted.png</file>
- <file>images/audio-volume-muted@2x.png</file>
- <file>images/bookmark-new.png</file>
- <file>images/bookmark-new@2x.png</file>
- <file>images/bookmark-new_active.png</file>
- <file>images/bookmark-new_active@2x.png</file>
- <file>images/bookmark-remove.png</file>
- <file>images/bookmark-remove@2x.png</file>
- <file>images/content-loading.png</file>
- <file>images/content-loading@2x.png</file>
- <file>images/document-save.png</file>
- <file>images/document-save@2x.png</file>
- <file>images/edit-clear.png</file>
- <file>images/edit-find.png</file>
- <file>images/go-next.png</file>
- <file>images/go-next@2x.png</file>
- <file>images/go-next_active.png</file>
- <file>images/go-next_active@2x.png</file>
- <file>images/go-previous.png</file>
- <file>images/go-previous@2x.png</file>
- <file>images/go-previous_active.png</file>
- <file>images/go-previous_active@2x.png</file>
- <file>images/go-top.png</file>
- <file>images/go-top@2x.png</file>
- <file>images/media-playback-pause.png</file>
- <file>images/media-playback-pause@2x.png</file>
- <file>images/media-playback-start.png</file>
- <file>images/media-playback-start@2x.png</file>
- <file>images/media-playback-stop.png</file>
- <file>images/media-playback-stop@2x.png</file>
- <file>images/media-skip-forward.png</file>
- <file>images/media-skip-forward@2x.png</file>
- <file>images/system-search.png</file>
- <file>images/system-search_active.png</file>
- <file>images/system-search_selected.png</file>
- <file>images/video-display.png</file>
- <file>images/video-display@2x.png</file>
- <file>images/view-fullscreen.png</file>
- <file>images/view-fullscreen@2x.png</file>
- <file>images/view-list.png</file>
- <file>images/view-list@2x.png</file>
- <file>images/view-refresh.png</file>
- <file>images/view-refresh_active.png</file>
- <file>images/view-refresh_selected.png</file>
- <file>images/view-restore.png</file>
- <file>images/view-restore@2x.png</file>
- <file>images/window-close.png</file>
- <file>images/window-close_active.png</file>
- <file>images/window-close_selected.png</file>
<file>images/64x64/app.png</file>
<file>images/64x64/app@2x.png</file>
- <file>images/safesearch.png</file>
- <file>images/safesearch@2x.png</file>
- <file>images/view-more.png</file>
- <file>images/view-more@2x.png</file>
- <file>images/email.png</file>
- <file>images/email@2x.png</file>
- <file>images/facebook.png</file>
- <file>images/facebook@2x.png</file>
- <file>images/link.png</file>
- <file>images/link@2x.png</file>
- <file>images/twitter.png</file>
- <file>images/twitter@2x.png</file>
</qresource>
</RCC>
#include "activation.h"
#endif
#ifdef APP_MAC
-#include "macutils.h"
#include "mac_startup.h"
+#include "macutils.h"
#endif
-#include "fontutils.h"
-#include "iconutils.h"
#include "appwidget.h"
#include "clickablelabel.h"
+#include "fontutils.h"
+#include "iconutils.h"
#include "mainwindow.h"
AboutView::AboutView(QWidget *parent) : View(parent) {
-
const int padding = 30;
const char *buildYear = __DATE__ + 7;
- // speedup painting since we'll paint the whole background
- // by ourselves anyway in paintEvent()
- setAttribute(Qt::WA_OpaquePaintEvent);
+ setBackgroundRole(QPalette::Base);
+ setForegroundRole(QPalette::Text);
+ setAutoFillBackground(true);
QBoxLayout *verticalLayout = new QVBoxLayout(this);
verticalLayout->setMargin(0);
aboutlayout->setMargin(padding);
aboutlayout->setSpacing(padding);
- logo = new ClickableLabel();
- logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
+ ClickableLabel *logo = new ClickableLabel();
+ auto setLogoPixmap = [logo] {
+ logo->setPixmap(IconUtils::pixmap(":/images/app.png", logo->devicePixelRatioF()));
+ };
+ setLogoPixmap();
+ connect(window()->windowHandle(), &QWindow::screenChanged, this, setLogoPixmap);
+
connect(logo, &ClickableLabel::clicked, MainWindow::instance(), &MainWindow::visitSite);
aboutlayout->addWidget(logo, 0, Qt::AlignTop);
layout->setSpacing(padding);
aboutlayout->addLayout(layout);
- QString css = "a { color: palette(text); text-decoration: none; font-weight: bold } h1 { font-weight: 300 }";
+ QColor lightTextColor = palette().text().color();
+#ifdef APP_MAC
+ lightTextColor.setAlphaF(.75);
+#endif
+#ifdef APP_MAC
+ QColor linkColor = mac::accentColor();
+#else
+ QColor linkColor = palette().highlight().color();
+#endif
- QString info = "<html><style>" + css + "</style><body>"
- "<h1>" + QString(Constants::NAME) + "</h1>"
- "<p>" + tr("There's life outside the browser!") + "</p>"
- "<p>" + tr("Version %1").arg(Constants::VERSION) + "</p>"
- + QString("<p><a href=\"%1/\">%1</a></p>").arg(Constants::WEBSITE);
+ QString info = "<html><style>"
+ "body { color: " +
+ lightTextColor.name(QColor::HexArgb) +
+ "; } "
+ "h1 { color: palette(text); font-weight: 100; } "
+ "a { color: " +
+ linkColor.name(QColor::HexArgb) +
+ "; text-decoration: none; font-weight: normal; }"
+ "</style><body>";
+
+ info += "<h1>" + QString(Constants::NAME) +
+ "</h1>"
+ "<p>" +
+ tr("There's life outside the browser!") +
+ "</p>"
+ "<p>" +
+ tr("Version %1").arg(Constants::VERSION) + "</p>" +
+ QString("<p><a href=\"%1/\">%1</a></p>").arg(Constants::WEBSITE);
#ifdef APP_ACTIVATION
QString email = Activation::instance().getEmail();
- if (!email.isEmpty())
- info += "<p>" + tr("Licensed to: %1").arg("<b>" + email + "</b>");
+ if (!email.isEmpty()) info += "<p>" + tr("Licensed to: %1").arg("<b>" + email + "</b>");
#endif
#ifndef APP_EXTRA
- info += "<p>" + tr("%1 is Free Software but its development takes precious time.").arg(Constants::NAME) + "<br/>"
- + tr("Please <a href='%1'>donate</a> to support the continued development of %2.")
- .arg(QString(Constants::WEBSITE).append("#donate"), Constants::NAME) + "</p>";
+ info += "<p>" +
+ tr("%1 is Free Software but its development takes precious time.")
+ .arg(Constants::NAME) +
+ "<br/>" +
+ tr("Please <a href='%1'>donate</a> to support the continued development of %2.")
+ .arg(QString(Constants::WEBSITE).append("#donate"), Constants::NAME) +
+ "</p>";
#endif
- info += "<p>" + tr("Translate %1 to your native language using %2").arg(Constants::NAME)
- .arg("<a href='http://www.transifex.net/projects/p/" + QString(Constants::UNIX_NAME) + "/'>Transifex</a>")
- + "</p>"
+ info += "<p>" +
+ tr("Translate %1 to your native language using %2")
+ .arg(Constants::NAME)
+ .arg("<a href='http://www.transifex.net/projects/p/" +
+ QString(Constants::UNIX_NAME) + "/'>Transifex</a>") +
+ "</p>";
+
+ info += "<p>" +
+ tr("Powered by %1")
+ .arg("<a href='https://" + QLatin1String(Constants::ORG_DOMAIN) +
+ "/opensource'>" + tr("Open-source software") + "</a>") +
+ "</p>";
- "<p>"
- + tr("Icon designed by %1.").arg("<a href='http://www.kolorguild.co.za/'>David Nel</a>")
- + "</p>"
+ info += "<p>" +
+ tr("Icon designed by %1.").arg("<a href='http://www.kolorguild.co.za/'>David Nel</a>") +
+ "</p>"
- #ifndef APP_EXTRA
- "<p>" + tr("Released under the <a href='%1'>GNU General Public License</a>")
- .arg("http://www.gnu.org/licenses/gpl.html") + "</p>"
- #endif
- "<p>© " + buildYear + " " + Constants::ORG_NAME + "</p>"
+#ifndef APP_EXTRA
+ "<p>" +
+ tr("Released under the <a href='%1'>GNU General Public License</a>")
+ .arg("http://www.gnu.org/licenses/gpl.html") +
+ "</p>"
+#endif
+ "<p>© " +
+ buildYear + " " + Constants::ORG_NAME +
+ "</p>"
"</body></html>";
QLabel *infoLabel = new QLabel(info, this);
closeButton->setDefault(true);
closeButton->setFocus();
- connect(closeButton, SIGNAL(clicked()), parent, SLOT(goBack()));
+ connect(closeButton, SIGNAL(clicked()), MainWindow::instance(), SLOT(goBack()));
buttonLayout->addWidget(closeButton);
layout->addLayout(buttonLayout);
void AboutView::appear() {
closeButton->setFocus();
- connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection);
-}
-
-void AboutView::paintEvent(QPaintEvent *event) {
- QWidget::paintEvent(event);
- QBrush brush;
- if (window()->isActiveWindow()) {
- brush = Qt::white;
- } else {
- brush = palette().window();
- }
- QPainter painter(this);
- painter.fillRect(0, 0, width(), height(), brush);
- painter.end();
-}
-
-void AboutView::screenChanged() {
- logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
}
#include "constants.h"
#include "view.h"
-class ClickableLabel;
-
class AboutView : public View {
Q_OBJECT
return s;
}
-protected:
- void paintEvent(QPaintEvent *e);
-
-private slots:
- void screenChanged();
-
private:
- ClickableLabel *logo;
QPushButton *closeButton;
};
#endif
AppWidget::AppWidget(const QString &name, const QString &code, QWidget *parent) : QWidget(parent), icon(0), name(name), downloadButton(0) {
const QString unixName = code.left(code.lastIndexOf('.'));
- const QString baseUrl = QLatin1String("http://") + Constants::ORG_DOMAIN;
+ const QString baseUrl = QLatin1String("https://") + Constants::ORG_DOMAIN;
const QString filesUrl = baseUrl + QLatin1String("/files/");
url = filesUrl + unixName + QLatin1String("/") + code;
webPage = baseUrl + QLatin1String("/") + unixName;
#ifndef QT_NO_DEBUG_OUTPUT
/// Gives human-readable event type information.
-QDebug operator<<(QDebug str, const QEvent * ev) {
+QDebug operator<<(QDebug str, const QEvent *ev) {
static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type");
str << "QEvent";
if (ev) {
QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
- if (!name.isEmpty()) str << name; else str << ev->type();
+ if (!name.isEmpty())
+ str << name;
+ else
+ str << ev->type();
} else {
- str << (void*)ev;
+ str << (void *)ev;
}
return str.maybeSpace();
}
#endif
-AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit):
- QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
-
+AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit)
+ : QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0),
+ itemHovering(false) {
popup = new QListWidget();
popup->setWindowFlags(Qt::Popup);
popup->setFocusProxy(buddy->toWidget());
popup->setWindowOpacity(.9);
popup->setProperty("suggest", true);
- connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(acceptSuggestion()));
- connect(popup, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
- SLOT(currentItemChanged(QListWidgetItem*)));
- connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(itemEntered(QListWidgetItem *)));
+ connect(popup, SIGNAL(itemClicked(QListWidgetItem *)), SLOT(acceptSuggestion()));
+ connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
+ SLOT(currentItemChanged(QListWidgetItem *)));
+ connect(popup, SIGNAL(itemEntered(QListWidgetItem *)), SLOT(itemEntered(QListWidgetItem *)));
timer = new QTimer(this);
timer->setSingleShot(true);
}
bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
-
if (obj != popup) {
switch (ev->type()) {
case QEvent::Move:
if (ev->type() == QEvent::KeyPress) {
bool consumed = false;
- QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
// qWarning() << keyEvent->text();
switch (keyEvent->key()) {
case Qt::Key_Enter:
QListWidgetItem *item = new QListWidgetItem(popup);
Suggestion *s = suggestions[i];
item->setText(s->value);
- if (!s->type.isEmpty())
- item->setIcon(QIcon(":/images/" + s->type + ".png"));
+ if (!s->type.isEmpty()) item->setIcon(QIcon(":/images/" + s->type + ".png"));
}
- popup->setCurrentItem(0);
+ popup->setCurrentItem(nullptr);
int h = popup->frameWidth() * 2;
for (int i = 0; i < suggestions.count(); ++i)
h += popup->sizeHintForRow(i);
void AutoComplete::acceptSuggestion() {
int index = popup->currentIndex().row();
if (index >= 0 && index < suggestions.size()) {
- Suggestion* suggestion = suggestions.at(index);
+ Suggestion *suggestion = suggestions.at(index);
buddy->setText(suggestion->value);
emit suggestionAccepted(suggestion);
emit suggestionAccepted(suggestion->value);
originalText.clear();
hideSuggestions();
- } else qWarning() << "No suggestion for index" << index;
+ } else
+ qWarning() << "No suggestion for index" << index;
}
void AutoComplete::preventSuggest() {
enabled = true;
}
-void AutoComplete::setSuggester(Suggester* suggester) {
+void AutoComplete::setSuggester(Suggester *suggester) {
if (this->suggester) this->suggester->disconnect();
this->suggester = suggester;
- connect(suggester, SIGNAL(ready(QVector<Suggestion*>)), SLOT(suggestionsReady(QVector<Suggestion*>)));
+ connect(suggester, SIGNAL(ready(QVector<Suggestion *>)),
+ SLOT(suggestionsReady(QVector<Suggestion *>)));
}
void AutoComplete::suggest() {
if (!enabled) return;
- popup->setCurrentItem(0);
+ popup->setCurrentItem(nullptr);
popup->clearSelection();
originalText = buddy->text();
$END_LICENSE */
#include "channelitemdelegate.h"
+#include "channelaggregator.h"
#include "channelmodel.h"
-#include "ytchannel.h"
#include "fontutils.h"
-#include "channelaggregator.h"
-#include "painterutils.h"
#include "iconutils.h"
+#include "painterutils.h"
+#include "ytchannel.h"
static const int ITEM_WIDTH = 150;
static const int ITEM_HEIGHT = 150;
static const int THUMB_WIDTH = 88;
static const int THUMB_HEIGHT = 88;
-ChannelItemDelegate::ChannelItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
+ChannelItemDelegate::ChannelItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
-}
-
-QSize ChannelItemDelegate::sizeHint(const QStyleOptionViewItem& /*option*/,
- const QModelIndex& /*index*/ ) const {
+QSize ChannelItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
+ const QModelIndex & /*index*/) const {
return QSize(ITEM_WIDTH, ITEM_HEIGHT);
}
-void ChannelItemDelegate::paint( QPainter* painter,
- const QStyleOptionViewItem& option,
- const QModelIndex& index ) const {
+void ChannelItemDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
const int itemType = index.data(ChannelModel::ItemTypeRole).toInt();
if (itemType == ChannelModel::ItemChannel)
paintChannel(painter, option, index);
QStyledItemDelegate::paint(painter, option, index);
}
-void ChannelItemDelegate::paintAggregate(QPainter* painter,
- const QStyleOptionViewItem& option,
- const QModelIndex& index) const {
+void ChannelItemDelegate::paintAggregate(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
Q_UNUSED(index);
painter->save();
-
painter->translate(option.rect.topLeft());
const QRect line(0, 0, option.rect.width(), option.rect.height());
- static const QPixmap thumbnail = IconUtils::pixmap(":/images/channels.png");
+ static QPixmap thumbnail = IconUtils::icon("channels").pixmap(88, 88);
+ connect(qApp, &QGuiApplication::paletteChanged, this,
+ [] { thumbnail = IconUtils::icon("channels").pixmap(88, 88); });
QString name = tr("All Videos");
-
drawItem(painter, line, thumbnail, name);
-
painter->restore();
}
-void ChannelItemDelegate::paintUnwatched(QPainter* painter,
- const QStyleOptionViewItem& option,
- const QModelIndex& index) const {
+void ChannelItemDelegate::paintUnwatched(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
Q_UNUSED(index);
painter->save();
painter->translate(option.rect.topLeft());
const QRect line(0, 0, option.rect.width(), option.rect.height());
- static const QPixmap thumbnail = IconUtils::pixmap(":/images/unwatched.png");
+ static QPixmap thumbnail = IconUtils::icon("unwatched").pixmap(88, 88);
+ connect(qApp, &QGuiApplication::paletteChanged, this,
+ [] { thumbnail = IconUtils::icon("unwatched").pixmap(88, 88); });
QString name = tr("Unwatched Videos");
-
drawItem(painter, line, thumbnail, name);
int notifyCount = ChannelAggregator::instance()->getUnwatchedCount();
painter->restore();
}
-void ChannelItemDelegate::paintChannel(QPainter* painter,
- const QStyleOptionViewItem& option,
- const QModelIndex& index) const {
+void ChannelItemDelegate::paintChannel(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
YTChannel *channel = index.data(ChannelModel::DataObjectRole).value<YTChannelPointer>().data();
if (!channel) return;
drawItem(painter, line, thumbnail, name);
int notifyCount = channel->getNotifyCount();
- if (notifyCount > 0)
- paintBadge(painter, line, QString::number(notifyCount));
+ if (notifyCount > 0) paintBadge(painter, line, QString::number(notifyCount));
painter->restore();
}
void ChannelItemDelegate::drawItem(QPainter *painter,
- const QRect &line,
- const QPixmap &thumbnail,
- const QString &name) const {
+ const QRect &line,
+ const QPixmap &thumbnail,
+ const QString &name) const {
painter->drawPixmap((line.width() - THUMB_WIDTH) / 2, 8, thumbnail);
QRect nameBox = line;
bool tooBig = false;
QRect textBox = painter->boundingRect(nameBox,
- Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap,
- name);
+ Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap, name);
if (textBox.height() > nameBox.height() || textBox.width() > nameBox.width()) {
painter->setFont(FontUtils::small());
- textBox = painter->boundingRect(nameBox,
- Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap,
+ textBox = painter->boundingRect(nameBox, Qt::AlignTop | Qt::AlignHCenter | Qt::TextWordWrap,
name);
if (textBox.height() > nameBox.height()) {
painter->setClipRect(nameBox);
}
void ChannelItemDelegate::paintBadge(QPainter *painter,
- const QRect &line,
- const QString &text) const {
+ const QRect &line,
+ const QString &text) const {
const int topLeft = (line.width() + THUMB_WIDTH) / 2;
painter->save();
painter->translate(topLeft, 0);
painter->setClipping(false);
- PainterUtils::paintBadge(painter, text, true);
+ PainterUtils::paintBadge(painter, text, true, QColor(230, 36, 41), true);
painter->restore();
}
#include "painterutils.h"
ChannelListView::ChannelListView() {
-
setSelectionMode(QAbstractItemView::NoSelection);
// layout
setFrameShape(QFrame::NoFrame);
setAttribute(Qt::WA_MacShowFocusRect, false);
- QPalette p = palette();
- /*
- p.setColor(QPalette::Base, p.window().color());
- p.setColor(QPalette::Text, p.windowText().color());
- */
- p.setColor(QPalette::Disabled, QPalette::Base, p.base().color());
- p.setColor(QPalette::Disabled, QPalette::Text, p.text().color());
- setPalette(p);
-
verticalScrollBar()->setPageStep(3);
verticalScrollBar()->setSingleStep(1);
setMouseTracking(true);
-
}
void ChannelListView::mousePressEvent(QMouseEvent *event) {
void ChannelListView::mouseMoveEvent(QMouseEvent *event) {
QWidget::mouseMoveEvent(event);
const QModelIndex index = indexAt(event->pos());
- if (index.isValid()) setCursor(Qt::PointingHandCursor);
- else unsetCursor();
+ if (index.isValid())
+ setCursor(Qt::PointingHandCursor);
+ else
+ unsetCursor();
}
void ChannelListView::paintEvent(QPaintEvent *event) {
#include <QtWidgets>
class ChannelListView : public QListView {
-
Q_OBJECT
public:
private:
QString errorMessage;
-
};
#endif // CHANNELLISTVIEW_H
$END_LICENSE */
#include "channelview.h"
-#include "ytchannel.h"
-#include "ytsearch.h"
-#include "searchparams.h"
-#include "channelmodel.h"
+#include "aggregatevideosource.h"
+#include "channelaggregator.h"
#include "channelitemdelegate.h"
+#include "channelmodel.h"
#include "database.h"
-#include "channelaggregator.h"
-#include "aggregatevideosource.h"
-#include "mainwindow.h"
#include "iconutils.h"
+#include "mainwindow.h"
+#include "searchparams.h"
+#include "ytchannel.h"
+#include "ytsearch.h"
#ifdef APP_EXTRA
#include "extra.h"
#endif
#include "channellistview.h"
namespace {
-static const QString sortByKey = "subscriptionsSortBy";
-static const QString showUpdatedKey = "subscriptionsShowUpdated";
-}
-
-ChannelView::ChannelView(QWidget *parent) : View(parent),
- showUpdated(false),
- sortBy(SortByName) {
+const QString sortByKey = "subscriptionsSortBy";
+const QString showUpdatedKey = "subscriptionsShowUpdated";
+} // namespace
+ChannelView::ChannelView(QWidget *parent) : View(parent), showUpdated(false), sortBy(SortByName) {
QBoxLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
layout->setSpacing(0);
channelsModel = new ChannelModel(this);
listView->setModel(channelsModel);
- connect(listView, SIGNAL(clicked(const QModelIndex &)), SLOT(itemActivated(const QModelIndex &)));
+ connect(listView, SIGNAL(clicked(const QModelIndex &)),
+ SLOT(itemActivated(const QModelIndex &)));
connect(listView, SIGNAL(contextMenu(QPoint)), SLOT(showContextMenu(QPoint)));
connect(listView, SIGNAL(viewportEntered()), channelsModel, SLOT(clearHover()));
setupActions();
- connect(ChannelAggregator::instance(), SIGNAL(channelChanged(YTChannel*)),
- channelsModel, SLOT(updateChannel(YTChannel*)));
+ connect(ChannelAggregator::instance(), SIGNAL(channelChanged(YTChannel *)), channelsModel,
+ SLOT(updateChannel(YTChannel *)));
connect(ChannelAggregator::instance(), SIGNAL(unwatchedCountChanged(int)),
SLOT(unwatchedCountChanged(int)));
QToolButton *sortButton = new QToolButton(this);
sortButton->setText(tr("Sort by"));
- sortButton->setIcon(IconUtils::icon("sort"));
+ IconUtils::setIcon(sortButton, "sort");
sortButton->setIconSize(QSize(16, 16));
sortButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
sortButton->setPopupMode(QToolButton::InstantPopup);
widgetAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O));
statusActions << widgetAction;
- markAsWatchedAction = new QAction(
- IconUtils::icon("mark-watched"), tr("Mark all as watched"), this);
+ markAsWatchedAction = new QAction(tr("Mark all as watched"), this);
+ IconUtils::setIcon(markAsWatchedAction, "mark-watched");
markAsWatchedAction->setEnabled(false);
markAsWatchedAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W));
connect(markAsWatchedAction, SIGNAL(triggered()), SLOT(markAllAsWatched()));
statusActions << markAsWatchedAction;
showUpdated = settings.value(showUpdatedKey, false).toBool();
- QAction *showUpdatedAction = new QAction(
- IconUtils::icon("show-updated"), tr("Show Updated"), this);
+ QAction *showUpdatedAction = new QAction(tr("Show Updated"), this);
+ IconUtils::setIcon(showUpdatedAction, "show-updated");
showUpdatedAction->setCheckable(true);
showUpdatedAction->setChecked(showUpdated);
showUpdatedAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U));
for (QAction *action : statusActions) {
window()->addAction(action);
- IconUtils::setupAction(action);
+ MainWindow::instance()->setupAction(action);
}
}
QString ChannelView::noSubscriptionsMessage() {
return tr("You have no subscriptions. "
- "Use the star symbol to subscribe to channels.");
+ "Use the star symbol to subscribe to channels.");
}
void ChannelView::appear() {
updateQuery();
- for (QAction* action : statusActions)
- MainWindow::instance()->showActionInStatusBar(action, true);
+ MainWindow::instance()->showActionsInStatusBar(statusActions, true);
setFocus();
ChannelAggregator::instance()->start();
}
void ChannelView::disappear() {
- for (QAction* action : statusActions)
- MainWindow::instance()->showActionInStatusBar(action, false);
+ MainWindow::instance()->showActionsInStatusBar(statusActions, false);
}
void ChannelView::itemActivated(const QModelIndex &index) {
QMenu menu;
if (channel->getNotifyCount() > 0) {
- QAction *markAsWatchedAction = menu.addAction(tr("Mark as Watched"), channel, SLOT(updateWatched()));
- connect(markAsWatchedAction, SIGNAL(triggered()),
- ChannelAggregator::instance(), SLOT(updateUnwatchedCount()));
+ QAction *markAsWatchedAction =
+ menu.addAction(tr("Mark as Watched"), channel, SLOT(updateWatched()));
+ connect(markAsWatchedAction, SIGNAL(triggered()), ChannelAggregator::instance(),
+ SLOT(updateUnwatchedCount()));
menu.addSeparator();
}
/*
// TODO
- QAction *notificationsAction = menu.addAction(tr("Receive Notifications"), user, SLOT(unsubscribe()));
- notificationsAction->setCheckable(true);
+ QAction *notificationsAction = menu.addAction(tr("Receive Notifications"), user,
+ SLOT(unsubscribe())); notificationsAction->setCheckable(true);
notificationsAction->setChecked(true);
*/
QAction *unsubscribeAction = menu.addAction(tr("Unsubscribe"), channel, SLOT(unsubscribe()));
- connect(unsubscribeAction, SIGNAL(triggered()),
- ChannelAggregator::instance(), SLOT(updateUnwatchedCount()));
+ connect(unsubscribeAction, SIGNAL(triggered()), ChannelAggregator::instance(),
+ SLOT(updateUnwatchedCount()));
menu.exec(mapToGlobal(point));
}
}
QString sql = "select user_id from subscriptions";
- if (showUpdated)
- sql += " where notify_count>0";
+ if (showUpdated) sql += " where notify_count>0";
switch (sortBy) {
case SortByUpdated:
}
#ifdef APP_EXTRA
- if (transition)
- Extra::fadeInWidget(this, this);
+ if (transition) Extra::fadeInWidget(this, this);
#endif
channelsModel->setQuery(sql, Database::instance().getConnection());
class ChannelListView;
class ChannelView : public View {
-
Q_OBJECT
public:
- ChannelView(QWidget *parent = 0);
-
+ ChannelView(QWidget *parent = nullptr);
+
signals:
void activated(VideoSource *videoSource);
ChannelListView *listView;
ChannelModel *channelsModel;
- QVector<QAction*> statusActions;
+ QVector<QAction *> statusActions;
bool showUpdated;
SortBy sortBy;
QAction *markAsWatchedAction;
-
};
#endif // CHANNELSVIEW_H
setCursor(Qt::PointingHandCursor);
}
+ClickableLabel::ClickableLabel(const QString &text, QWidget *parent) : QLabel(text, parent) {
+ setCursor(Qt::PointingHandCursor);
+}
+
void ClickableLabel::mouseReleaseEvent(QMouseEvent *e) {
- if (rect().contains(e->pos())) emit clicked();
+ if (e->button() == Qt::LeftButton && rect().contains(e->pos())) emit clicked();
+}
+
+void ClickableLabel::leaveEvent(QEvent *e) {
+ emit hovered(false);
+ QLabel::leaveEvent(e);
+}
+
+void ClickableLabel::enterEvent(QEvent *e) {
+ emit hovered(true);
+ QLabel::enterEvent(e);
}
#include <QtWidgets>
class ClickableLabel : public QLabel {
-
Q_OBJECT
public:
- explicit ClickableLabel(QWidget *parent = 0);
+ explicit ClickableLabel(QWidget *parent = nullptr);
+ explicit ClickableLabel(const QString &text, QWidget *parent = nullptr);
signals:
void clicked();
+ void hovered(bool value);
protected:
void mouseReleaseEvent(QMouseEvent *e);
-
+ void enterEvent(QEvent *e);
+ void leaveEvent(QEvent *e);
};
#endif // CLICKABLELABEL_H
const char *Constants::UNIX_NAME = STRINGIFY(APP_UNIX_NAME);
const char *Constants::ORG_NAME = "Flavio Tordini";
const char *Constants::ORG_DOMAIN = "flavio.tordini.org";
-const char *Constants::WEBSITE = "http://flavio.tordini.org/minitube";
+const char *Constants::WEBSITE = "https://flavio.tordini.org/minitube";
const char *Constants::EMAIL = "flavio.tordini@gmail.com";
s = QCoreApplication::translate("DataUtils", "%n day(s) ago", Q_NULLPTR, n);
} else if (seconds < (f = 60 * 60 * 24 * 30)) {
int n = seconds / (60 * 60 * 24 * 7);
- s = QCoreApplication::translate("DataUtils", "%n weeks(s) ago", Q_NULLPTR, n);
+ s = QCoreApplication::translate("DataUtils", "%n week(s) ago", Q_NULLPTR, n);
} else if (seconds < (f = 60 * 60 * 24 * 365)) {
int n = seconds / (60 * 60 * 24 * 30);
s = QCoreApplication::translate("DataUtils", "%n month(s) ago", Q_NULLPTR, n);
if (hours == 0) return res.sprintf("%d:%02d", minutes, seconds);
return res.sprintf("%d:%02d:%02d", hours, minutes, seconds);
}
+
+QString DataUtils::formatCount(int c) {
+ QString s;
+ int f = 1;
+ if (c < 1) {
+ return s;
+ } else if (c < (f *= 1000)) {
+ s = QString::number(c);
+ } else if (c < (f *= 1000)) {
+ int n = c / 1000;
+ s = QString::number(n) +
+ QCoreApplication::translate("DataUtils", "K", "K as in Kilo, i.e. thousands");
+ } else if (c < (f *= 1000)) {
+ int n = c / (1000 * 1000);
+ s = QString::number(n) +
+ QCoreApplication::translate("DataUtils", "M", "M stands for Millions");
+ } else {
+ int n = c / (1000 * 1000 * 1000);
+ s = QString::number(n) +
+ QCoreApplication::translate("DataUtils", "B", "B stands for Billions");
+ }
+
+ return QCoreApplication::translate("DataUtils", "%1 views").arg(s);
+}
#include <QtCore>
class DataUtils {
-
public:
static QString stringToFilename(const QString &s);
static QString regioneCode(const QLocale &locale);
static uint parseIsoPeriod(const QString &isoPeriod);
static QString formatDateTime(const QDateTime &dt);
static QString formatDuration(uint secs);
+ static QString formatCount(int c);
private:
- DataUtils() { }
-
+ DataUtils() {}
};
#endif // DATAUTILS_H
+++ /dev/null
-#include "exlineedit.h"
-#include "iconutils.h"
-
-ClearButton::ClearButton(QWidget *parent) : QAbstractButton(parent), hovered(false), mousePressed(false) {
- setCursor(Qt::ArrowCursor);
- setToolTip(tr("Clear"));
- setVisible(false);
- setFocusPolicy(Qt::NoFocus);
-}
-
-void ClearButton::paintEvent(QPaintEvent *e) {
- Q_UNUSED(e);
- QPainter painter(this);
- const int h = height();
- int iconSize = 16;
- if (h > 30) iconSize = 22;
- QIcon::Mode iconMode = QIcon::Normal;
- if (mousePressed) iconMode = QIcon::Active;
- QPixmap p = IconUtils::icon("edit-clear").pixmap(iconSize, iconSize, iconMode);
- int x = (width() - p.width()) / 2;
- int y = (h - p.height()) / 2;
- painter.drawPixmap(x, y, p);
-}
-
-void ClearButton::textChanged(const QString &text) {
- setVisible(!text.isEmpty());
-}
-
-void ClearButton::enterEvent(QEvent *e) {
- hovered = true;
- QAbstractButton::enterEvent(e);
-}
-
-void ClearButton::leaveEvent(QEvent *e) {
- hovered = false;
- QAbstractButton::leaveEvent(e);
-}
-
-void ClearButton::mousePressEvent(QMouseEvent *e) {
- mousePressed = true;
- QAbstractButton::mousePressEvent(e);
-}
-
-void ClearButton::mouseReleaseEvent(QMouseEvent *e) {
- mousePressed = false;
- QAbstractButton::mouseReleaseEvent(e);
-}
-
-ExLineEdit::ExLineEdit(QWidget *parent)
- : QWidget(parent)
- , m_leftWidget(0)
- , m_lineEdit(new QLineEdit(this))
- , m_clearButton(new ClearButton(this)) {
- setFocusPolicy(m_lineEdit->focusPolicy());
- setAttribute(Qt::WA_InputMethodEnabled);
- setSizePolicy(m_lineEdit->sizePolicy());
- setBackgroundRole(m_lineEdit->backgroundRole());
- setMouseTracking(true);
- setAcceptDrops(true);
- setAttribute(Qt::WA_MacShowFocusRect, true);
- QPalette p = m_lineEdit->palette();
- setPalette(p);
-
- // line edit
- m_lineEdit->setFrame(false);
- m_lineEdit->setFocusProxy(this);
- m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
- m_lineEdit->setStyleSheet("background:transparent");
- QPalette clearPalette = m_lineEdit->palette();
- clearPalette.setBrush(QPalette::Base, QBrush(Qt::transparent));
- m_lineEdit->setPalette(clearPalette);
-
- // clearButton
- connect(m_clearButton, SIGNAL(clicked()), m_lineEdit, SLOT(clear()));
- connect(m_lineEdit, SIGNAL(textChanged(const QString&)), m_clearButton, SLOT(textChanged(const QString&)));
-}
-
-void ExLineEdit::setFont(const QFont &font) {
- m_lineEdit->setFont(font);
- updateGeometries();
-}
-
-void ExLineEdit::setLeftWidget(QWidget *widget) {
- m_leftWidget = widget;
-}
-
-QWidget *ExLineEdit::leftWidget() const {
- return m_leftWidget;
-}
-
-void ExLineEdit::clear() {
- m_lineEdit->clear();
-}
-
-QString ExLineEdit::text() {
- return m_lineEdit->text();
-}
-
-void ExLineEdit::resizeEvent(QResizeEvent *e) {
- Q_ASSERT(m_leftWidget);
- updateGeometries();
- QWidget::resizeEvent(e);
-}
-
-void ExLineEdit::updateGeometries() {
- QStyleOptionFrame panel;
- initStyleOption(&panel);
- QRect rect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
-
- int padding = 3;
- // int height = rect.height() + padding*2;
- int width = rect.width();
-
- // int m_leftWidgetHeight = m_leftWidget->height();
- m_leftWidget->setGeometry(rect.x() + 2, 0,
- m_leftWidget->width(), m_leftWidget->height());
-
- int clearButtonWidth = this->height();
- m_lineEdit->setGeometry(m_leftWidget->x() + m_leftWidget->width(), padding,
- width - clearButtonWidth - m_leftWidget->width(), this->height() - padding*2);
-
- m_clearButton->setGeometry(this->width() - clearButtonWidth, 0,
- clearButtonWidth, this->height());
-}
-
-void ExLineEdit::initStyleOption(QStyleOptionFrame *option) const {
- option->initFrom(this);
- option->rect = contentsRect();
- option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
- option->midLineWidth = 0;
- option->state |= QStyle::State_Sunken;
- if (m_lineEdit->isReadOnly())
- option->state |= QStyle::State_ReadOnly;
-#ifdef QT_KEYPAD_NAVIGATION
- if (hasEditFocus())
- option->state |= QStyle::State_HasEditFocus;
-#endif
- option->features = QStyleOptionFrame::None;
-}
-
-QSize ExLineEdit::sizeHint() const {
- m_lineEdit->setFrame(true);
- QSize size = m_lineEdit->sizeHint();
- m_lineEdit->setFrame(false);
- size = size + QSize(3, 3);
- return size;
-}
-
-void ExLineEdit::focusInEvent(QFocusEvent *e) {
- m_lineEdit->event(e);
- QWidget::focusInEvent(e);
-}
-
-void ExLineEdit::focusOutEvent(QFocusEvent *e) {
- m_lineEdit->event(e);
-
- if (m_lineEdit->completer()) {
- connect(m_lineEdit->completer(), SIGNAL(activated(QString)),
- m_lineEdit, SLOT(setText(QString)));
- connect(m_lineEdit->completer(), SIGNAL(highlighted(QString)),
- m_lineEdit, SLOT(_q_completionHighlighted(QString)));
- }
- QWidget::focusOutEvent(e);
-}
-
-void ExLineEdit::keyPressEvent(QKeyEvent *e) {
- if (e->key() == Qt::Key_Escape && !m_lineEdit->text().isEmpty()) {
- m_lineEdit->clear();
- }
- m_lineEdit->event(e);
- QWidget::keyPressEvent(e);
-}
-
-bool ExLineEdit::event(QEvent *e) {
- if (e->type() == QEvent::ShortcutOverride || e->type() == QEvent::InputMethod)
- m_lineEdit->event(e);
- return QWidget::event(e);
-}
-
-void ExLineEdit::paintEvent(QPaintEvent *e) {
- Q_UNUSED(e);
- QPainter p(this);
- QStyleOptionFrame panel;
- initStyleOption(&panel);
- style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this);
-}
+++ /dev/null
-#ifndef EXLINEEDIT_H
-#define EXLINEEDIT_H
-
-#include <QtWidgets>
-
-class ClearButton : public QAbstractButton {
-
- Q_OBJECT
-
-public:
- ClearButton(QWidget *parent = 0);
-
-public slots:
- void textChanged(const QString &text);
-
-protected:
- void paintEvent(QPaintEvent *e);
- void enterEvent(QEvent *e);
- void leaveEvent(QEvent *e);
- void mousePressEvent(QMouseEvent *e);
- void mouseReleaseEvent(QMouseEvent *e);
-
-private:
- bool hovered;
- bool mousePressed;
-};
-
-class ExLineEdit : public QWidget {
-
- Q_OBJECT
-
-public:
- ExLineEdit(QWidget *parent = 0);
- QLineEdit *lineEdit() const { return m_lineEdit; }
- void setLeftWidget(QWidget *widget);
- QWidget *leftWidget() const;
- void clear();
- QString text();
- QSize sizeHint() const;
- void updateGeometries();
- void setFont(const QFont &font);
-
-protected:
- void focusInEvent(QFocusEvent *e);
- void focusOutEvent(QFocusEvent *e);
- void keyPressEvent(QKeyEvent *e);
- void paintEvent(QPaintEvent *e);
- void resizeEvent(QResizeEvent *e);
- bool event(QEvent *e);
- void initStyleOption(QStyleOptionFrame *option) const;
-
- QWidget *m_leftWidget;
- QLineEdit *m_lineEdit;
- ClearButton *m_clearButton;
-};
-
-#endif // EXLINEEDIT_H
-
QFont createFontWithMinSize(bool isBold, double sizeScale) {
const int minPixels = 11;
QFont font = createFont(isBold, sizeScale);
- if (font.pixelSize() < minPixels)
- font.setPixelSize(minPixels);
+ if (font.pixelSize() < minPixels) font.setPixelSize(minPixels);
return font;
}
-}
+} // namespace
const QFont &FontUtils::small() {
static const QFont font = createFontWithMinSize(false, .9);
}
const QFont &FontUtils::medium() {
- static const QFont font = createFont(false, 1.1);
+ static const QFont font = createFont(false, 1.15);
return font;
}
const QFont &FontUtils::mediumBold() {
- static const QFont font = createFont(true, 1.1);
+ static const QFont font = createFont(true, 1.15);
return font;
}
#include "gridwidget.h"
-GridWidget::GridWidget(QWidget *parent) :
- QWidget(parent),
- hovered(false),
- pressed(false) {
+GridWidget::GridWidget(QWidget *parent) : QWidget(parent), hovered(false), pressed(false) {
setCursor(Qt::PointingHandCursor);
setFocusPolicy(Qt::StrongFocus);
}
}
void GridWidget::keyReleaseEvent(QKeyEvent *event) {
- if (event->key() == Qt::Key_Return)
- emit activated();
+ if (event->key() == Qt::Key_Return) emit activated();
}
#include <QtWidgets>
class GridWidget : public QWidget {
-
Q_OBJECT
public:
- GridWidget(QWidget *parent = 0);
-
+ GridWidget(QWidget *parent = nullptr);
+
signals:
void activated();
-
+
protected:
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
void keyReleaseEvent(QKeyEvent *event);
-
+
bool hovered;
bool pressed;
};
$END_LICENSE */
#include "homeview.h"
-#include "segmentedcontrol.h"
-#include "searchview.h"
-#include "standardfeedsview.h"
+#include "channelaggregator.h"
#include "channelview.h"
+#include "iconutils.h"
#include "mainwindow.h"
#include "mediaview.h"
+#include "searchview.h"
+#include "segmentedcontrol.h"
+#include "standardfeedsview.h"
#include "ytstandardfeed.h"
-#include "iconutils.h"
-#include "channelaggregator.h"
#ifdef APP_MAC
#include "macutils.h"
#endif
-HomeView::HomeView(QWidget *parent) : View(parent),
- standardFeedsView(0),
- channelsView(0) {
-
+HomeView::HomeView(QWidget *parent)
+ : View(parent), searchView(nullptr), standardFeedsView(nullptr), channelsView(nullptr) {
QBoxLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
layout->setSpacing(0);
stackedWidget = new QStackedWidget();
layout->addWidget(stackedWidget);
-
- searchView = new SearchView(this);
- connect(searchView, SIGNAL(search(SearchParams*)),
- MainWindow::instance(), SLOT(showMedia(SearchParams*)));
- stackedWidget->addWidget(searchView);
}
void HomeView::setupBar() {
connect(ChannelAggregator::instance(), SIGNAL(unwatchedCountChanged(int)),
SLOT(unwatchedCountChanged(int)));
- const auto a = bar->actions();
- for (QAction* action : a) {
+ const auto &a = bar->actions();
+ for (QAction *action : a) {
addAction(action);
- IconUtils::setupAction(action);
+ MainWindow::instance()->setupAction(action);
}
}
void HomeView::showWidget(QWidget *widget) {
- QWidget* currentWidget = stackedWidget->currentWidget();
- if (currentWidget == widget) return;
- QMetaObject::invokeMethod(currentWidget, "disappear");
- currentWidget->setEnabled(false);
+ QWidget *currentWidget = stackedWidget->currentWidget();
+ if (currentWidget && currentWidget != widget) {
+ QMetaObject::invokeMethod(currentWidget, "disappear");
+ currentWidget->setEnabled(false);
+ }
stackedWidget->setCurrentWidget(widget);
widget->setEnabled(true);
- QMetaObject::invokeMethod(widget, "appear");
- QTimer::singleShot(0, widget, SLOT(setFocus()));
-
-#ifdef APP_MAC
- // Workaround cursor bug on macOS
- window()->unsetCursor();
-#endif
+ QMetaObject::invokeMethod(widget, "appear", Qt::QueuedConnection);
}
void HomeView::appear() {
- QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear", Qt::QueuedConnection);
+ if (stackedWidget->count() == 0)
+ showSearch();
+ else
+ QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear", Qt::QueuedConnection);
}
void HomeView::disappear() {
}
void HomeView::showSearch() {
+ if (!searchView) {
+ searchView = new SearchView(this);
+ connect(searchView, SIGNAL(search(SearchParams *)), MainWindow::instance(),
+ SLOT(showMedia(SearchParams *)));
+ stackedWidget->addWidget(searchView);
+ }
showWidget(searchView);
bar->setCheckedAction(0);
}
void HomeView::showStandardFeeds() {
if (!standardFeedsView) {
standardFeedsView = new StandardFeedsView();
- connect(standardFeedsView, SIGNAL(activated(VideoSource*)),
- MainWindow::instance(),
- SLOT(showMedia(VideoSource*)));
+ connect(standardFeedsView, SIGNAL(activated(VideoSource *)), MainWindow::instance(),
+ SLOT(showMedia(VideoSource *)));
stackedWidget->addWidget(standardFeedsView);
}
showWidget(standardFeedsView);
void HomeView::showChannels() {
if (!channelsView) {
channelsView = new ChannelView();
- connect(channelsView, SIGNAL(activated(VideoSource*)),
- MainWindow::instance(),
- SLOT(showMedia(VideoSource*)));
+ connect(channelsView, SIGNAL(activated(VideoSource *)), MainWindow::instance(),
+ SLOT(showMedia(VideoSource *)));
stackedWidget->addWidget(channelsView);
}
showWidget(channelsView);
+++ /dev/null
-# A wrapper for the Qt Network Access API
-
-This is just a wrapper around Qt's QNetworkAccessManager and friends. I use it in my Qt apps at http://flavio.tordini.org . It allows me to add missing functionality as needed, e.g.:
-
-- Throttling (as required by many web APIs nowadays)
-- Read timeouts (don't let your requests get stuck forever)
-- Automatic retries
-- User agent and request header defaults
-- Partial requests
-- Redirection support (now supported by Qt >= 5.6)
-
-It has a simpler, higher-level API that I find easier to work with. The design emerged naturally in years of practical use.
-
-A basic example:
-
-```
-QObject *reply = Http::instance().get("https://google.com/");
-connect(reply, SIGNAL(data(QByteArray)), SLOT(onSuccess(QByteArray)));
-connect(reply, SIGNAL(error(QString)), SLOT(onError(QString)));
-
-void MyClass::onSuccess(const QByteArray &bytes) {
- qDebug() << "Feel the bytes!" << bytes;
-}
-
-void MyClass::onError(const QString &message) {
- qDebug() << "Something's wrong here" << message;
-}
-```
-
-This is a real-world example of building a Http object suitable to a web service. It throttles requests, uses a custom user agent and caches results:
-
-```
-Http &myHttp() {
- static Http *http = [] {
- Http *http = new Http;
- http->addRequestHeader("User-Agent", userAgent());
-
- ThrottledHttp *throttledHttp = new ThrottledHttp(*http);
- throttledHttp->setMilliseconds(1000);
-
- CachedHttp *cachedHttp = new CachedHttp(*throttledHttp, "mycache");
- cachedHttp->setMaxSeconds(86400 * 30);
-
- return cachedHttp;
- }();
- return *http;
-}
-```
-
-If the full power (and complexity) of QNetworkReply is needed you can always fallback to it:
-
-```
-HttpRequest req;
-req.url = "https://flavio.tordini.org/";
-QNetworkReply *reply = Http::instance().networkReply(req);
-// Use QNetworkReply as needed...
-```
-
-You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes.
-
-Cheers,
-
-Flavio
+++ /dev/null
-QT *= network
-
-INCLUDEPATH += $$PWD/src
-DEPENDPATH += $$PWD/src
-
-HEADERS += \
- $$PWD/src/cachedhttp.h \
- $$PWD/src/http.h \
- $$PWD/src/localcache.h \
- $$PWD/src/throttledhttp.h
-
-SOURCES += \
- $$PWD/src/cachedhttp.cpp \
- $$PWD/src/http.cpp \
- $$PWD/src/localcache.cpp \
- $$PWD/src/throttledhttp.cpp
+++ /dev/null
-#include "cachedhttp.h"
-#include "localcache.h"
-
-namespace {
-
-QByteArray requestHash(const HttpRequest &req) {
- const char sep = '|';
- QByteArray s = req.url.toEncoded() + sep + req.body + sep + QByteArray::number(req.offset);
- if (req.operation == QNetworkAccessManager::PostOperation) {
- s.append(sep);
- s.append("POST");
- }
- return LocalCache::hash(s);
-}
-}
-
-CachedHttpReply::CachedHttpReply(const QByteArray &body, const HttpRequest &req)
- : bytes(body), req(req) {
- QTimer::singleShot(0, this, SLOT(emitSignals()));
-}
-
-QByteArray CachedHttpReply::body() const {
- return bytes;
-}
-
-void CachedHttpReply::emitSignals() {
- emit data(body());
- emit finished(*this);
- deleteLater();
-}
-
-WrappedHttpReply::WrappedHttpReply(LocalCache *cache, const QByteArray &key, QObject *httpReply)
- : QObject(httpReply), cache(cache), key(key), httpReply(httpReply) {
- connect(httpReply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray)));
- connect(httpReply, SIGNAL(error(QString)), SIGNAL(error(QString)));
- connect(httpReply, SIGNAL(finished(HttpReply)), SLOT(originFinished(HttpReply)));
-}
-
-void WrappedHttpReply::originFinished(const HttpReply &reply) {
- if (reply.isSuccessful()) cache->insert(key, reply.body());
- emit finished(reply);
-}
-
-CachedHttp::CachedHttp(Http &http, const char *name)
- : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {}
-
-void CachedHttp::setMaxSeconds(uint seconds) {
- cache->setMaxSeconds(seconds);
-}
-
-void CachedHttp::setMaxSize(uint maxSize) {
- cache->setMaxSize(maxSize);
-}
-
-QObject *CachedHttp::request(const HttpRequest &req) {
- bool cacheable = req.operation == QNetworkAccessManager::GetOperation ||
- (cachePostRequests && req.operation == QNetworkAccessManager::PostOperation);
- if (!cacheable) {
- qDebug() << "Not cacheable" << req.url;
- return http.request(req);
- }
- const QByteArray key = requestHash(req);
- const QByteArray value = cache->value(key);
- if (!value.isNull()) {
- qDebug() << "CachedHttp HIT" << req.url;
- return new CachedHttpReply(value, req);
- }
- qDebug() << "CachedHttp MISS" << req.url.toString();
- return new WrappedHttpReply(cache, key, http.request(req));
-}
+++ /dev/null
-#ifndef CACHEDHTTP_H
-#define CACHEDHTTP_H
-
-#include "http.h"
-
-class LocalCache;
-
-class CachedHttp : public Http {
-public:
- CachedHttp(Http &http = Http::instance(), const char *name = "http");
- void setMaxSeconds(uint seconds);
- void setMaxSize(uint maxSize);
- void setCachePostRequests(bool value) { cachePostRequests = value; }
- QObject *request(const HttpRequest &req);
-
-private:
- Http &http;
- LocalCache *cache;
- bool cachePostRequests;
-};
-
-class CachedHttpReply : public HttpReply {
- Q_OBJECT
-
-public:
- CachedHttpReply(const QByteArray &body, const HttpRequest &req);
- QUrl url() const { return req.url; }
- int statusCode() const { return 200; }
- QByteArray body() const;
-
-private slots:
- void emitSignals();
-
-private:
- const QByteArray bytes;
- const HttpRequest &req;
-};
-
-class WrappedHttpReply : public QObject {
- Q_OBJECT
-
-public:
- WrappedHttpReply(LocalCache *cache, const QByteArray &key, QObject *httpReply);
-
-signals:
- void data(const QByteArray &bytes);
- void error(const QString &message);
- void finished(const HttpReply &reply);
-
-private slots:
- void originFinished(const HttpReply &reply);
-
-private:
- LocalCache *cache;
- QByteArray key;
- QObject *httpReply;
-};
-
-#endif // CACHEDHTTP_H
+++ /dev/null
-#include "http.h"
-
-namespace {
-
-QNetworkAccessManager *createNetworkAccessManager() {
- QNetworkAccessManager *nam = new QNetworkAccessManager();
- return nam;
-}
-
-QNetworkAccessManager *networkAccessManager() {
- static QMap<QThread *, QNetworkAccessManager *> nams;
- QThread *t = QThread::currentThread();
- QMap<QThread *, QNetworkAccessManager *>::const_iterator i = nams.constFind(t);
- if (i != nams.constEnd()) return i.value();
- QNetworkAccessManager *nam = createNetworkAccessManager();
- nams.insert(t, nam);
- return nam;
-}
-
-static int defaultReadTimeout = 10000;
-}
-
-Http::Http() : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout) {}
-
-void Http::setRequestHeaders(const QMap<QByteArray, QByteArray> &headers) {
- requestHeaders = headers;
-}
-
-QMap<QByteArray, QByteArray> &Http::getRequestHeaders() {
- return requestHeaders;
-}
-
-void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) {
- requestHeaders.insert(name, value);
-}
-
-void Http::setReadTimeout(int timeout) {
- readTimeout = timeout;
-}
-
-Http &Http::instance() {
- static Http *i = new Http();
- return *i;
-}
-
-const QMap<QByteArray, QByteArray> &Http::getDefaultRequestHeaders() {
- static const QMap<QByteArray, QByteArray> defaultRequestHeaders = [] {
- QMap<QByteArray, QByteArray> h;
- h.insert("Accept-Charset", "utf-8");
- h.insert("Connection", "Keep-Alive");
- return h;
- }();
- return defaultRequestHeaders;
-}
-
-void Http::setDefaultReadTimeout(int timeout) {
- defaultReadTimeout = timeout;
-}
-
-QNetworkReply *Http::networkReply(const HttpRequest &req) {
- QNetworkRequest request(req.url);
-
- QMap<QByteArray, QByteArray> &headers = requestHeaders;
- if (!req.headers.isEmpty()) headers = req.headers;
-
- QMap<QByteArray, QByteArray>::const_iterator it;
- for (it = headers.constBegin(); it != headers.constEnd(); ++it)
- request.setRawHeader(it.key(), it.value());
-
- if (req.offset > 0)
- request.setRawHeader("Range", QString("bytes=%1-").arg(req.offset).toUtf8());
-
- QNetworkAccessManager *manager = networkAccessManager();
-
- QNetworkReply *networkReply = 0;
- switch (req.operation) {
- case QNetworkAccessManager::GetOperation:
- networkReply = manager->get(request);
- break;
-
- case QNetworkAccessManager::HeadOperation:
- networkReply = manager->head(request);
- break;
-
- case QNetworkAccessManager::PostOperation:
- networkReply = manager->post(request, req.body);
- break;
-
- default:
- qWarning() << "Unknown operation:" << req.operation;
- }
-
- return networkReply;
-}
-
-QObject *Http::request(const HttpRequest &req) {
- return new NetworkHttpReply(req, *this);
-}
-
-QObject *Http::request(const QUrl &url,
- QNetworkAccessManager::Operation operation,
- const QByteArray &body,
- uint offset) {
- HttpRequest req;
- req.url = url;
- req.operation = operation;
- req.body = body;
- req.offset = offset;
- return request(req);
-}
-
-QObject *Http::get(const QUrl &url) {
- return request(url, QNetworkAccessManager::GetOperation);
-}
-
-QObject *Http::head(const QUrl &url) {
- return request(url, QNetworkAccessManager::HeadOperation);
-}
-
-QObject *Http::post(const QUrl &url, const QMap<QString, QString> ¶ms) {
- QByteArray body;
- QMapIterator<QString, QString> i(params);
- while (i.hasNext()) {
- i.next();
- body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&';
- }
- HttpRequest req;
- req.url = url;
- req.operation = QNetworkAccessManager::PostOperation;
- req.body = body;
- req.headers = requestHeaders;
- req.headers.insert("Content-Type", "application/x-www-form-urlencoded");
- return request(req);
-}
-
-QObject *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) {
- HttpRequest req;
- req.url = url;
- req.operation = QNetworkAccessManager::PostOperation;
- req.body = body;
- req.headers = requestHeaders;
- QByteArray cType = contentType;
- if (cType.isEmpty()) cType = "application/x-www-form-urlencoded";
- req.headers.insert("Content-Type", cType);
- return request(req);
-}
-
-NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http)
- : http(http), req(req), retryCount(0) {
- if (req.url.isEmpty()) {
- qWarning() << "Empty URL";
- }
-
- networkReply = http.networkReply(req);
- setParent(networkReply);
- setupReply();
-
- readTimeoutTimer = new QTimer(this);
- readTimeoutTimer->setInterval(http.getReadTimeout());
- readTimeoutTimer->setSingleShot(true);
- connect(readTimeoutTimer, SIGNAL(timeout()), SLOT(readTimeout()), Qt::UniqueConnection);
- readTimeoutTimer->start();
-}
-
-void NetworkHttpReply::setupReply() {
- connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
- SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection);
- connect(networkReply, SIGNAL(finished()), SLOT(replyFinished()), Qt::UniqueConnection);
- connect(networkReply, SIGNAL(downloadProgress(qint64, qint64)),
- SLOT(downloadProgress(qint64, qint64)), Qt::UniqueConnection);
-}
-
-QString NetworkHttpReply::errorMessage() {
- return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') +
- reasonPhrase();
-}
-
-void NetworkHttpReply::emitError() {
- const QString msg = errorMessage();
-#ifndef QT_NO_DEBUG_OUTPUT
- qDebug() << "Http:" << msg;
- if (!req.body.isEmpty()) qDebug() << "Http:" << req.body;
-#endif
- emit error(msg);
- emitFinished();
-}
-
-void NetworkHttpReply::emitFinished() {
- readTimeoutTimer->stop();
-
- // disconnect to avoid replyFinished() from being called
- networkReply->disconnect();
-
- emit finished(*this);
-
- // bye bye my reply
- // this will also delete this object and HttpReply as the QNetworkReply is their parent
- networkReply->deleteLater();
-}
-
-void NetworkHttpReply::replyFinished() {
- QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
- if (redirection.isValid()) {
- HttpRequest redirectReq;
- redirectReq.url = redirection;
- redirectReq.operation = req.operation;
- redirectReq.body = req.body;
- redirectReq.offset = req.offset;
- QNetworkReply *redirectReply = http.networkReply(redirectReq);
- setParent(redirectReply);
- networkReply->deleteLater();
- networkReply = redirectReply;
- setupReply();
- readTimeoutTimer->start();
- return;
- }
-
- if (isSuccessful()) {
- bytes = networkReply->readAll();
- emit data(bytes);
-
-#ifndef QT_NO_DEBUG_OUTPUT
- if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool())
- qDebug() << networkReply->url().toString() << statusCode();
- else
- qDebug() << "CACHE" << networkReply->url().toString();
-#endif
- }
-
- emitFinished();
-}
-
-void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) {
- Q_UNUSED(code);
- const int status = statusCode();
- if (retryCount <= 3 && status >= 500 && status < 600) {
- qDebug() << "Retrying" << req.url;
- networkReply->disconnect();
- networkReply->deleteLater();
- QNetworkReply *retryReply = http.networkReply(req);
- setParent(retryReply);
- networkReply = retryReply;
- setupReply();
- retryCount++;
- readTimeoutTimer->start();
- } else {
- emitError();
- return;
- }
-}
-
-void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) {
- // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url();
- if (bytesReceived > 0 && readTimeoutTimer->isActive()) {
- readTimeoutTimer->stop();
- disconnect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), this,
- SLOT(downloadProgress(qint64, qint64)));
- }
-}
-
-void NetworkHttpReply::readTimeout() {
- if (!networkReply) return;
- networkReply->disconnect();
- networkReply->abort();
- networkReply->deleteLater();
-
- if (retryCount > 3 && (networkReply->operation() != QNetworkAccessManager::GetOperation &&
- networkReply->operation() != QNetworkAccessManager::HeadOperation)) {
- emitError();
- emit finished(*this);
- return;
- }
-
- qDebug() << "Timeout" << req.url;
- QNetworkReply *retryReply = http.networkReply(req);
- setParent(retryReply);
- networkReply = retryReply;
- setupReply();
- retryCount++;
- readTimeoutTimer->start();
-}
-
-QUrl NetworkHttpReply::url() const {
- return networkReply->url();
-}
-
-int NetworkHttpReply::statusCode() const {
- return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-}
-
-QString NetworkHttpReply::reasonPhrase() const {
- return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
-}
-
-const QList<QNetworkReply::RawHeaderPair> NetworkHttpReply::headers() const {
- return networkReply->rawHeaderPairs();
-}
-
-QByteArray NetworkHttpReply::header(const QByteArray &headerName) const {
- return networkReply->rawHeader(headerName);
-}
-
-QByteArray NetworkHttpReply::body() const {
- return bytes;
-}
+++ /dev/null
-#ifndef HTTP_H
-#define HTTP_H
-
-#include <QtNetwork>
-
-class HttpRequest {
-public:
- HttpRequest() : operation(QNetworkAccessManager::GetOperation), offset(0) {}
- QUrl url;
- QNetworkAccessManager::Operation operation;
- QByteArray body;
- uint offset;
- QMap<QByteArray, QByteArray> headers;
-};
-
-class Http {
-public:
- static Http &instance();
- static const QMap<QByteArray, QByteArray> &getDefaultRequestHeaders();
- static void setDefaultReadTimeout(int timeout);
-
- Http();
-
- void setRequestHeaders(const QMap<QByteArray, QByteArray> &headers);
- QMap<QByteArray, QByteArray> &getRequestHeaders();
- void addRequestHeader(const QByteArray &name, const QByteArray &value);
-
- void setReadTimeout(int timeout);
- int getReadTimeout() { return readTimeout; }
-
- QNetworkReply *networkReply(const HttpRequest &req);
- virtual QObject *request(const HttpRequest &req);
- QObject *request(const QUrl &url,
- QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
- const QByteArray &body = QByteArray(),
- uint offset = 0);
- QObject *get(const QUrl &url);
- QObject *head(const QUrl &url);
- QObject *post(const QUrl &url, const QMap<QString, QString> ¶ms);
- QObject *post(const QUrl &url, const QByteArray &body, const QByteArray &contentType);
-
-private:
- QMap<QByteArray, QByteArray> requestHeaders;
- int readTimeout;
-};
-
-class HttpReply : public QObject {
- Q_OBJECT
-
-public:
- HttpReply(QObject *parent = 0) : QObject(parent) {}
- virtual QUrl url() const = 0;
- virtual int statusCode() const = 0;
- int isSuccessful() const { return statusCode() >= 200 && statusCode() < 300; }
- virtual QString reasonPhrase() const { return QString(); }
- virtual const QList<QNetworkReply::RawHeaderPair> headers() const {
- return QList<QNetworkReply::RawHeaderPair>();
- }
- virtual QByteArray header(const QByteArray &headerName) const {
- Q_UNUSED(headerName);
- return QByteArray();
- }
-
- virtual QByteArray body() const = 0;
-
-signals:
- void data(const QByteArray &bytes);
- void error(const QString &message);
- void finished(const HttpReply &reply);
-};
-
-class NetworkHttpReply : public HttpReply {
- Q_OBJECT
-
-public:
- NetworkHttpReply(const HttpRequest &req, Http &http);
- QUrl url() const;
- int statusCode() const;
- QString reasonPhrase() const;
- const QList<QNetworkReply::RawHeaderPair> headers() const;
- QByteArray header(const QByteArray &headerName) const;
- QByteArray body() const;
-
-private slots:
- void replyFinished();
- void replyError(QNetworkReply::NetworkError);
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
- void readTimeout();
-
-private:
- void setupReply();
- QString errorMessage();
- void emitError();
- void emitFinished();
-
- Http &http;
- HttpRequest req;
- QNetworkReply *networkReply;
- QTimer *readTimeoutTimer;
- int retryCount;
- QByteArray bytes;
-};
-
-#endif // HTTP_H
+++ /dev/null
-#include "localcache.h"
-
-LocalCache *LocalCache::instance(const char *name) {
- static QMap<QByteArray, LocalCache *> instances;
- auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name)));
- if (i != instances.constEnd()) return i.value();
- LocalCache *instance = new LocalCache(name);
- instances.insert(instance->getName(), instance);
- return instance;
-}
-
-LocalCache::LocalCache(const QByteArray &name)
- : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), expiring(false),
- insertCount(0) {
- directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') +
- QLatin1String(name) + QLatin1Char('/');
-#ifndef QT_NO_DEBUG_OUTPUT
- hits = 0;
- misses = 0;
-#endif
-}
-
-LocalCache::~LocalCache() {
-#ifndef QT_NO_DEBUG_OUTPUT
- debugStats();
-#endif
-}
-
-QByteArray LocalCache::hash(const QByteArray &s) {
- QCryptographicHash hash(QCryptographicHash::Sha1);
- hash.addData(s);
- const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36);
- static const char sep('/');
- QByteArray p;
- p.reserve(h.length() + 2);
- p.append(h.at(0));
- p.append(sep);
- p.append(h.at(1));
- p.append(sep);
- p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2));
- return p;
-}
-
-bool LocalCache::isCached(const QString &path) {
- bool cached = (QFile::exists(path) &&
- (maxSeconds == 0 ||
- QDateTime::currentDateTime().toTime_t() - QFileInfo(path).created().toTime_t() <
- maxSeconds));
-#ifndef QT_NO_DEBUG_OUTPUT
- if (!cached) misses++;
-#endif
- return cached;
-}
-
-QByteArray LocalCache::value(const QByteArray &key) {
- const QString path = cachePath(key);
- if (!isCached(path)) return QByteArray();
-
- QFile file(path);
- if (!file.open(QIODevice::ReadOnly)) {
- qWarning() << __PRETTY_FUNCTION__ << file.fileName() << file.errorString();
-#ifndef QT_NO_DEBUG_OUTPUT
- misses++;
-#endif
- return QByteArray();
- }
-#ifndef QT_NO_DEBUG_OUTPUT
- hits++;
-#endif
- return file.readAll();
-}
-
-void LocalCache::insert(const QByteArray &key, const QByteArray &value) {
- const QueueItem item = {key, value};
- insertQueue.append(item);
- QTimer::singleShot(0, [this]() {
- if (insertQueue.isEmpty()) return;
- for (const auto &item : insertQueue) {
- const QString path = cachePath(item.key);
- const QString parentDir = path.left(path.lastIndexOf('/'));
- if (!QFile::exists(parentDir)) {
- QDir().mkpath(parentDir);
- }
- QFile file(path);
- if (!file.open(QIODevice::WriteOnly)) {
- qWarning() << "Cannot create" << path;
- continue;
- }
- file.write(item.value);
- file.close();
- if (size > 0) size += item.value.size();
- }
- insertQueue.clear();
-
- // expire cache every n inserts
- if (maxSize > 0 && ++insertCount % 100 == 0) {
- if (size == 0 || size > maxSize) size = expire();
- }
- });
-}
-
-bool LocalCache::clear() {
-#ifndef QT_NO_DEBUG_OUTPUT
- hits = 0;
- misses = 0;
-#endif
- size = 0;
- insertCount = 0;
- return QDir(directory).removeRecursively();
-}
-
-QString LocalCache::cachePath(const QByteArray &key) const {
- return directory + QLatin1String(key.constData());
-}
-
-qint64 LocalCache::expire() {
- if (expiring) return size;
- expiring = true;
-
- QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
- QDirIterator it(directory, filters, QDirIterator::Subdirectories);
-
- QMultiMap<QDateTime, QString> cacheItems;
- qint64 totalSize = 0;
- while (it.hasNext()) {
- QString path = it.next();
- QFileInfo info = it.fileInfo();
- cacheItems.insert(info.created(), path);
- totalSize += info.size();
- qApp->processEvents();
- }
-
- int removedFiles = 0;
- qint64 goal = (maxSize * 9) / 10;
- auto i = cacheItems.constBegin();
- while (i != cacheItems.constEnd()) {
- if (totalSize < goal) break;
- QString name = i.value();
- QFile file(name);
- qint64 size = file.size();
- file.remove();
- totalSize -= size;
- ++removedFiles;
- ++i;
- qApp->processEvents();
- }
-#ifndef QT_NO_DEBUG_OUTPUT
- debugStats();
- if (removedFiles > 0) {
- qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles
- << "New Size:" << totalSize;
- }
-#endif
-
- expiring = false;
-
- return totalSize;
-}
-
-#ifndef QT_NO_DEBUG_OUTPUT
-void LocalCache::debugStats() {
- int total = hits + misses;
- if (total > 0) {
- qDebug() << "Cache:" << name << '\n'
- << "Inserts:" << insertCount << '\n'
- << "Requests:" << total << '\n'
- << "Hits:" << hits << (hits * 100) / total << "%\n"
- << "Misses:" << misses << (misses * 100) / total << "%";
- }
-}
-#endif
+++ /dev/null
-#ifndef LOCALCACHE_H
-#define LOCALCACHE_H
-
-#include <QtCore>
-
-/**
- * @brief Not thread-safe
- */
-class LocalCache {
-public:
- static LocalCache *instance(const char *name);
- ~LocalCache();
- static QByteArray hash(const QByteArray &s);
-
- const QByteArray &getName() const { return name; }
-
- void setMaxSeconds(uint value) { maxSeconds = value; }
- void setMaxSize(uint value) { maxSize = value; }
-
- QByteArray value(const QByteArray &key);
- void insert(const QByteArray &key, const QByteArray &value);
- bool clear();
-
-private:
- LocalCache(const QByteArray &name);
- QString cachePath(const QByteArray &key) const;
- bool isCached(const QString &path);
- qint64 expire();
-#ifndef QT_NO_DEBUG_OUTPUT
- void debugStats();
-#endif
-
- QByteArray name;
- QString directory;
- uint maxSeconds;
- qint64 maxSize;
- qint64 size;
- bool expiring;
- uint insertCount;
- struct QueueItem {
- QByteArray key;
- QByteArray value;
- };
- QVector<QueueItem> insertQueue;
-
-#ifndef QT_NO_DEBUG_OUTPUT
- uint hits;
- uint misses;
-#endif
-};
-
-#endif // LOCALCACHE_H
+++ /dev/null
-#include "throttledhttp.h"
-
-namespace {
-
-QElapsedTimer initElapsedTimer() {
- QElapsedTimer timer;
- timer.start();
- return timer;
-}
-}
-
-ThrottledHttp::ThrottledHttp(Http &http) : http(http), elapsedTimer(initElapsedTimer()) {}
-
-QObject *ThrottledHttp::request(const HttpRequest &req) {
- return new ThrottledHttpReply(http, req, milliseconds, elapsedTimer);
-}
-
-ThrottledHttpReply::ThrottledHttpReply(Http &http,
- const HttpRequest &req,
- int milliseconds,
- QElapsedTimer &elapsedTimer)
- : http(http), req(req), milliseconds(milliseconds), elapsedTimer(elapsedTimer), timer(0) {
- checkElapsed();
-}
-
-void ThrottledHttpReply::checkElapsed() {
- /*
- static QMutex mutex;
- QMutexLocker locker(&mutex);
- */
-
- const qint64 elapsedSinceLastRequest = elapsedTimer.elapsed();
- if (elapsedSinceLastRequest < milliseconds) {
- if (!timer) {
- timer = new QTimer(this);
- timer->setSingleShot(true);
- timer->setTimerType(Qt::PreciseTimer);
- connect(timer, SIGNAL(timeout()), SLOT(checkElapsed()));
- }
- qDebug() << "Throttling" << req.url
- << QString("%1ms").arg(milliseconds - elapsedSinceLastRequest);
- timer->setInterval(milliseconds - elapsedSinceLastRequest);
- timer->start();
- return;
- }
- elapsedTimer.start();
- doRequest();
-}
-
-void ThrottledHttpReply::doRequest() {
- QObject *reply = http.request(req);
- connect(reply, SIGNAL(data(QByteArray)), SIGNAL(data(QByteArray)));
- connect(reply, SIGNAL(error(QString)), SIGNAL(error(QString)));
- connect(reply, SIGNAL(finished(HttpReply)), SIGNAL(finished(HttpReply)));
-
- // this will cause the deletion of this object once the request is finished
- setParent(reply);
-}
+++ /dev/null
-#ifndef THROTTLEDHTTP_H
-#define THROTTLEDHTTP_H
-
-#include "http.h"
-#include <QtCore>
-#include <QtNetwork>
-
-class ThrottledHttp : public Http {
-public:
- ThrottledHttp(Http &http = Http::instance());
- void setMilliseconds(int milliseconds) { this->milliseconds = milliseconds; }
- QObject *request(const HttpRequest &req);
-
-private:
- Http &http;
- int milliseconds;
- QElapsedTimer elapsedTimer;
-};
-
-class ThrottledHttpReply : public HttpReply {
- Q_OBJECT
-
-public:
- ThrottledHttpReply(Http &http,
- const HttpRequest &req,
- int milliseconds,
- QElapsedTimer &elapsedTimer);
- QUrl url() const { return req.url; }
- int statusCode() const { return 200; }
- QByteArray body() const { return QByteArray(); }
-
-private slots:
- void checkElapsed();
-
-private:
- void doRequest();
- Http &http;
- HttpRequest req;
- int milliseconds;
- QElapsedTimer &elapsedTimer;
- QTimer *timer;
-};
-
-#endif // THROTTLEDHTTP_H
#include "httputils.h"
+#include "cachedhttp.h"
#include "constants.h"
#include "http.h"
-#include "throttledhttp.h"
-#include "cachedhttp.h"
#include "localcache.h"
+#include "throttledhttp.h"
Http &HttpUtils::notCached() {
static Http *h = [] {
const QByteArray &HttpUtils::userAgent() {
static const QByteArray ua = [] {
- return QString(QLatin1String(Constants::NAME)
- + QLatin1Char('/') + QLatin1String(Constants::VERSION)
- + QLatin1String(" ( ") + Constants::WEBSITE + QLatin1String(" )")).toUtf8();
+ return QString(QLatin1String(Constants::NAME) + QLatin1Char('/') +
+ QLatin1String(Constants::VERSION) + QLatin1String(" ( ") +
+ Constants::WEBSITE + QLatin1String(" )"))
+ .toUtf8();
}();
return ua;
}
const QByteArray &HttpUtils::stealthUserAgent() {
- static const QByteArray ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36";
+ static const QByteArray ua = "curl/7.37.0";
return ua;
}
$END_LICENSE */
#include "iconutils.h"
-#include <QAction>
#include "mainwindow.h"
+#include <QAction>
+
+namespace {
+void addIconFile(QIcon &icon,
+ const QString &filename,
+ int size,
+ QIcon::Mode mode = QIcon::Normal,
+ QIcon::State state = QIcon::Off) {
+ if (QFile::exists(filename)) {
+ icon.addFile(filename, QSize(size, size), mode, state);
+ }
+}
+} // namespace
QIcon IconUtils::fromTheme(const QString &name) {
- const QLatin1String symbolic("-symbolic");
+ static const QLatin1String symbolic("-symbolic");
if (name.endsWith(symbolic)) return QIcon::fromTheme(name);
QIcon icon = QIcon::fromTheme(name + symbolic);
if (icon.isNull()) return QIcon::fromTheme(name);
return icon;
}
-QIcon IconUtils::fromResources(const QString &name) {
- QLatin1String path(":/images/");
- QLatin1String ext(".png");
- const QString pathAndName = path + name;
- QIcon icon = QIcon(pathAndName + ext);
- if (!icon.isNull()) {
- QLatin1String active("_active");
- QLatin1String selected("_selected");
- QLatin1String disabled("_disabled");
- QLatin1String checked("_checked");
- QLatin1String twoX("@2x");
-
- icon.addPixmap(QPixmap(pathAndName + active + ext), QIcon::Active);
- icon.addPixmap(QPixmap(pathAndName + selected + ext), QIcon::Selected);
- icon.addPixmap(QPixmap(pathAndName + disabled + ext), QIcon::Disabled);
- icon.addPixmap(QPixmap(pathAndName + checked + ext), QIcon::Normal, QIcon::On);
-
- const QString twoXAndExt = twoX + ext;
- icon.addPixmap(QPixmap(pathAndName + active + twoXAndExt), QIcon::Active);
- icon.addPixmap(QPixmap(pathAndName + selected + twoXAndExt), QIcon::Selected);
- icon.addPixmap(QPixmap(pathAndName + disabled + twoXAndExt), QIcon::Disabled);
- icon.addPixmap(QPixmap(pathAndName + checked + twoXAndExt), QIcon::Normal, QIcon::On);
+QIcon IconUtils::fromResources(const char *name) {
+ static const QLatin1String active("_active");
+ static const QLatin1String selected("_selected");
+ static const QLatin1String disabled("_disabled");
+ static const QLatin1String checked("_checked");
+ static const QLatin1String ext(".png");
+
+ QString path(":/icons/");
+
+ if (MainWindow::instance()->palette().window().color().value() > 128)
+ path += QLatin1String("light/");
+ else
+ path += QLatin1String("dark/");
+
+ QIcon icon;
+
+ // WARN keep these sizes updated with what we really use
+ for (int size : {16, 24, 32, 88}) {
+ const QString pathAndName = path + QString::number(size) + '/' + name;
+ QString iconFilename = pathAndName + ext;
+ if (QFile::exists(iconFilename)) {
+ addIconFile(icon, iconFilename, size);
+ addIconFile(icon, pathAndName + active + ext, size, QIcon::Active);
+ addIconFile(icon, pathAndName + selected + ext, size, QIcon::Selected);
+ addIconFile(icon, pathAndName + disabled + ext, size, QIcon::Disabled);
+ addIconFile(icon, pathAndName + checked + ext, size, QIcon::Normal, QIcon::On);
+ }
}
return icon;
}
-QIcon IconUtils::icon(const QString &name) {
+QIcon IconUtils::icon(const char *name) {
#ifdef APP_LINUX
QIcon icon = fromTheme(name);
if (icon.isNull()) icon = fromResources(name);
#endif
}
-QIcon IconUtils::icon(const QStringList &names) {
+QIcon IconUtils::icon(const QVector<const char *> &names) {
QIcon icon;
- for (const QString &name : names) {
+ for (auto name : names) {
icon = IconUtils::icon(name);
if (!icon.availableSizes().isEmpty()) break;
}
return icon;
}
-QIcon IconUtils::tintedIcon(const QString &name, const QColor &color, QList<QSize> sizes) {
+QPixmap IconUtils::iconPixmap(const char *name,
+ int size,
+ const QColor &background,
+ const qreal pixelRatio) {
+ QString path(":/icons/");
+ if (background.value() > 128)
+ path += "light/";
+ else
+ path += "dark/";
+ path += QString::number(size) + '/' + name + QLatin1String(".png");
+ return IconUtils::pixmap(path, pixelRatio);
+}
+
+QIcon IconUtils::tintedIcon(const char *name, const QColor &color, const QVector<QSize> &sizes) {
QIcon i = IconUtils::icon(name);
QIcon t;
- if (sizes.isEmpty()) sizes = i.availableSizes();
+ // if (sizes.isEmpty()) sizes = i.availableSizes();
for (const QSize &size : sizes) {
QPixmap pixmap = i.pixmap(size);
- QImage tintedImage = tinted(pixmap.toImage(), color);
- t.addPixmap(QPixmap::fromImage(tintedImage));
+ tint(pixmap, color);
+ t.addPixmap(pixmap);
}
return t;
}
-QIcon IconUtils::tintedIcon(const QString &name, const QColor &color, const QSize &size) {
- return IconUtils::tintedIcon(name, color, QList<QSize>() << size);
+QIcon IconUtils::tintedIcon(const char *name, const QColor &color, const QSize &size) {
+ return IconUtils::tintedIcon(name, color, QVector<QSize>() << size);
}
QImage IconUtils::grayscaled(const QImage &image) {
return img;
}
-QImage IconUtils::tinted(const QImage &image, const QColor &color,
- QPainter::CompositionMode mode) {
+QImage IconUtils::tinted(const QImage &image, const QColor &color, QPainter::CompositionMode mode) {
QImage img(image.size(), QImage::Format_ARGB32_Premultiplied);
QPainter painter(&img);
painter.drawImage(0, 0, grayscaled(image));
return img;
}
-void IconUtils::setupAction(QAction *action) {
- // never autorepeat.
- // unexperienced users tend to keep keys pressed for a "long" time
- action->setAutoRepeat(false);
-
- // show keyboard shortcuts in the status bar
- if (!action->shortcut().isEmpty())
- action->setStatusTip(action->statusTip() +
- QLatin1String(" (") +
- action->shortcut().toString(QKeySequence::NativeText) +
- QLatin1String(")"));
+void IconUtils::tint(QPixmap &pixmap, const QColor &color, QPainter::CompositionMode mode) {
+ QPainter painter(&pixmap);
+ painter.setCompositionMode(mode);
+ painter.fillRect(pixmap.rect(), color);
}
-QPixmap IconUtils::pixmap(const QString &name) {
+QPixmap IconUtils::pixmap(const QString &filename, const qreal pixelRatio) {
// Check if a "@2x" file exists
- const qreal pixelRatio = IconUtils::pixelRatio();
if (pixelRatio > 1.0) {
- int dotIndex = name.lastIndexOf(QLatin1Char('.'));
+ int dotIndex = filename.lastIndexOf(QLatin1Char('.'));
if (dotIndex != -1) {
- QString at2xfileName = name;
+ QString at2xfileName = filename;
at2xfileName.insert(dotIndex, QLatin1String("@2x"));
if (QFile::exists(at2xfileName)) {
QPixmap pixmap(at2xfileName);
}
}
}
- return QPixmap(name);
-}
-
-qreal IconUtils::pixelRatio() {
-#if QT_VERSION >= 0x050600
- return MainWindow::instance()->devicePixelRatioF();
-#else
- return MainWindow::instance()->devicePixelRatio();
-#endif
+ return QPixmap(filename);
}
#include <QtGui>
class IconUtils {
-
public:
static QIcon fromTheme(const QString &name);
- static QIcon fromResources(const QString &name);
- static QIcon icon(const QString &name);
- static QIcon icon(const QStringList &names);
- static QIcon tintedIcon(const QString &name, const QColor &color,
- QList<QSize> sizes = QList<QSize>());
- static QIcon tintedIcon(const QString &name, const QColor &color, const QSize &size);
- static void setupAction(QAction *action);
+ static QIcon fromResources(const char *name);
+
+ template <class T> static void setIcon(T *obj, const char *name) {
+ QIcon i = icon(name);
+ obj->setIcon(i);
+ obj->connect(qApp, &QGuiApplication::paletteChanged, obj, [obj, name] {
+ QIcon i = icon(name);
+ obj->setIcon(i);
+ });
+ }
+ static QIcon icon(const char *name);
+ static QIcon icon(const QVector<const char *> &names);
+
+ static QPixmap
+ iconPixmap(const char *name, int size, const QColor &background, const qreal pixelRatio);
+
+ static QIcon tintedIcon(const char *name, const QColor &color, const QVector<QSize> &sizes);
+ static QIcon tintedIcon(const char *name, const QColor &color, const QSize &size);
// HiDPI stuff
- static QPixmap pixmap(const QString &name);
- static qreal maxSupportedPixelRatio() { return 2.0; }
- static qreal pixelRatio();
+ static QPixmap pixmap(const QString &filename, const qreal pixelRatio);
+
+ static void tint(QPixmap &pixmap,
+ const QColor &color,
+ QPainter::CompositionMode mode = QPainter::CompositionMode_SourceIn);
private:
- IconUtils() { }
+ IconUtils() {}
static QImage grayscaled(const QImage &image);
- static QImage tinted(const QImage &image, const QColor &color,
+ static QImage tinted(const QImage &image,
+ const QColor &color,
QPainter::CompositionMode mode = QPainter::CompositionMode_Screen);
};
+++ /dev/null
-INCLUDEPATH += $$PWD/src
-DEPENDPATH += $$PWD/src
-
-HEADERS += $$PWD/src/idle.h
-
-mac {
- SOURCES += $$PWD/src/idle_mac.cpp
-} else:win32 {
- SOURCES += $$PWD/src/idle_win.cpp
-} else {
- QT *= dbus
- SOURCES += $$PWD/src/idle_linux.cpp
-}
+++ /dev/null
-#ifndef IDLE_H
-#define IDLE_H
-
-#include <QString>
-
-class Idle {
-
-public:
- static bool preventDisplaySleep(const QString &reason);
- static bool allowDisplaySleep();
- static QString displayErrorMessage();
-
- static bool preventSystemSleep(const QString &reason);
- static bool allowSystemSleep();
- static QString systemErrorMessage();
-
-};
-
-#endif // IDLE_H
+++ /dev/null
-#include "idle.h"
-
-#include <QtCore>
-#include <QDBusInterface>
-#include <QDBusReply>
-#include <QDBusConnectionInterface>
-
-namespace {
-
-const char *fdDisplayService = "org.freedesktop.ScreenSaver";
-const char *fdDisplayPath = "/org/freedesktop/ScreenSaver";
-const char *fdDisplayInterface = fdDisplayService;
-
-const char *gnomeSystemService = "org.gnome.SessionManager";
-const char *gnomeSystemPath = "/org/gnome/SessionManager";
-const char *gnomeSystemInterface = gnomeSystemService;
-
-quint32 cookie;
-QString errorMessage;
-
-bool handleReply(const QDBusReply<quint32> &reply) {
- if (reply.isValid()) {
- cookie = reply.value();
- qDebug() << "Success!" << cookie;
- errorMessage.clear();
- return true;
- }
- errorMessage = reply.error().message();
- return false;
-}
-
-}
-
-bool Idle::preventDisplaySleep(const QString &reason) {
- QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface);
- QDBusReply<quint32> reply = dbus.call("Inhibit", QCoreApplication::applicationName(), reason);
- return handleReply(reply);
-}
-
-bool Idle::allowDisplaySleep() {
- QDBusInterface dbus(fdDisplayService, fdDisplayPath, fdDisplayInterface);
- dbus.call("UnInhibit", cookie);
- return true;
-}
-
-QString Idle::displayErrorMessage() {
- return errorMessage;
-}
-
-bool Idle::preventSystemSleep(const QString &reason) {
- QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface);
- QDBusReply<quint32> reply = dbus.call("Inhibit", QCoreApplication::applicationName(), reason);
- return handleReply(reply);
-}
-
-bool Idle::allowSystemSleep() {
- QDBusInterface dbus(gnomeSystemService, gnomeSystemPath, gnomeSystemInterface);
- dbus.call("UnInhibit", cookie);
- return true;
-}
-
-QString Idle::systemErrorMessage() {
- return errorMessage;
-}
-
+++ /dev/null
-#include "idle.h"
-
-#include <IOKit/pwr_mgt/IOPMLib.h>
-
-namespace {
-
-IOPMAssertionID displayAssertionID = 0;
-IOReturn displayRes = 0;
-
-IOPMAssertionID systemAssertionID = 0;
-IOReturn systemRes = 0;
-
-}
-
-bool Idle::preventDisplaySleep(const QString &reason) {
- displayRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,
- kIOPMAssertionLevelOn, reason.toCFString(), &displayAssertionID);
- return displayRes == kIOReturnSuccess;
-}
-
-bool Idle::allowDisplaySleep() {
- displayRes = IOPMAssertionRelease(displayAssertionID);
- return displayRes == kIOReturnSuccess;
-}
-
-QString Idle::displayErrorMessage() {
- return QString();
- // return QString::fromUtf8(IOService::stringFromReturn(displayRes));
-}
-
-bool Idle::preventSystemSleep(const QString &reason) {
- systemRes = IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep,
- kIOPMAssertionLevelOn, reason.toCFString(), &systemAssertionID);
- return systemRes == kIOReturnSuccess;
-}
-
-bool Idle::allowSystemSleep() {
- systemRes = IOPMAssertionRelease(systemAssertionID);
- return systemRes == kIOReturnSuccess;
-}
-
-QString Idle::systemErrorMessage() {
- return QString();
- // return QString::fromUtf8(IOService::stringFromReturn(systemRes));
-}
+++ /dev/null
-#include "idle.h"
-
-#include "windows.h"
-
-namespace {
-EXECUTION_STATE executionState;
-}
-
-bool Idle::preventDisplaySleep(const QString &reason) {
- executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
- return true;
-}
-
-bool Idle::allowDisplaySleep() {
- SetThreadExecutionState(ES_CONTINUOUS | executionState);
- return true;
-}
-
-QString Idle::displayErrorMessage() {
- return QString();
-}
-
-bool Idle::preventSystemSleep(const QString &reason) {
- executionState = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
- return true;
-}
-
-bool Idle::allowSystemSleep() {
- SetThreadExecutionState(ES_CONTINUOUS | executionState);
- return true;
-}
-
-QString Idle::systemErrorMessage() {
- return QString();
-}
$END_LICENSE */
#include "jsfunctions.h"
-#include "http.h"
#include "constants.h"
+#include "http.h"
#include <QJSValueIterator>
-JsFunctions* JsFunctions::instance() {
+JsFunctions *JsFunctions::instance() {
static JsFunctions *i = new JsFunctions(QLatin1String(Constants::WEBSITE) + "-ws/functions.js");
return i;
}
-JsFunctions::JsFunctions(const QString &url, QObject *parent) : QObject(parent), url(url), engine(0) {
+JsFunctions::JsFunctions(const QString &url, QObject *parent)
+ : QObject(parent), url(url), engine(nullptr) {
QFile file(jsPath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
else
qWarning() << "Cannot open" << file.errorString() << file.fileName();
QFileInfo info(file);
- bool stale = info.size() == 0 || info.lastModified().toTime_t() < QDateTime::currentDateTime().toTime_t() - 1800;
+ bool stale = info.size() == 0 || info.lastModified().toTime_t() <
+ QDateTime::currentDateTime().toTime_t() - 1800;
if (stale) loadJs();
} else {
QFile resFile(QLatin1String(":/") + jsFilename());
QUrlQuery q;
q.addQueryItem("v", Constants::VERSION);
url.setQuery(q);
- QObject* reply = Http::instance().get(url);
+ QObject *reply = Http::instance().get(url);
connect(reply, SIGNAL(data(QByteArray)), SLOT(gotJs(QByteArray)));
connect(reply, SIGNAL(error(QString)), SLOT(errorJs(QString)));
}
QJSValue JsFunctions::evaluate(const QString &js) {
if (!engine) return QString();
QJSValue value = engine->evaluate(js);
- if (value.isUndefined())
- qWarning() << "Undefined result for" << js;
- if (value.isError())
- qWarning() << "Error in" << js << value.toString();
+ if (value.isUndefined()) qWarning() << "Undefined result for" << js;
+ if (value.isError()) qWarning() << "Error in" << js << value.toString();
return value;
}
return string("signatureFunctionNameRE()");
}
+QStringList JsFunctions::signatureFunctionNameREs() {
+ return stringArray("signatureFunctionNameREs()");
+}
+
QStringList JsFunctions::apiKeys() {
return stringArray("apiKeys()");
}
#ifndef JSFUNCTIONS_H
#define JSFUNCTIONS_H
-#include <QtCore>
-#include <QtNetwork>
#include <QJSEngine>
#include <QJSValue>
+#include <QtCore>
+#include <QtNetwork>
class JsFunctions : public QObject {
-
Q_OBJECT
public:
- static JsFunctions* instance();
- JsFunctions(const QString &url, QObject *parent = 0);
+ static JsFunctions *instance();
+ JsFunctions(const QString &url, QObject *parent = nullptr);
QJSValue evaluate(const QString &js);
QString string(const QString &js);
QStringList stringArray(const QString &js);
QString ageGateRE();
QString jsPlayerRE();
QString signatureFunctionNameRE();
+ QStringList signatureFunctionNameREs();
QStringList apiKeys();
signals:
titleLabel->setText(title);
titleLabel->setVisible(window()->height() > 100);
- const int maxDescLength = 400;
+ const int maxDescLength = 500;
QString videoDesc = video->getDescription();
if (videoDesc.length() > maxDescLength) {
progressBar->setValue(0);
}
-void LoadingWidget::bufferStatus(int percent) {
- if (startTime.elapsed() > 2000 && percent > progressBar->value())
+void LoadingWidget::bufferStatus(qreal value) {
+ int percent = value * 100.;
+ if (startTime.elapsed() > 1000 && percent > progressBar->value())
progressBar->setValue(percent);
}
layout()->setMargin(spacing);
titleLabel->setFont(f);
- f.setPixelSize(f.pixelSize() / 2);
- descriptionLabel->setFont(f);
+ QFont descFont = descriptionLabel->font();
+ descFont.setPixelSize(f.pixelSize() / 2);
+ descriptionLabel->setFont(descFont);
}
void LoadingWidget::clear() {
#include "video.h"
class LoadingWidget : public QWidget {
-
Q_OBJECT
public:
void resizeEvent(QResizeEvent *e);
public slots:
- void bufferStatus(int);
+ void bufferStatus(qreal value);
private:
void adjustFontSize();
QLabel *descriptionLabel;
QProgressBar *progressBar;
QTime startTime;
-
};
#endif // LOADINGWIDGET_H
$END_LICENSE */
-#include <QtWidgets>
#include <QtNetwork>
+#include <QtWidgets>
-#include <qtsingleapplication.h>
#include "constants.h"
+#include "iconutils.h"
#include "mainwindow.h"
#include "searchparams.h"
-#include "iconutils.h"
+#include <qtsingleapplication.h>
#ifdef APP_EXTRA
#include "extra.h"
#endif
#include "mac_startup.h"
#endif
-void showWindow(QtSingleApplication &app, const QString &dataDir) {
+void showWindow(QtSingleApplication &app, const QString &pkgDataDir) {
MainWindow *mainWin = new MainWindow();
#ifndef APP_MAC
QIcon appIcon;
- if (QDir(dataDir).exists()) {
+ if (!pkgDataDir.isEmpty()) {
appIcon = IconUtils::icon(Constants::UNIX_NAME);
} else {
QString dataDir = qApp->applicationDirPath() + "/data";
- const int iconSizes [] = { 16, 22, 32, 48, 64, 128, 256, 512 };
+ const int iconSizes[] = {16, 22, 32, 48, 64, 128, 256, 512};
for (int i = 0; i < 8; i++) {
QString size = QString::number(iconSizes[i]);
- QString png = dataDir + "/" + size + "x" + size + "/" + Constants::UNIX_NAME + ".png";
+ QString png = dataDir + '/' + size + 'x' + size + '/' + Constants::UNIX_NAME +
+ QLatin1String(".png");
appIcon.addFile(png, QSize(iconSizes[i], iconSizes[i]));
}
}
mainWin->setWindowIcon(appIcon);
#endif
- mainWin->connect(&app, SIGNAL(messageReceived(const QString &)),
- mainWin, SLOT(messageReceived(const QString &)));
+ mainWin->connect(&app, SIGNAL(messageReceived(const QString &)), mainWin,
+ SLOT(messageReceived(const QString &)));
app.setActivationWindow(mainWin, true);
mainWin->show();
// Seed random number generator
qsrand(QDateTime::currentDateTime().toTime_t());
+#ifdef MEDIA_MPV
+ QSurfaceFormat format = QSurfaceFormat::defaultFormat();
+#ifdef APP_MAC
+ format.setMajorVersion(4);
+ format.setMinorVersion(1);
+#endif
+ format.setProfile(QSurfaceFormat::CoreProfile);
+ QSurfaceFormat::setDefaultFormat(format);
+#endif
+
#ifdef Q_OS_MAC
mac::MacMain();
#endif
QtSingleApplication app(argc, argv);
- QString message = app.arguments().size() > 1 ? app.arguments().at(1) : QString();
- if (message == QLatin1String("--help")) {
- MainWindow::printHelp();
- return 0;
+ QString message;
+ if (app.arguments().size() > 1) {
+ message = app.arguments().at(1);
+ if (message == QLatin1String("--help")) {
+ MainWindow::printHelp();
+ return 0;
+ }
}
- if (app.sendMessage(message))
- return 0;
+ if (app.sendMessage(message)) return 0;
app.setApplicationName(Constants::NAME);
app.setOrganizationName(Constants::ORG_NAME);
cssFile.open(QFile::ReadOnly);
QString styleSheet = QLatin1String(cssFile.readAll());
app.setStyleSheet(styleSheet);
+ cssFile.close();
#endif
// qt translations
QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
- // app translations
+ QString pkgDataDir;
#ifdef PKGDATADIR
- QString dataDir = QLatin1String(PKGDATADIR);
-#else
- QString dataDir = "";
+ pkgDataDir = QLatin1String(PKGDATADIR);
#endif
+
+ // app translations
#ifdef APP_MAC
QString localeDir = qApp->applicationDirPath() + QLatin1String("/../Resources/locale");
#else
QString localeDir = qApp->applicationDirPath() + QLatin1String("/locale");
#endif
if (!QDir(localeDir).exists()) {
- localeDir = dataDir + QLatin1String("/locale");
+ localeDir = pkgDataDir + QLatin1String("/locale");
}
- // qDebug() << "Using locale dir" << localeDir << locale;
+ qDebug() << "Using locale dir" << localeDir << QLocale::system();
QTranslator translator;
translator.load(QLocale::system(), QString(), QString(), localeDir);
app.installTranslator(&translator);
QNetworkProxyFactory::setUseSystemConfiguration(true);
- showWindow(app, dataDir);
+ showWindow(app, pkgDataDir);
return app.exec();
}
#endif
#include <iostream>
#ifdef APP_EXTRA
+#include "compositefader.h"
#include "extra.h"
#include "updatedialog.h"
#endif
#include "seekslider.h"
#include "sidebarwidget.h"
#include "toolbarmenu.h"
-#include "videoareawidget.h"
+#include "videoarea.h"
#include "yt3.h"
#include "ytregions.h"
+#ifdef MEDIA_QTAV
+#include "mediaqtav.h"
+#endif
+#ifdef MEDIA_MPV
+#include "mediampv.h"
+#endif
+
namespace {
-static MainWindow *mainWindowInstance;
+MainWindow *mainWindowInstance;
}
MainWindow *MainWindow::instance() {
}
MainWindow::MainWindow()
- : updateChecker(0), aboutView(0), downloadView(0), regionsView(0), mainToolBar(0),
-#ifdef APP_PHONON
- mediaObject(0), audioOutput(0),
-#endif
- fullScreenActive(false), compactModeActive(false), initialized(false), toolbarMenu(0) {
-
+ : aboutView(nullptr), downloadView(nullptr), regionsView(nullptr), mainToolBar(nullptr),
+ fullScreenActive(false), compactModeActive(false), initialized(false), toolbarMenu(nullptr),
+ media(nullptr) {
mainWindowInstance = this;
// views mechanism
views = new QStackedWidget();
- views->hide();
setCentralWidget(views);
#ifdef APP_EXTRA
Extra::windowSetup(this);
#endif
- messageLabel = new QLabel();
- messageLabel->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
- messageLabel->setStyleSheet("padding:5px;border:1px solid #808080;background:palette(window)");
+ messageLabel = new QLabel(this);
+ messageLabel->setWordWrap(false);
+ messageLabel->setStyleSheet("padding:5px;border:0;background:palette(window)");
+ messageLabel->setAlignment(Qt::AlignCenter);
messageLabel->hide();
adjustMessageLabelPosition();
messageTimer = new QTimer(this);
// build ui
createActions();
createMenus();
- createToolBars();
+ createToolBar();
hideToolbar();
createStatusBar();
views->widget(i)->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
setMinimumWidth(0);
- views->show();
-
#ifdef APP_ACTIVATION
Activation::instance().initialCheck();
#else
showHome();
#endif
- QTimer::singleShot(100, this, &MainWindow::lazyInit);
+ QTimer::singleShot(1000, this, &MainWindow::lazyInit);
}
void MainWindow::lazyInit() {
-#ifdef APP_PHONON
- initPhonon();
-#endif
mediaView->initialize();
-#ifdef APP_PHONON
- mediaView->setMediaObject(mediaObject);
-#endif
+ initMedia();
qApp->processEvents();
// CLI
getAction("minimize")->setEnabled(!isMinimized());
}
#endif
+ if (messageLabel->isVisible()) {
+ if (e->type() == QEvent::ActivationChange || e->type() == QEvent::WindowStateChange ||
+ e->type() == QEvent::WindowDeactivate || e->type() == QEvent::ApplicationStateChange) {
+ hideMessage();
+ }
+ }
QMainWindow::changeEvent(e);
}
toolbarMenu->move(mapToGlobal(p));
}
+ if (obj == this && t == QEvent::StyleChange) {
+ qDebug() << "Style change detected";
+ qApp->paletteChanged(qApp->palette());
+ return false;
+ }
+
// standard event processing
return QMainWindow::eventFilter(obj, e);
}
void MainWindow::createActions() {
- stopAct = new QAction(IconUtils::icon("media-playback-stop"), tr("&Stop"), this);
+ stopAct = new QAction(tr("&Stop"), this);
+ IconUtils::setIcon(stopAct, "media-playback-stop");
stopAct->setStatusTip(tr("Stop playback and go back to the search view"));
- stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape)
- << QKeySequence(Qt::Key_MediaStop));
+ stopAct->setShortcuts(QList<QKeySequence>()
+ << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
stopAct->setEnabled(false);
actionMap.insert("stop", stopAct);
connect(stopAct, SIGNAL(triggered()), SLOT(stop()));
- skipBackwardAct = new QAction(IconUtils::icon("media-skip-backward"), tr("P&revious"), this);
+ skipBackwardAct = new QAction(tr("P&revious"), this);
skipBackwardAct->setStatusTip(tr("Go back to the previous track"));
skipBackwardAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Left));
skipBackwardAct->setEnabled(false);
actionMap.insert("previous", skipBackwardAct);
connect(skipBackwardAct, SIGNAL(triggered()), mediaView, SLOT(skipBackward()));
- skipAct = new QAction(IconUtils::icon("media-skip-forward"), tr("S&kip"), this);
+ skipAct = new QAction(tr("S&kip"), this);
+ IconUtils::setIcon(skipAct, "media-skip-forward");
skipAct->setStatusTip(tr("Skip to the next video"));
skipAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_Right)
<< QKeySequence(Qt::Key_MediaNext));
actionMap.insert("skip", skipAct);
connect(skipAct, SIGNAL(triggered()), mediaView, SLOT(skip()));
- pauseAct = new QAction(IconUtils::icon("media-playback-start"), tr("&Play"), this);
+ pauseAct = new QAction(tr("&Play"), this);
+ IconUtils::setIcon(pauseAct, "media-playback-start");
pauseAct->setStatusTip(tr("Resume playback"));
- pauseAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Space)
- << QKeySequence(Qt::Key_MediaPlay));
+ pauseAct->setShortcuts(QList<QKeySequence>()
+ << QKeySequence(Qt::Key_Space) << QKeySequence(Qt::Key_MediaPlay));
pauseAct->setEnabled(false);
actionMap.insert("pause", pauseAct);
connect(pauseAct, SIGNAL(triggered()), mediaView, SLOT(pause()));
- fullscreenAct = new QAction(IconUtils::icon("view-fullscreen"), tr("&Full Screen"), this);
+ fullscreenAct = new QAction(tr("&Full Screen"), this);
+ IconUtils::setIcon(fullscreenAct, "view-fullscreen");
fullscreenAct->setStatusTip(tr("Go full screen"));
QList<QKeySequence> fsShortcuts;
#ifdef APP_MAC
compactViewAct = new QAction(tr("&Compact Mode"), this);
compactViewAct->setStatusTip(tr("Hide the playlist and the toolbar"));
-#ifdef APP_MAC
- compactViewAct->setShortcut(QKeySequence(Qt::CTRL + Qt::META + Qt::Key_C));
-#else
compactViewAct->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C));
-#endif
compactViewAct->setCheckable(true);
compactViewAct->setChecked(false);
compactViewAct->setEnabled(false);
actionMap.insert("webpage", webPageAct);
connect(webPageAct, SIGNAL(triggered()), mediaView, SLOT(openWebPage()));
- copyPageAct = new QAction(IconUtils::icon("link"), tr("Copy the YouTube &Link"), this);
+ copyPageAct = new QAction(tr("Copy the YouTube &Link"), this);
+ IconUtils::setIcon(copyPageAct, "link");
copyPageAct->setStatusTip(tr("Copy the current video YouTube link to the clipboard"));
copyPageAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L));
copyPageAct->setEnabled(false);
removeAct = new QAction(tr("&Remove"), this);
removeAct->setStatusTip(tr("Remove the selected videos from the playlist"));
- removeAct->setShortcuts(QList<QKeySequence>() << QKeySequence("Del")
- << QKeySequence("Backspace"));
+ removeAct->setShortcuts(QList<QKeySequence>()
+ << QKeySequence("Del") << QKeySequence("Backspace"));
removeAct->setEnabled(false);
actionMap.insert("remove", removeAct);
connect(removeAct, SIGNAL(triggered()), mediaView, SLOT(removeSelected()));
addAction(volumeDownAct);
volumeMuteAct = new QAction(this);
- volumeMuteAct->setIcon(IconUtils::icon("audio-volume-high"));
+ IconUtils::setIcon(volumeMuteAct, "audio-volume-high");
volumeMuteAct->setStatusTip(tr("Mute volume"));
- volumeMuteAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K));
+ volumeMuteAct->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M));
actionMap.insert("volumeMute", volumeMuteAct);
- connect(volumeMuteAct, SIGNAL(triggered()), SLOT(volumeMute()));
+ connect(volumeMuteAct, SIGNAL(triggered()), SLOT(toggleVolumeMute()));
addAction(volumeMuteAct);
- QAction *definitionAct = new QAction(this);
- definitionAct->setIcon(IconUtils::icon("video-display"));
+ QToolButton *definitionButton = new QToolButton(this);
+ definitionButton->setText(YT3::instance().maxVideoDefinition().getName());
+ IconUtils::setIcon(definitionButton, "video-display");
+ definitionButton->setIconSize(QSize(16, 16));
+ definitionButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ definitionButton->setPopupMode(QToolButton::InstantPopup);
+ QMenu *definitionMenu = new QMenu(this);
+ QActionGroup *group = new QActionGroup(this);
+ for (auto &defName : VideoDefinition::getDefinitionNames()) {
+ QAction *a = new QAction(defName);
+ a->setCheckable(true);
+ a->setActionGroup(group);
+ a->setChecked(defName == YT3::instance().maxVideoDefinition().getName());
+ connect(a, &QAction::triggered, this, [this, defName, definitionButton] {
+ setDefinitionMode(defName);
+ definitionButton->setText(defName);
+ });
+ connect(&YT3::instance(), &YT3::maxVideoDefinitionChanged, this,
+ [defName, definitionButton](const QString &name) {
+ if (defName == name) definitionButton->setChecked(true);
+ });
+ definitionMenu->addAction(a);
+ }
+ definitionButton->setMenu(definitionMenu);
+ QWidgetAction *definitionAct = new QWidgetAction(this);
+ definitionAct->setDefaultWidget(definitionButton);
definitionAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_D));
actionMap.insert("definition", definitionAct);
- connect(definitionAct, SIGNAL(triggered()), SLOT(toggleDefinitionMode()));
addAction(definitionAct);
QAction *action;
- action = new QAction(IconUtils::icon("media-playback-start"), tr("&Manually Start Playing"),
- this);
+ action = new QAction(tr("&Manually Start Playing"), this);
+ IconUtils::setIcon(action, "media-playback-start");
action->setStatusTip(tr("Manually start playing videos"));
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T));
action->setCheckable(true);
actionMap.insert("manualplay", action);
action = new QAction(tr("&Downloads"), this);
+ IconUtils::setIcon(action, "document-save");
action->setStatusTip(tr("Show details about video downloads"));
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));
action->setCheckable(true);
- action->setIcon(IconUtils::icon("document-save"));
connect(action, SIGNAL(toggled(bool)), SLOT(toggleDownloads(bool)));
actionMap.insert("downloads", action);
action = new QAction(tr("&Download"), this);
+ IconUtils::setIcon(action, "document-save");
action->setStatusTip(tr("Download the current video"));
action->setShortcut(QKeySequence::Save);
- action->setIcon(IconUtils::icon("document-save"));
action->setEnabled(false);
action->setVisible(false);
action->setPriority(QAction::LowPriority);
action->setEnabled(false);
connect(action, SIGNAL(triggered()), mediaView, SLOT(toggleSubscription()));
actionMap.insert("subscribeChannel", action);
- mediaView->updateSubscriptionAction(0, false);
+ mediaView->updateSubscriptionActionForVideo(0, false);
QString shareTip = tr("Share the current video using %1");
- action = new QAction(IconUtils::icon("twitter"), "&Twitter", this);
+ action = new QAction("&Twitter", this);
+ IconUtils::setIcon(action, "twitter");
action->setStatusTip(shareTip.arg("Twitter"));
action->setEnabled(false);
actionMap.insert("twitter", action);
connect(action, SIGNAL(triggered()), mediaView, SLOT(shareViaTwitter()));
- action = new QAction(IconUtils::icon("facebook"), "&Facebook", this);
+ action = new QAction("&Facebook", this);
+ IconUtils::setIcon(action, "facebook");
action->setStatusTip(shareTip.arg("Facebook"));
action->setEnabled(false);
actionMap.insert("facebook", action);
connect(action, SIGNAL(triggered()), mediaView, SLOT(shareViaFacebook()));
- action = new QAction(IconUtils::icon("email"), tr("&Email"), this);
+ action = new QAction(tr("&Email"), this);
+ IconUtils::setIcon(action, "email");
action->setStatusTip(shareTip.arg(tr("Email")));
action->setEnabled(false);
actionMap.insert("email", action);
actionMap.insert("restore", action);
connect(action, SIGNAL(triggered()), SLOT(restore()));
- action = new QAction(IconUtils::icon("go-top"), tr("&Float on Top"), this);
+ action = new QAction(tr("&Float on Top"), this);
+ IconUtils::setIcon(action, "go-top");
action->setCheckable(true);
actionMap.insert("ontop", action);
connect(action, SIGNAL(toggled(bool)), SLOT(floatOnTop(bool)));
- action =
- new QAction(IconUtils::icon("media-playback-stop"), tr("&Stop After This Video"), this);
+ action = new QAction(tr("&Stop After This Video"), this);
+ IconUtils::setIcon(action, "media-playback-stop");
action->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_Escape));
action->setCheckable(true);
action->setEnabled(false);
action = new QAction(tr("More..."), this);
actionMap.insert("moreRegion", action);
- action = new QAction(IconUtils::icon("view-list"), tr("&Related Videos"), this);
+ action = new QAction(tr("&Related Videos"), this);
+ IconUtils::setIcon(action, "view-list");
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
action->setStatusTip(tr("Watch videos related to the current one"));
action->setEnabled(false);
actionMap.insert("openInBrowser", action);
connect(action, SIGNAL(triggered()), mediaView, SLOT(openInBrowser()));
- action = new QAction(IconUtils::icon("safesearch"), tr("Restricted Mode"), this);
+ action = new QAction(tr("Restricted Mode"), this);
+ IconUtils::setIcon(action, "safesearch");
action->setStatusTip(tr("Hide videos that may contain inappropriate content"));
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K));
action->setCheckable(true);
connect(action, SIGNAL(triggered()), SLOT(toggleMenuVisibilityWithMessage()));
actionMap.insert("toggleMenu", action);
- action = new QAction(IconUtils::icon("view-more"), tr("Menu"), this);
+ action = new QAction(tr("Menu"), this);
+ IconUtils::setIcon(action, "open-menu");
connect(action, SIGNAL(triggered()), SLOT(toggleToolbarMenu()));
actionMap.insert("toolbarMenu", action);
#endif
// common action properties
- for (QAction *action : actionMap) {
+ for (QAction *action : qAsConst(actionMap)) {
// add actions to the MainWindow so that they work
// when the menu is hidden
addAction(action);
- IconUtils::setupAction(action);
+ MainWindow::instance()->setupAction(action);
}
}
menuMap.insert("view", viewMenu);
viewMenu->addAction(getAction("ontop"));
viewMenu->addAction(compactViewAct);
-#ifndef APP_MAC
+ viewMenu->addSeparator();
viewMenu->addAction(fullscreenAct);
+#ifndef APP_MAC
+ viewMenu->addSeparator();
viewMenu->addAction(getAction("toggleMenu"));
#endif
#endif
}
-void MainWindow::createToolBars() {
+void MainWindow::createToolBar() {
// Create widgets
+ currentTimeLabel = new QLabel("00:00", this);
- currentTimeLabel = new QLabel("00:00");
- currentTimeLabel->setFont(FontUtils::small());
-
-#ifdef APP_PHONON_SEEK
- seekSlider = new Phonon::SeekSlider();
-#if APP_LINUX
+ seekSlider = new SeekSlider(this);
+ seekSlider->setEnabled(false);
seekSlider->setTracking(false);
-#else
- seekSlider->setTracking(true);
-#endif
- // Phonon freezes the application with streaming videos if
- // tracking is set to true and the seek slider is dragged.
- seekSlider->setIconVisible(false);
- seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
-#else
- slider = new SeekSlider(this);
- slider->setEnabled(false);
- slider->setTracking(false);
- slider->setMaximum(1000);
- slider->setOrientation(Qt::Horizontal);
- slider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
-#endif
-
-#ifdef APP_PHONON
- volumeSlider = new Phonon::VolumeSlider();
- volumeSlider->setMuteVisible(false);
- // qDebug() << volumeSlider->children();
- // status tip for the volume slider
- QSlider *volumeQSlider = volumeSlider->findChild<QSlider *>();
- if (volumeQSlider)
- volumeQSlider->setStatusTip(
- tr("Press %1 to raise the volume, %2 to lower it")
- .arg(volumeUpAct->shortcut().toString(QKeySequence::NativeText),
- volumeDownAct->shortcut().toString(QKeySequence::NativeText)));
- // this makes the volume slider smaller
- volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-#endif
+ seekSlider->setMaximum(1000);
+ volumeSlider = new SeekSlider(this);
+ volumeSlider->setValue(volumeSlider->maximum());
#if defined(APP_MAC_SEARCHFIELD) && !defined(APP_MAC_QMACTOOLBAR)
SearchWrapper *searchWrapper = new SearchWrapper(this);
toolbarSearch = new SearchLineEdit(this);
#endif
toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize() * 15);
+ toolbarSearch->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
toolbarSearch->setSuggester(new YTSuggester(this));
connect(toolbarSearch, SIGNAL(search(const QString &)), SLOT(search(const QString &)));
connect(toolbarSearch, SIGNAL(suggestionAccepted(Suggestion *)),
SLOT(suggestionAccepted(Suggestion *)));
toolbarSearch->setStatusTip(searchFocusAct->statusTip());
-// Add widgets to toolbar
+ // Add widgets to toolbar
#ifdef APP_MAC_QMACTOOLBAR
currentTimeLabel->hide();
mainToolBar->addWidget(new Spacer());
+ currentTimeLabel->setFont(FontUtils::small());
+ currentTimeLabel->setMinimumWidth(currentTimeLabel->fontInfo().pixelSize() * 4);
+ currentTimeLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
mainToolBar->addWidget(currentTimeLabel);
- mainToolBar->addWidget(new Spacer(this, currentTimeLabel->sizeHint().height() / 2));
-#ifdef APP_PHONON_SEEK
- mainToolBar->addWidget(seekSlider);
-#else
- mainToolBar->addWidget(slider);
+
+#ifdef APP_WIN
+ mainToolBar->addWidget(new Spacer(nullptr, 10));
#endif
- /*
- mainToolBar->addWidget(new Spacer());
- totalTime = new QLabel(mainToolBar);
- totalTime->setFont(smallerFont);
- mainToolBar->addWidget(totalTime);
- */
+ seekSlider->setOrientation(Qt::Horizontal);
+ seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+ seekSlider->setFocusPolicy(Qt::NoFocus);
+ mainToolBar->addWidget(seekSlider);
mainToolBar->addWidget(new Spacer());
+
mainToolBar->addAction(volumeMuteAct);
-#ifdef APP_LINUX
+#ifndef APP_MAC_QMACTOOLBAR
QToolButton *volumeMuteButton =
qobject_cast<QToolButton *>(mainToolBar->widgetForAction(volumeMuteAct));
- volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16));
+ volumeMuteButton->setIconSize(QSize(16, 16));
+ auto fixVolumeMuteIconSize = [volumeMuteButton] {
+ volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16));
+ };
+ fixVolumeMuteIconSize();
+ volumeMuteButton->connect(volumeMuteAct, &QAction::changed, volumeMuteButton,
+ fixVolumeMuteIconSize);
#endif
-#ifdef APP_PHONON
+ volumeSlider->setStatusTip(
+ tr("Press %1 to raise the volume, %2 to lower it")
+ .arg(volumeUpAct->shortcut().toString(QKeySequence::NativeText),
+ volumeDownAct->shortcut().toString(QKeySequence::NativeText)));
+
+ volumeSlider->setOrientation(Qt::Horizontal);
+ // this makes the volume slider smaller
+ volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ volumeSlider->setFocusPolicy(Qt::NoFocus);
mainToolBar->addWidget(volumeSlider);
-#endif
mainToolBar->addWidget(new Spacer());
regionMenu->addAction(moreRegionsAction);
connect(moreRegionsAction, SIGNAL(triggered()), SLOT(showRegionsView()));
regionAction->setMenu(regionMenu);
- } else {
- connect(regionAction, SIGNAL(triggered()), SLOT(showRegionsView()));
}
+ connect(regionAction, SIGNAL(triggered()), SLOT(showRegionsView()));
/* Stupid code that generates the QRC items
foreach(YTRegion r, YTRegions::list())
void MainWindow::showStopAfterThisInStatusBar(bool show) {
QAction *action = getAction("stopafterthis");
- showActionInStatusBar(action, show);
+ showActionsInStatusBar({action}, show);
}
-void MainWindow::showActionInStatusBar(QAction *action, bool show) {
+void MainWindow::showActionsInStatusBar(const QVector<QAction *> &actions, bool show) {
#ifdef APP_EXTRA
Extra::fadeInWidget(statusBar(), statusBar());
#endif
- if (show) {
- if (statusToolBar->actions().contains(action)) return;
- if (statusToolBar->actions().isEmpty()) {
- statusToolBar->addAction(action);
+ for (auto action : actions) {
+ if (show) {
+ if (statusToolBar->actions().contains(action)) continue;
+ if (statusToolBar->actions().isEmpty()) {
+ statusToolBar->addAction(action);
+ } else {
+ statusToolBar->insertAction(statusToolBar->actions().at(0), action);
+ }
} else {
- statusToolBar->insertAction(statusToolBar->actions().at(0), action);
+ statusToolBar->removeAction(action);
}
+ }
+
+ if (show) {
if (statusBar()->isHidden() && !fullScreenActive) setStatusBarVisibility(true);
} else {
- statusToolBar->removeAction(action);
if (statusBar()->isVisible() && !needStatusBar()) setStatusBarVisibility(false);
}
}
void MainWindow::setStatusBarVisibility(bool show) {
- statusBar()->setVisible(show);
- if (views->currentWidget() == mediaView)
- QTimer::singleShot(0, mediaView, SLOT(adjustWindowSize()));
+ if (statusBar()->isVisible() != show) {
+ statusBar()->setVisible(show);
+ if (views->currentWidget() == mediaView)
+ QTimer::singleShot(0, mediaView, SLOT(adjustWindowSize()));
+ }
}
void MainWindow::adjustStatusBarVisibility() {
void MainWindow::readSettings() {
QSettings settings;
- if (settings.contains("geometry")) {
- restoreGeometry(settings.value("geometry").toByteArray());
+ QByteArray geometrySettings = settings.value("geometry").toByteArray();
+ if (!geometrySettings.isEmpty()) {
+ restoreGeometry(geometrySettings);
} else {
- const QRect desktopSize = qApp->desktop()->availableGeometry();
+ const QRect desktopSize = QGuiApplication::primaryScreen()->availableGeometry();
int w = desktopSize.width() * .9;
int h = qMin(w / 2, desktopSize.height());
setGeometry(
QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, QSize(w, h), desktopSize));
}
- const VideoDefinition &firstDefinition = VideoDefinition::getDefinitions().at(0);
- setDefinitionMode(settings.value("definition", firstDefinition.getName()).toString());
+ setDefinitionMode(settings.value("definition", YT3::instance().maxVideoDefinition().getName())
+ .toString());
getAction("manualplay")->setChecked(settings.value("manualplay", false).toBool());
getAction("safeSearch")->setChecked(settings.value("safeSearch", false).toBool());
#ifndef APP_MAC
if (!isReallyFullScreen()) {
settings.setValue("geometry", saveGeometry());
- mediaView->saveSplitterState();
+ if (mediaView) mediaView->saveSplitterState();
}
settings.setValue("manualplay", getAction("manualplay")->isChecked());
void MainWindow::goBack() {
if (history.size() > 1) {
history.pop();
- QWidget *widget = history.pop();
- showWidget(widget);
+ showView(history.pop());
}
}
-void MainWindow::showWidget(QWidget *widget, bool transition) {
- Q_UNUSED(transition);
+void MainWindow::showView(View *view, bool transition) {
+ if (!history.isEmpty() && view == history.top()) {
+ qDebug() << "Attempting to show same view" << view;
+ return;
+ }
- setUpdatesEnabled(false);
+#ifdef APP_MAC
+ if (transition && !history.isEmpty()) CompositeFader::go(this, this->grab());
+#endif
if (compactViewAct->isChecked()) compactViewAct->toggle();
View *oldView = qobject_cast<View *>(views->currentWidget());
if (oldView) {
oldView->disappear();
- views->currentWidget()->setEnabled(false);
+ oldView->setEnabled(false);
+ oldView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
} else
- qDebug() << "Cannot cast view";
+ qDebug() << "Cannot cast old view";
- const bool isMediaView = widget == mediaView;
+ view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ view->setEnabled(true);
+ views->setCurrentWidget(view);
+ view->appear();
+ QString title = view->getTitle();
+ if (title.isEmpty())
+ title = Constants::NAME;
+ else
+ title += QLatin1String(" - ") + Constants::NAME;
+ setWindowTitle(title);
+
+ const bool isMediaView = view == mediaView;
stopAct->setEnabled(isMediaView);
compactViewAct->setEnabled(isMediaView);
- toolbarSearch->setEnabled(widget == homeView || isMediaView || widget == downloadView);
-
- aboutAct->setEnabled(widget != aboutView);
- getAction("downloads")->setChecked(widget == downloadView);
-
- QWidget *oldWidget = views->currentWidget();
- if (oldWidget) oldWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
-
- views->setCurrentWidget(widget);
- widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- // call show method on the new view
- View *newView = qobject_cast<View *>(widget);
- if (newView) {
- widget->setEnabled(true);
-
- QString title = newView->getTitle();
- if (title.isEmpty())
- title = Constants::NAME;
- else
- title += QLatin1String(" - ") + Constants::NAME;
- setWindowTitle(title);
-
- statusToolBar->setUpdatesEnabled(false);
-
- // dynamic view actions
- /* Not currently used by any view
- foreach (QAction* action, viewActions)
- showActionInStatusBar(action, false);
- viewActions = newView->getViewActions();
- foreach (QAction* action, viewActions)
- showActionInStatusBar(action, true);
- */
-
- adjustStatusBarVisibility();
- messageLabel->hide();
-
- newView->appear();
-
- statusToolBar->setUpdatesEnabled(true);
-
- /*
- QString desc = metadata.value("description").toString();
- if (!desc.isEmpty()) showMessage(desc);
- */
- }
-
- setUpdatesEnabled(true);
-
-#ifdef APP_MAC
- // Workaround cursor bug on macOS
- unsetCursor();
- mac::uncloseWindow(winId());
-#endif
+ toolbarSearch->setEnabled(isMediaView);
+ aboutAct->setEnabled(view != aboutView);
+ getAction("downloads")->setChecked(view == downloadView);
+
+ // dynamic view actions
+ /* Not currently used by any view
+ showActionsInStatusBar(viewActions, false);
+ viewActions = newView->getViewActions();
+ showActionsInStatusBar(viewActions, true);
+ */
- history.push(widget);
+ history.push(view);
emit viewChanged();
}
aboutView = new AboutView(this);
views->addWidget(aboutView);
}
- showWidget(aboutView);
+ showView(aboutView);
}
void MainWindow::visitSite() {
}
void MainWindow::reportIssue() {
- QUrl url("http://flavio.tordini.org/forums/forum/minitube-forums/minitube-troubleshooting");
+ QUrl url("https://flavio.tordini.org/forums/forum/minitube-forums/minitube-troubleshooting");
QDesktopServices::openUrl(url);
}
#endif
// do not save geometry when in full screen or in compact mode
if (!fullScreenActive && !compactViewAct->isChecked()) {
+#ifdef APP_MAC
+ hideToolbar();
+#endif
writeSettings();
}
// mediaView->stop();
ChannelAggregator::instance()->stop();
ChannelAggregator::instance()->cleanup();
Database::shutdown();
+ HttpUtils::clearCaches();
qApp->quit();
}
bool MainWindow::confirmQuit() {
if (DownloadManager::instance()->activeItems() > 0) {
QMessageBox msgBox(this);
- msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png"));
+ msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF()));
msgBox.setText(
tr("Do you want to exit %1 with a download in progress?").arg(Constants::NAME));
msgBox.setInformativeText(
}
void MainWindow::showHome() {
- showWidget(homeView);
+ showView(homeView);
currentTimeLabel->clear();
+ seekSlider->setValue(0);
}
void MainWindow::showMedia(SearchParams *searchParams) {
- showWidget(mediaView);
+ showView(mediaView);
if (getAction("safeSearch")->isChecked())
searchParams->setSafeSearch(SearchParams::Strict);
else
}
void MainWindow::showMedia(VideoSource *videoSource) {
- showWidget(mediaView);
+ showView(mediaView);
mediaView->setVideoSource(videoSource);
}
-#ifdef APP_PHONON
-void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState */) {
- // qDebug() << "Phonon state: " << newState;
+void MainWindow::stateChanged(Media::State newState) {
+ qDebug() << newState;
+
+ seekSlider->setEnabled(newState != Media::StoppedState);
switch (newState) {
- case Phonon::ErrorState:
- if (mediaObject->errorType() == Phonon::FatalError) {
- // Do not display because we try to play incomplete video files and sometimes trigger
- // this
- // We retry automatically (in MediaView) so no need to show it
- // showMessage(tr("Fatal error: %1").arg(mediaObject->errorString()));
- } else {
- showMessage(tr("Error: %1").arg(mediaObject->errorString()));
- }
+ case Media::ErrorState:
+ showMessage(tr("Error: %1").arg(media->errorString()));
break;
- case Phonon::PlayingState:
+ case Media::PlayingState:
pauseAct->setEnabled(true);
pauseAct->setIcon(IconUtils::icon("media-playback-pause"));
pauseAct->setText(tr("&Pause"));
pauseAct->setStatusTip(tr("Pause playback") + " (" +
pauseAct->shortcut().toString(QKeySequence::NativeText) + ")");
- // stopAct->setEnabled(true);
break;
- case Phonon::StoppedState:
+ case Media::StoppedState:
pauseAct->setEnabled(false);
pauseAct->setIcon(IconUtils::icon("media-playback-start"));
pauseAct->setText(tr("&Play"));
pauseAct->setStatusTip(tr("Resume playback") + " (" +
pauseAct->shortcut().toString(QKeySequence::NativeText) + ")");
- // stopAct->setEnabled(false);
break;
- case Phonon::PausedState:
+ case Media::PausedState:
pauseAct->setEnabled(true);
pauseAct->setIcon(IconUtils::icon("media-playback-start"));
pauseAct->setText(tr("&Play"));
pauseAct->setStatusTip(tr("Resume playback") + " (" +
pauseAct->shortcut().toString(QKeySequence::NativeText) + ")");
- // stopAct->setEnabled(true);
break;
- case Phonon::BufferingState:
+ case Media::BufferingState:
pauseAct->setEnabled(false);
pauseAct->setIcon(IconUtils::icon("content-loading"));
pauseAct->setText(tr("&Loading..."));
pauseAct->setStatusTip(QString());
break;
- case Phonon::LoadingState:
+ case Media::LoadingState:
pauseAct->setEnabled(false);
currentTimeLabel->clear();
- // totalTime->clear();
- // stopAct->setEnabled(true);
break;
default:;
}
}
-#endif
void MainWindow::stop() {
showHome();
#endif
#ifdef APP_MAC_QMACTOOLBAR
int moreButtonWidth = 40;
- toolbarSearch->move(width() - toolbarSearch->width() - moreButtonWidth - 7, -38);
+ toolbarSearch->move(width() - toolbarSearch->width() - moreButtonWidth - 7, -34);
#endif
hideMessage();
}
-void MainWindow::moveEvent(QMoveEvent *e) {
- Q_UNUSED(e);
- hideMessage();
-}
-
void MainWindow::enterEvent(QEvent *e) {
Q_UNUSED(e);
#ifdef APP_MAC
#endif
} else {
-// Exit full screen
+ // Exit full screen
#ifdef APP_MAC
MacSupport::exitFullScreen(this, views);
if (fullScreenActive) {
stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_MediaStop));
} else {
- stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape)
- << QKeySequence(Qt::Key_MediaStop));
+ stopAct->setShortcuts(QList<QKeySequence>()
+ << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
}
#ifdef Q_OS_MAC
}
void MainWindow::missingKeyWarning() {
+ static bool shown = false;
+ if (shown) return;
+ shown = true;
QMessageBox msgBox(this);
- msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png"));
+ msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF()));
msgBox.setText(QString("%1 was built without a Google API key.").arg(Constants::NAME));
msgBox.setInformativeText(QString("It won't work unless you enter one."
"<p>In alternative you can get %1 from the developer site.")
.arg(Constants::NAME));
msgBox.setModal(true);
msgBox.setWindowModality(Qt::WindowModal);
+ msgBox.addButton(QMessageBox::Close);
QPushButton *enterKeyButton =
msgBox.addButton(QString("Enter API key..."), QMessageBox::AcceptRole);
QPushButton *devButton = msgBox.addButton(QString("Get from %1").arg(Constants::WEBSITE),
QMessageBox::AcceptRole);
QPushButton *helpButton = msgBox.addButton(QMessageBox::Help);
+
msgBox.exec();
+
if (msgBox.clickedButton() == helpButton) {
- QDesktopServices::openUrl(QUrl(
- "https://github.com/flaviotordini/minitube/blob/master/README.md#google-api-key"));
+ QDesktopServices::openUrl(QUrl("https://github.com/flaviotordini/minitube/blob/master/"
+ "README.md#google-api-key"));
} else if (msgBox.clickedButton() == enterKeyButton) {
bool ok;
QString text = QInputDialog::getText(this, QString(), "Google API key:", QLineEdit::Normal,
} else if (msgBox.clickedButton() == devButton) {
QDesktopServices::openUrl(QUrl(Constants::WEBSITE));
}
+ shown = false;
}
void MainWindow::compactView(bool enable) {
+ setUpdatesEnabled(false);
+
compactModeActive = enable;
static QList<QKeySequence> compactShortcuts;
if (settings.contains(key))
restoreGeometry(settings.value(key).toByteArray());
else
- resize(320, 180);
+ resize(480, 270);
#ifdef APP_MAC_QMACTOOLBAR
mac::showToolBar(winId(), !enable);
mediaView->setFocus();
} else {
+ settings.setValue(key, saveGeometry());
+
// unset minimum size
setMinimumSize(0, 0);
+
#ifdef Q_OS_MAC
mac::SetupFullScreenWindow(winId());
#endif
- settings.setValue(key, saveGeometry());
#ifdef APP_MAC_QMACTOOLBAR
mac::showToolBar(winId(), !enable);
#else
menuBar()->setVisible(menuVisibleBeforeCompactMode);
}
#endif
+
+ setUpdatesEnabled(true);
}
void MainWindow::toggleToolbarMenu() {
toolbarSearch->setFocus();
}
-#ifdef APP_PHONON
-void MainWindow::initPhonon() {
- // Phonon initialization
- if (mediaObject) delete mediaObject;
- if (audioOutput) delete audioOutput;
- mediaObject = new Phonon::MediaObject(this);
- mediaObject->setTickInterval(100);
- connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
- SLOT(stateChanged(Phonon::State, Phonon::State)));
- connect(mediaObject, SIGNAL(tick(qint64)), SLOT(tick(qint64)));
- connect(mediaObject, SIGNAL(totalTimeChanged(qint64)), SLOT(totalTimeChanged(qint64)));
-
- audioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this);
- connect(audioOutput, SIGNAL(mutedChanged(bool)), SLOT(volumeMutedChanged(bool)));
- Phonon::createPath(mediaObject, audioOutput);
- volumeSlider->setAudioOutput(audioOutput);
-
-#ifdef APP_PHONON_SEEK
- seekSlider->setMediaObject(mediaObject);
+void MainWindow::initMedia() {
+#ifdef MEDIA_QTAV
+ qFatal("QtAV has a showstopper bug. Audio stops randomly. See bug "
+ "https://github.com/wang-bin/QtAV/issues/1184");
+ media = new MediaQtAV(this);
+#elif defined MEDIA_MPV
+ media = new MediaMPV();
+#else
+ qFatal("No media backend defined");
#endif
+ media->init();
+ media->setUserAgent(HttpUtils::stealthUserAgent());
QSettings settings;
- audioOutput->setVolume(settings.value("volume", 1.).toReal());
- // audioOutput->setMuted(settings.value("volumeMute").toBool());
-
- mediaObject->stop();
+ qreal volume = settings.value("volume", 1.).toReal();
+ media->setVolume(volume);
+
+ connect(media, &Media::error, this, &MainWindow::handleError);
+ connect(media, &Media::stateChanged, this, &MainWindow::stateChanged);
+ connect(media, &Media::positionChanged, this, &MainWindow::tick);
+
+ connect(seekSlider, &QSlider::sliderMoved, this, [this](int value) {
+ // value : maxValue = posit ion : duration
+ qint64 ms = (value * media->duration()) / seekSlider->maximum();
+ qDebug() << "Seeking to" << ms;
+ media->seek(ms);
+ if (media->state() == Media::PausedState) media->play();
+ });
+ connect(seekSlider, &QSlider::sliderPressed, this, [this]() {
+ // value : maxValue = position : duration
+ qint64 ms = (seekSlider->value() * media->duration()) / seekSlider->maximum();
+ media->seek(ms);
+ if (media->state() == Media::PausedState) media->play();
+ });
+ connect(media, &Media::started, this, [this]() { seekSlider->setValue(0); });
+
+ connect(media, &Media::volumeChanged, this, &MainWindow::volumeChanged);
+ connect(media, &Media::volumeMutedChanged, this, &MainWindow::volumeMutedChanged);
+ connect(volumeSlider, &QSlider::sliderMoved, this, [this](int value) {
+ qreal volume = (qreal)value / volumeSlider->maximum();
+ media->setVolume(volume);
+ });
+ connect(volumeSlider, &QSlider::sliderPressed, this, [this]() {
+ qreal volume = (qreal)volumeSlider->value() / volumeSlider->maximum();
+ media->setVolume(volume);
+ });
+
+ mediaView->setMedia(media);
}
-#endif
void MainWindow::tick(qint64 time) {
+#ifdef APP_MAC
+ bool isDown = seekSlider->property("down").isValid();
+#else
+ bool isDown = seekSlider->isSliderDown();
+#endif
+ if (!isDown && media->state() == Media::PlayingState) {
+ // value : maxValue = position : duration
+ qint64 duration = media->duration();
+ if (duration <= 0) return;
+ int value = (seekSlider->maximum() * media->position()) / duration;
+ seekSlider->setValue(value);
+ }
+
const QString s = formatTime(time);
if (s != currentTimeLabel->text()) {
currentTimeLabel->setText(s);
emit currentTimeChanged(s);
- }
-
-// remaining time
-#ifdef APP_PHONON
- const qint64 remainingTime = mediaObject->remainingTime();
- currentTimeLabel->setStatusTip(tr("Remaining time: %1").arg(formatTime(remainingTime)));
-
-#ifndef APP_PHONON_SEEK
- const qint64 totalTime = mediaObject->totalTime();
- slider->blockSignals(true);
- // qWarning() << totalTime << time << time * 100 / totalTime;
- if (totalTime > 0 && time > 0 && !slider->isSliderDown() &&
- mediaObject->state() == Phonon::PlayingState)
- slider->setValue(time * slider->maximum() / totalTime);
- slider->blockSignals(false);
-#endif
-#endif
-}
-
-void MainWindow::totalTimeChanged(qint64 time) {
- if (time <= 0) {
- // totalTime->clear();
- return;
+ // remaining time
+ const qint64 remainingTime = media->remainingTime();
+ currentTimeLabel->setStatusTip(tr("Remaining time: %1").arg(formatTime(remainingTime)));
}
- // totalTime->setText(formatTime(time));
-
- /*
- slider->blockSignals(true);
- slider->setMaximum(time/1000);
- slider->blockSignals(false);
- */
}
QString MainWindow::formatTime(qint64 duration) {
}
void MainWindow::volumeUp() {
-#ifdef APP_PHONON
- qreal newVolume = volumeSlider->audioOutput()->volume() + .1;
- if (newVolume > volumeSlider->maximumVolume()) newVolume = volumeSlider->maximumVolume();
- volumeSlider->audioOutput()->setVolume(newVolume);
-#endif
+ qreal newVolume = media->volume() + .1;
+ if (newVolume > 1.) newVolume = 1.;
+ media->setVolume(newVolume);
}
void MainWindow::volumeDown() {
-#ifdef APP_PHONON
- qreal newVolume = volumeSlider->audioOutput()->volume() - .1;
- if (newVolume < 0.) newVolume = 0.;
- volumeSlider->audioOutput()->setVolume(newVolume);
-#endif
+ qreal newVolume = media->volume() - .1;
+ if (newVolume < 0) newVolume = 0;
+ media->setVolume(newVolume);
}
-void MainWindow::volumeMute() {
-#ifdef APP_PHONON
- bool muted = volumeSlider->audioOutput()->isMuted();
- volumeSlider->audioOutput()->setMuted(!muted);
- qApp->processEvents();
- if (muted && volumeSlider->audioOutput()->volume() == 0.) {
- volumeSlider->audioOutput()->setVolume(volumeSlider->maximumVolume());
- }
- qDebug() << volumeSlider->audioOutput()->isMuted() << volumeSlider->audioOutput()->volume();
-#endif
+void MainWindow::toggleVolumeMute() {
+ bool muted = media->volumeMuted();
+ media->setVolumeMuted(!muted);
+}
+
+void MainWindow::volumeChanged(qreal newVolume) {
+ // automatically unmute when volume changes
+ if (media->volumeMuted()) media->setVolumeMuted(false);
+ showMessage(tr("Volume at %1%").arg((int)(newVolume * 100)));
+ // newVolume : 1.0 = x : 1000
+ int value = newVolume * volumeSlider->maximum();
+ volumeSlider->blockSignals(true);
+ volumeSlider->setValue(value);
+ volumeSlider->blockSignals(false);
}
void MainWindow::volumeMutedChanged(bool muted) {
volumeMuteAct->setIcon(IconUtils::icon("audio-volume-high"));
showMessage(tr("Volume is unmuted"));
}
-#ifdef APP_LINUX
- QToolButton *volumeMuteButton =
- qobject_cast<QToolButton *>(mainToolBar->widgetForAction(volumeMuteAct));
- volumeMuteButton->setIcon(volumeMuteButton->icon().pixmap(16));
-#endif
}
void MainWindow::setDefinitionMode(const QString &definitionName) {
tr("Maximum video definition set to %1").arg(definitionAct->text()) + " (" +
definitionAct->shortcut().toString(QKeySequence::NativeText) + ")");
showMessage(definitionAct->statusTip());
- QSettings settings;
- settings.setValue("definition", definitionName);
+ YT3::instance().setMaxVideoDefinition(definitionName);
+ if (views->currentWidget() == mediaView) {
+ mediaView->reloadCurrentVideo();
+ }
}
void MainWindow::toggleDefinitionMode() {
- const QString definitionName = QSettings().value("definition").toString();
const QVector<VideoDefinition> &definitions = VideoDefinition::getDefinitions();
- const VideoDefinition ¤tDefinition = VideoDefinition::forName(definitionName);
- if (currentDefinition.isEmpty()) {
- setDefinitionMode(definitions.at(0).getName());
- return;
- }
+ const VideoDefinition ¤tDefinition = YT3::instance().maxVideoDefinition();
int index = definitions.indexOf(currentDefinition);
if (index != definitions.size() - 1) {
} else {
index = 0;
}
- // TODO: pass a VideoDefinition instead of QString.
setDefinitionMode(definitions.at(index).getName());
}
if (views->currentWidget() == homeView &&
homeView->currentWidget() == homeView->getSearchView())
return;
- showActionInStatusBar(getAction("manualplay"), enabled);
+ showActionsInStatusBar({getAction("manualplay")}, enabled);
}
void MainWindow::updateDownloadMessage(const QString &message) {
} else {
getAction("downloads")
->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J));
- stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape)
- << QKeySequence(Qt::Key_MediaStop));
+ stopAct->setShortcuts(QList<QKeySequence>()
+ << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
}
if (!downloadView) {
views->addWidget(downloadView);
}
if (show)
- showWidget(downloadView);
+ showView(downloadView);
else
goBack();
}
if (secondsSinceLastCheck < 86400) return;
// check it out
- if (updateChecker) delete updateChecker;
- updateChecker = new UpdateChecker();
- connect(updateChecker, SIGNAL(newVersion(QString)), this, SLOT(gotNewVersion(QString)));
+ UpdateChecker *updateChecker = new UpdateChecker();
+ connect(updateChecker, &UpdateChecker::newVersion, this,
+ [this, updateChecker](const QString &version) {
+ updateChecker->deleteLater();
+ QSettings settings;
+ QString checkedVersion = settings.value("checkedVersion").toString();
+ if (checkedVersion == version) return;
+#ifdef APP_SIMPLEUPDATE
+ simpleUpdateDialog(version);
+#elif defined(APP_EXTRA) && !defined(APP_MAC)
+ UpdateDialog *dialog = new UpdateDialog(version, this);
+ dialog->show();
+#endif
+ });
updateChecker->checkForUpdate();
settings.setValue(updateCheckKey, unixTime);
}
-void MainWindow::gotNewVersion(const QString &version) {
- if (updateChecker) {
- delete updateChecker;
- updateChecker = 0;
- }
-
- QSettings settings;
- QString checkedVersion = settings.value("checkedVersion").toString();
- if (checkedVersion == version) return;
-
-#ifdef APP_EXTRA
-#ifndef APP_MAC
- UpdateDialog *dialog = new UpdateDialog(version, this);
- dialog->show();
-#endif
-#else
- simpleUpdateDialog(version);
-#endif
-}
-
void MainWindow::simpleUpdateDialog(const QString &version) {
QMessageBox msgBox(this);
- msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png"));
+ msgBox.setIconPixmap(IconUtils::pixmap(":/images/64x64/app.png", devicePixelRatioF()));
msgBox.setText(tr("%1 version %2 is now available.").arg(Constants::NAME, version));
msgBox.setModal(true);
msgBox.setWindowModality(Qt::WindowModal);
}
void MainWindow::floatOnTop(bool onTop, bool showAction) {
- if (showAction) showActionInStatusBar(getAction("ontop"), onTop);
+ if (showAction) showActionsInStatusBar({getAction("ontop")}, onTop);
#ifdef APP_MAC
mac::floatOnTop(winId(), onTop);
#else
std::cout << msg.toLocal8Bit().data();
}
+void MainWindow::setupAction(QAction *action) {
+ // never autorepeat.
+ // unexperienced users tend to keep keys pressed for a "long" time
+ action->setAutoRepeat(false);
+
+ // show keyboard shortcuts in the status bar
+ if (!action->shortcut().isEmpty())
+ action->setStatusTip(action->statusTip() + QLatin1String(" (") +
+ action->shortcut().toString(QKeySequence::NativeText) +
+ QLatin1String(")"));
+}
+
QAction *MainWindow::getAction(const char *name) {
return actionMap.value(QByteArray::fromRawData(name, strlen(name)));
}
#endif
if (statusBar()->isVisible())
statusBar()->showMessage(message, 60000);
- else {
+ else if (isActiveWindow()) {
messageLabel->setText(message);
QSize size = messageLabel->sizeHint();
- // round width to nearest 10 to avoid flicker with fast changing messages (e.g. volume
+ // round width to avoid flicker with fast changing messages (e.g. volume
// changes)
- int w = size.width();
- const int multiple = 10;
+ int w = size.width() + 10;
+ const int multiple = 15;
w = w + multiple / 2;
w -= w % multiple;
size.setWidth(w);
}
}
+void MainWindow::handleError(const QString &message) {
+ qWarning() << message;
+ showMessage(message);
+}
+
#ifdef APP_ACTIVATION
void MainWindow::showActivationView() {
- QWidget *activationView = ActivationView::instance();
+ View *activationView = ActivationView::instance();
views->addWidget(activationView);
- if (views->currentWidget() != activationView) showWidget(activationView);
+ if (views->currentWidget() != activationView) showView(activationView);
}
#endif
SLOT(load()));
views->addWidget(regionsView);
}
- showWidget(regionsView);
+ showView(regionsView);
}
#define MAINWINDOW_H
#include <QtWidgets>
-#ifdef APP_PHONON
-#include <phonon/audiooutput.h>
-#include <phonon/volumeslider.h>
-#include <phonon/mediaobject.h>
-#include <phonon/seekslider.h>
-#endif
+#include "media.h"
+
+class View;
class HomeView;
class MediaView;
class DownloadView;
class SearchLineEdit;
-class UpdateChecker;
class SearchParams;
class VideoSource;
class Suggestion;
class ToolbarMenu;
class MainWindow : public QMainWindow {
-
Q_OBJECT
public:
- static MainWindow* instance();
+ static MainWindow *instance();
MainWindow();
-#ifdef APP_PHONON_SEEK
- Phonon::SeekSlider* getSeekSlider() { return seekSlider; }
-#else
- QSlider* getSlider() { return slider; }
-#endif
-#ifdef APP_PHONON
- Phonon::AudioOutput* getAudioOutput() { return audioOutput; }
- Phonon::VolumeSlider *getVolumeSlider() { return volumeSlider; }
-#endif
+
+ QSlider *getSeekSlider() { return seekSlider; }
+ QSlider *getVolumeSlider() { return volumeSlider; }
+
QLabel *getCurrentTimeLabel() { return currentTimeLabel; }
void readSettings();
void writeSettings();
static void printHelp();
QStackedWidget *getViews() { return views; }
- MediaView* getMediaView() { return mediaView; }
- HomeView* getHomeView() { return homeView; }
- QAction* getRegionAction() { return regionAction; }
+ MediaView *getMediaView() { return mediaView; }
+ HomeView *getHomeView() { return homeView; }
+ QAction *getRegionAction() { return regionAction; }
SearchLineEdit *getToolbarSearch() { return toolbarSearch; }
+ void setupAction(QAction *action);
QAction *getAction(const char *name);
void addNamedAction(const QByteArray &name, QAction *action);
QMenu *getMenu(const char *name);
- void showActionInStatusBar(QAction*, bool show);
+ void showActionsInStatusBar(const QVector<QAction *> &actions, bool show);
void setStatusBarVisibility(bool show);
void adjustStatusBarVisibility();
void goBack();
void showMessage(const QString &message);
void hideMessage();
+ void handleError(const QString &message);
bool isReallyFullScreen();
bool isCompact() { return compactModeActive; }
void missingKeyWarning();
void visitSite();
+ void setDefinitionMode(const QString &definitionName);
signals:
void currentTimeChanged(const QString &s);
void dragEnterEvent(QDragEnterEvent *e);
void dropEvent(QDropEvent *e);
void resizeEvent(QResizeEvent *e);
- void moveEvent(QMoveEvent *e);
void leaveEvent(QEvent *e);
void enterEvent(QEvent *e);
private slots:
void lazyInit();
void checkForUpdate();
- void gotNewVersion(const QString &version);
void donate();
void reportIssue();
void about();
void updateUIForFullscreen();
void compactView(bool enable);
void stop();
-#ifdef APP_PHONON
- void stateChanged(Phonon::State newState, Phonon::State oldState);
-#endif
void searchFocus();
- void tick(qint64 time);
- void totalTimeChanged(qint64 time);
- void setDefinitionMode(const QString &definitionName);
void toggleDefinitionMode();
void clearRecentKeywords();
+ // media
+ void stateChanged(Media::State state);
+ void tick(qint64 time);
+
void volumeUp();
void volumeDown();
- void volumeMute();
+ void toggleVolumeMute();
+ void volumeChanged(qreal newVolume);
void volumeMutedChanged(bool muted);
void updateDownloadMessage(const QString &);
#endif
private:
-#ifdef APP_PHONON
- void initPhonon();
-#endif
+ void initMedia();
void createActions();
void createMenus();
- void createToolBars();
+ void createToolBar();
void createStatusBar();
- void showWidget(QWidget*, bool transition = true);
+ void showView(View *view, bool transition = false);
static QString formatTime(qint64 duration);
bool confirmQuit();
void simpleUpdateDialog(const QString &version);
bool needStatusBar();
void adjustMessageLabelPosition();
- UpdateChecker *updateChecker;
-
- QHash<QByteArray, QAction*> actionMap;
- QHash<QByteArray, QMenu*> menuMap;
+ QHash<QByteArray, QAction *> actionMap;
+ QHash<QByteArray, QMenu *> menuMap;
// view mechanism
QStackedWidget *views;
- QStack<QWidget*> history;
- // QVector<QAction*> viewActions;
+ QStack<View *> history;
// view widgets
HomeView *homeView;
MediaView *mediaView;
- QWidget *aboutView;
- QWidget *downloadView;
- QWidget *regionsView;
+ View *aboutView;
+ View *downloadView;
+ View *regionsView;
// actions
QAction *backAct;
SearchLineEdit *toolbarSearch;
QToolBar *statusToolBar;
QAction *regionAction;
-
- // phonon
-#ifdef APP_PHONON
-#ifdef APP_PHONON_SEEK
- Phonon::SeekSlider *seekSlider;
-#else
- QSlider *slider;
-#endif
- Phonon::VolumeSlider *volumeSlider;
- Phonon::MediaObject *mediaObject;
- Phonon::AudioOutput *audioOutput;
-#endif
+ QSlider *seekSlider;
+ QSlider *volumeSlider;
QLabel *currentTimeLabel;
bool fullScreenActive;
ToolbarMenu *toolbarMenu;
QToolButton *toolbarMenuButton;
+
+ Media *media;
};
#endif
#include "mediaview.h"
#include "constants.h"
-#include "downloaditem.h"
#include "downloadmanager.h"
#include "http.h"
#include "loadingwidget.h"
#include "sidebarheader.h"
#include "sidebarwidget.h"
#include "temporary.h"
-#include "videoareawidget.h"
+#include "videoarea.h"
#ifdef APP_ACTIVATION
#include "activation.h"
#endif
#endif
#include "datautils.h"
#include "idle.h"
+#include "videodefinition.h"
MediaView *MediaView::instance() {
static MediaView *i = new MediaView();
}
MediaView::MediaView(QWidget *parent)
- : View(parent), stopped(false), downloadItem(0)
+ : View(parent), splitter(nullptr), stopped(false)
#ifdef APP_SNAPSHOT
,
- snapshotSettings(0)
+ snapshotSettings(nullptr)
#endif
,
pauseTime(0) {
playlistView = new PlaylistView();
playlistView->setParent(this);
connect(playlistView, SIGNAL(activated(const QModelIndex &)),
- SLOT(itemActivated(const QModelIndex &)));
+ SLOT(onItemActivated(const QModelIndex &)));
playlistModel = new PlaylistModel();
- connect(playlistModel, SIGNAL(activeRowChanged(int)), SLOT(activeRowChanged(int)));
+ connect(playlistModel, &PlaylistModel::activeVideoChanged, this,
+ &MediaView::activeVideoChanged);
// needed to restore the selection after dragndrop
connect(playlistModel, SIGNAL(needSelectionFor(QVector<Video *>)),
SLOT(selectVideos(QVector<Video *>)));
SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
- connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
+ connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(onAuthorPushed(QModelIndex)));
sidebar = new SidebarWidget(this);
sidebar->setPlaylist(playlistView);
+ sidebar->setMaximumWidth(playlistView->minimumWidth() * 3);
connect(sidebar->getRefineSearchWidget(), SIGNAL(searchRefined()), SLOT(searchAgain()));
connect(playlistModel, SIGNAL(haveSuggestions(const QStringList &)), sidebar,
SLOT(showSuggestions(const QStringList &)));
connect(sidebar, SIGNAL(suggestionAccepted(QString)), mainWindow, SLOT(search(QString)));
splitter->addWidget(sidebar);
- videoAreaWidget = new VideoAreaWidget(this);
-
-#ifdef APP_PHONON
- videoWidget = new Phonon::VideoWidget(this);
- videoAreaWidget->setVideoWidget(videoWidget);
-#endif
+ videoAreaWidget = new VideoArea(this);
videoAreaWidget->setListModel(playlistModel);
loadingWidget = new LoadingWidget(this);
for (auto *name : videoActionNames) {
currentVideoActions.append(mainWindow->getAction(name));
}
-
-#ifndef APP_PHONON_SEEK
- QSlider *slider = mainWindow->getSlider();
- connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
-#endif
}
-#ifdef APP_PHONON
-void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
- this->mediaObject = mediaObject;
- Phonon::createPath(mediaObject, videoWidget);
- connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
- connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
- SLOT(stateChanged(Phonon::State, Phonon::State)));
- connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
- connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
+void MediaView::setMedia(Media *media) {
+ this->media = media;
+
+ videoWidget = media->videoWidget();
+ videoAreaWidget->setVideoWidget(videoWidget);
+
+ connect(media, &Media::finished, this, &MediaView::onPlaybackFinished);
+ connect(media, &Media::stateChanged, this, &MediaView::mediaStateChanged);
+ connect(media, &Media::aboutToFinish, this, &MediaView::onAboutToFinish);
+ connect(media, &Media::bufferStatus, loadingWidget, &LoadingWidget::bufferStatus);
}
-#endif
SearchParams *MediaView::getSearchParams() {
VideoSource *videoSource = playlistModel->getVideoSource();
YTSearch *search = qobject_cast<YTSearch *>(videoSource);
return search->getSearchParams();
}
- return 0;
+ return nullptr;
}
void MediaView::search(SearchParams *searchParams) {
// qDebug() << "Adding VideoSource" << videoSource->getName() << videoSource;
+ YTSearch * ytSearch = qobject_cast<YTSearch *>(videoSource);
+ if (nullptr != ytSearch) {
+ if (!ytSearch->getSearchParams()->channelId().isEmpty()) {
+ updateSubscriptionActionForChannel(ytSearch->getSearchParams()->channelId());
+ }
+ }
+
if (addToHistory) {
int currentIndex = getHistoryIndex();
if (currentIndex >= 0 && currentIndex < history.size() - 1) {
VideoSource *vs = history.takeLast();
if (!vs->parent()) {
qDebug() << "Deleting VideoSource" << vs->getName() << vs;
- delete vs;
+ vs->deleteLater();
}
}
}
playlistModel->setVideoSource(videoSource);
- QSettings settings;
- if (settings.value("manualplay", false).toBool()) {
- videoAreaWidget->showPickMessage();
+ if (media->state() == Media::StoppedState) {
+ QSettings settings;
+ if (settings.value("manualplay", false).toBool()) {
+ videoAreaWidget->showPickMessage();
+ }
}
sidebar->showPlaylist();
void MediaView::handleError(const QString &message) {
qWarning() << __PRETTY_FUNCTION__ << message;
-#ifdef APP_PHONON_SEEK
- mediaObject->play();
-#else
- QTimer::singleShot(500, this, SLOT(startPlaying()));
+#ifndef QT_NO_DEBUG_OUTPUT
+ MainWindow::instance()->showMessage(message);
#endif
}
-#ifdef APP_PHONON
-void MediaView::stateChanged(Phonon::State newState, Phonon::State oldState) {
- if (pauseTime > 0 && (newState == Phonon::PlayingState || newState == Phonon::BufferingState)) {
- mediaObject->seek(pauseTime);
+void MediaView::mediaStateChanged(Media::State state) {
+ if (pauseTime > 0 && (state == Media::PlayingState || state == Media::BufferingState)) {
+ qDebug() << "Seeking to" << pauseTime;
+ media->seek(pauseTime);
pauseTime = 0;
}
- if (newState == Phonon::PlayingState) {
+ if (state == Media::PlayingState) {
videoAreaWidget->showVideo();
- } else if (newState == Phonon::ErrorState) {
- qWarning() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
- if (mediaObject->errorType() == Phonon::FatalError) handleError(mediaObject->errorString());
+ } else if (state == Media::ErrorState) {
+ handleError(media->errorString());
}
- if (newState == Phonon::PlayingState) {
+ if (state == Media::PlayingState) {
bool res = Idle::preventDisplaySleep(QString("%1 is playing").arg(Constants::NAME));
if (!res) qWarning() << "Error disabling idle display sleep" << Idle::displayErrorMessage();
- } else if (oldState == Phonon::PlayingState) {
+ } else if (state == Media::PausedState || state == Media::StoppedState) {
bool res = Idle::allowDisplaySleep();
if (!res) qWarning() << "Error enabling idle display sleep" << Idle::displayErrorMessage();
}
}
-#endif
void MediaView::pause() {
-#ifdef APP_PHONON
- switch (mediaObject->state()) {
- case Phonon::PlayingState:
- mediaObject->pause();
+ switch (media->state()) {
+ case Media::PlayingState:
+ media->pause();
pauseTimer.start();
break;
default:
if (pauseTimer.hasExpired(60000)) {
pauseTimer.invalidate();
- connect(playlistModel->activeVideo(), SIGNAL(gotStreamUrl(QUrl)),
- SLOT(resumeWithNewStreamUrl(QUrl)));
+ connect(playlistModel->activeVideo(), &Video::gotStreamUrl, this,
+ &MediaView::resumeWithNewStreamUrl);
playlistModel->activeVideo()->loadStreamUrl();
} else
- mediaObject->play();
+ media->play();
break;
}
-#endif
}
QRegExp MediaView::wordRE(const QString &s) {
videoAreaWidget->update();
errorTimer->stop();
playlistView->selectionModel()->clearSelection();
- if (downloadItem) {
- downloadItem->stop();
- delete downloadItem;
- downloadItem = 0;
- currentVideoSize = 0;
- }
+
MainWindow::instance()->getAction("refineSearch")->setChecked(false);
- updateSubscriptionAction(0, false);
+ updateSubscriptionActionForVideo(nullptr, false);
#ifdef APP_ACTIVATION
demoTimer->stop();
#endif
a->setEnabled(false);
a->setVisible(false);
-#ifdef APP_PHONON
- mediaObject->stop();
- mediaObject->clear();
-#endif
+ media->stop();
+ media->clearQueue();
currentVideoId.clear();
-#ifndef APP_PHONON_SEEK
- QSlider *slider = MainWindow::instance()->getSlider();
- slider->setEnabled(false);
- slider->setValue(0);
-#else
-// Phonon::SeekSlider *slider = MainWindow::instance()->getSeekSlider();
-#endif
-
#ifdef APP_SNAPSHOT
if (snapshotSettings) {
delete snapshotSettings;
- snapshotSettings = 0;
+ snapshotSettings = nullptr;
}
#endif
}
return currentVideoId;
}
-void MediaView::activeRowChanged(int row) {
+void MediaView::activeVideoChanged(Video *video, Video *previousVideo) {
if (stopped) return;
+ media->stop();
errorTimer->stop();
-#ifdef APP_PHONON
- mediaObject->stop();
-#endif
- if (downloadItem) {
- downloadItem->stop();
- delete downloadItem;
- downloadItem = 0;
- currentVideoSize = 0;
+ if (previousVideo && previousVideo != video) {
+ if (previousVideo->isLoadingStreamUrl()) previousVideo->abortLoadStreamUrl();
}
- Video *video = playlistModel->videoAt(row);
- if (!video) return;
-
// optimize window for 16:9 video
adjustWindowSize();
videoAreaWidget->showLoading(video);
- connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
+ connect(video, &Video::gotStreamUrl, this, &MediaView::gotStreamUrl, Qt::UniqueConnection);
connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(skip()), Qt::UniqueConnection);
video->loadStreamUrl();
QLatin1String(Constants::NAME));
// ensure active item is visible
+ int row = playlistModel->rowForVideo(video);
if (row != -1) {
QModelIndex index = playlistModel->index(row, 0, QModelIndex());
playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
// enable/disable actions
MainWindow::instance()
->getAction("download")
- ->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
+ ->setEnabled(DownloadManager::instance()->itemForVideo(video) == nullptr);
MainWindow::instance()->getAction("previous")->setEnabled(row > 0);
MainWindow::instance()->getAction("stopafterthis")->setEnabled(true);
MainWindow::instance()->getAction("relatedVideos")->setEnabled(true);
a->setEnabled(enableDownload);
a->setVisible(enableDownload);
- updateSubscriptionAction(video, YTChannel::isSubscribed(video->getChannelId()));
+ updateSubscriptionActionForVideo(video, YTChannel::isSubscribed(video->getChannelId()));
for (QAction *action : currentVideoActions)
action->setEnabled(true);
-#ifndef APP_PHONON_SEEK
- QSlider *slider = MainWindow::instance()->getSlider();
- slider->setEnabled(false);
- slider->setValue(0);
-#endif
-
#ifdef APP_SNAPSHOT
if (snapshotSettings) {
delete snapshotSettings;
- snapshotSettings = 0;
+ snapshotSettings = nullptr;
MainWindow::instance()->adjustStatusBarVisibility();
}
#endif
// see you in gotStreamUrl...
}
-void MediaView::gotStreamUrl(QUrl streamUrl) {
+void MediaView::gotStreamUrl(const QString &streamUrl, const QString &audioUrl) {
if (stopped) return;
- if (!streamUrl.isValid()) {
+ if (streamUrl.isEmpty()) {
+ qWarning() << "Empty stream url";
skip();
return;
}
currentVideoId = video->getId();
-#ifdef APP_PHONON_SEEK
- mediaObject->setCurrentSource(streamUrl);
- mediaObject->play();
-#else
- startDownloading();
-#endif
+ if (audioUrl.isEmpty()) {
+ qDebug() << "Playing" << streamUrl;
+ media->play(streamUrl);
+ } else {
+ qDebug() << "Playing" << streamUrl << audioUrl;
+ media->playSeparateAudioAndVideo(streamUrl, audioUrl);
+ }
// ensure we always have videos ahead
playlistModel->searchNeeded();
ChannelAggregator::instance()->videoWatched(video);
}
-void MediaView::downloadStatusChanged() {
- // qDebug() << __PRETTY_FUNCTION__;
- switch (downloadItem->status()) {
- case Downloading:
- // qDebug() << "Downloading";
- if (downloadItem->offset() == 0)
- startPlaying();
- else {
-#ifdef APP_PHONON
- // qDebug() << "Seeking to" << downloadItem->offset();
- mediaObject->seek(offsetToTime(downloadItem->offset()));
- mediaObject->play();
-#endif
- }
- break;
- case Starting:
- // qDebug() << "Starting";
- break;
- case Finished:
-// qDebug() << "Finished" << mediaObject->state();
-#ifdef APP_PHONON_SEEK
- MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
-#endif
- break;
- case Failed:
- // qDebug() << "Failed";
- skip();
- break;
- case Idle:
- // qDebug() << "Idle";
- break;
- }
-}
-
-void MediaView::startPlaying() {
- // qDebug() << __PRETTY_FUNCTION__;
- if (stopped) return;
- if (!downloadItem) {
- skip();
- return;
- }
-
- if (downloadItem->offset() == 0) {
- currentVideoSize = downloadItem->bytesTotal();
- // qDebug() << "currentVideoSize" << currentVideoSize;
- }
-
- // go!
- QString source = downloadItem->currentFilename();
- qDebug() << "Playing" << source << QFile::exists(source);
-#ifdef APP_PHONON
- mediaObject->setCurrentSource(QUrl::fromLocalFile(source));
- mediaObject->play();
-#endif
-#ifdef APP_PHONON_SEEK
- MainWindow::instance()->getSeekSlider()->setEnabled(false);
-#else
- QSlider *slider = MainWindow::instance()->getSlider();
- slider->setEnabled(true);
-#endif
-}
-
-void MediaView::itemActivated(const QModelIndex &index) {
+void MediaView::onItemActivated(const QModelIndex &index) {
if (playlistModel->rowExists(index.row())) {
// if it's the current video, just rewind and play
Video *activeVideo = playlistModel->activeVideo();
Video *video = playlistModel->videoAt(index.row());
if (activeVideo && video && activeVideo == video) {
- // mediaObject->seek(0);
- sliderMoved(0);
-#ifdef APP_PHONON
- mediaObject->play();
-#endif
+ media->play();
} else
playlistModel->setActiveRow(index.row());
playlistModel->setActiveRow(prevRow);
}
-void MediaView::aboutToFinish() {
-#ifdef APP_PHONON
- qint64 currentTime = mediaObject->currentTime();
- qint64 totalTime = mediaObject->totalTime();
+void MediaView::onAboutToFinish() {
+ qint64 currentTime = media->position();
+ qint64 totalTime = media->duration();
// qDebug() << __PRETTY_FUNCTION__ << currentTime << totalTime;
if (totalTime < 1 || currentTime + 10000 < totalTime) {
// QTimer::singleShot(500, this, SLOT(playbackResume()));
- mediaObject->seek(currentTime);
- mediaObject->play();
+ media->seek(currentTime);
+ media->play();
}
-#endif
}
-void MediaView::playbackFinished() {
+void MediaView::onPlaybackFinished() {
if (stopped) return;
-#ifdef APP_PHONON
- const qint64 totalTime = mediaObject->totalTime();
- const qint64 currentTime = mediaObject->currentTime();
+ const qint64 totalTime = media->duration();
+ const qint64 currentTime = media->position();
// qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime() << totalTime;
// add 10 secs for imprecise Phonon backends (VLC, Xine)
if (currentTime > 0 && currentTime + 10000 < totalTime) {
// mediaObject->seek(currentTime);
- QTimer::singleShot(500, this, SLOT(playbackResume()));
+ QTimer::singleShot(500, this, SLOT(resumePlayback()));
} else {
QAction *stopAfterThisAction = MainWindow::instance()->getAction("stopafterthis");
if (stopAfterThisAction->isChecked()) {
} else
skip();
}
-#endif
}
-void MediaView::playbackResume() {
+void MediaView::resumePlayback() {
if (stopped) return;
-#ifdef APP_PHONON
- const qint64 currentTime = mediaObject->currentTime();
+ const qint64 currentTime = media->position();
// qDebug() << __PRETTY_FUNCTION__ << currentTime;
- if (currentTime > 0) mediaObject->seek(currentTime);
- mediaObject->play();
-#endif
+ if (currentTime > 0) media->seek(currentTime);
+ media->play();
}
void MediaView::openWebPage() {
Video *video = playlistModel->activeVideo();
if (!video) return;
-#ifdef APP_PHONON
- mediaObject->pause();
-#endif
- QString url = video->getWebpage() + QLatin1String("&t=") +
- QString::number(mediaObject->currentTime() / 1000);
+ media->pause();
+ QString url =
+ video->getWebpage() + QLatin1String("&t=") + QString::number(media->position() / 1000);
QDesktopServices::openUrl(url);
}
void MediaView::copyVideoLink() {
Video *video = playlistModel->activeVideo();
if (!video) return;
- QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
+ QApplication::clipboard()->setText(video->getStreamUrl());
QString message = tr("You can now paste the video stream URL into another application") + ". " +
tr("The link will be valid only for a limited time.");
MainWindow::instance()->showMessage(message);
void MediaView::openInBrowser() {
Video *video = playlistModel->activeVideo();
if (!video) return;
-#ifdef APP_PHONON
- mediaObject->pause();
-#endif
+ media->pause();
QDesktopServices::openUrl(video->getStreamUrl());
}
void MediaView::setSidebarVisibility(bool visible) {
if (sidebar->isVisible() == visible) return;
sidebar->setVisible(visible);
- sidebar->raise();
- playlistView->setFocus();
+ if (visible) {
+ sidebar->move(0, 0);
+ sidebar->resize(sidebar->width(), window()->height());
+ sidebar->raise();
+ playlistView->setFocus();
+ }
}
void MediaView::removeSidebar() {
sidebar->hide();
-#ifndef APP_MAC
sidebar->setParent(window());
- sidebar->move(0, 0);
- sidebar->raise();
-#endif
}
void MediaView::restoreSidebar() {
sidebar->show();
-#ifndef APP_MAC
splitter->insertWidget(0, sidebar);
-#endif
}
bool MediaView::isSidebarVisible() {
void MediaView::saveSplitterState() {
QSettings settings;
- settings.setValue("splitter", splitter->saveState());
+ if (splitter) settings.setValue("splitter", splitter->saveState());
}
void MediaView::downloadVideo() {
Video *video = playlistModel->activeVideo();
if (!video) return;
DownloadManager::instance()->addItem(video);
- MainWindow::instance()->showActionInStatusBar(MainWindow::instance()->getAction("downloads"),
- true);
+ MainWindow::instance()->showActionsInStatusBar({MainWindow::instance()->getAction("downloads")},
+ true);
QString message = tr("Downloading %1").arg(video->getTitle());
MainWindow::instance()->showMessage(message);
}
#ifdef APP_SNAPSHOT
void MediaView::snapshot() {
- qint64 currentTime = mediaObject->currentTime() / 1000;
+ qint64 currentTime = media->position() / 1000;
- QImage image = videoWidget->snapshot();
- if (image.isNull()) {
- qWarning() << "Null snapshot";
- return;
- }
+ QObject *context = new QObject();
+ connect(media, &Media::snapshotReady, context,
+ [this, currentTime, context](const QImage &image) {
+ context->deleteLater();
- // QPixmap pixmap = QPixmap::grabWindow(videoWidget->winId());
- QPixmap pixmap = QPixmap::fromImage(
- image.scaled(videoWidget->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- videoAreaWidget->showSnapshotPreview(pixmap);
-
- Video *video = playlistModel->activeVideo();
- if (!video) return;
+ if (image.isNull()) {
+ qWarning() << "Null snapshot";
+ return;
+ }
- QString location = SnapshotSettings::getCurrentLocation();
- QDir dir(location);
- if (!dir.exists()) dir.mkpath(location);
- QString basename = video->getTitle();
- QString format = video->getDuration() > 3600 ? "h_mm_ss" : "m_ss";
- basename += " (" + QTime(0, 0, 0).addSecs(currentTime).toString(format) + ")";
- basename = DataUtils::stringToFilename(basename);
- QString filename = location + "/" + basename + ".png";
- qDebug() << filename;
- image.save(filename, "PNG");
-
- if (snapshotSettings) delete snapshotSettings;
- snapshotSettings = new SnapshotSettings(videoWidget);
- snapshotSettings->setSnapshot(pixmap, filename);
- QStatusBar *statusBar = MainWindow::instance()->statusBar();
+ QPixmap pixmap = QPixmap::fromImage(image.scaled(
+ videoWidget->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
+ videoAreaWidget->showSnapshotPreview(pixmap);
+
+ Video *video = playlistModel->activeVideo();
+ if (!video) return;
+
+ QString location = SnapshotSettings::getCurrentLocation();
+ QDir dir(location);
+ if (!dir.exists()) dir.mkpath(location);
+ QString basename = video->getTitle();
+ QString format = video->getDuration() > 3600 ? "h_mm_ss" : "m_ss";
+ basename += " (" + QTime(0, 0, 0).addSecs(currentTime).toString(format) + ")";
+ basename = DataUtils::stringToFilename(basename);
+ QString filename = location + "/" + basename + ".png";
+ qDebug() << filename;
+ image.save(filename, "PNG");
+
+ if (snapshotSettings) delete snapshotSettings;
+ snapshotSettings = new SnapshotSettings(videoWidget);
+ snapshotSettings->setSnapshot(pixmap, filename);
+ QStatusBar *statusBar = MainWindow::instance()->statusBar();
#ifdef APP_EXTRA
- Extra::fadeInWidget(statusBar, statusBar);
+ Extra::fadeInWidget(statusBar, statusBar);
#endif
- statusBar->insertPermanentWidget(0, snapshotSettings);
- snapshotSettings->show();
- MainWindow::instance()->setStatusBarVisibility(true);
-}
+ statusBar->insertPermanentWidget(0, snapshotSettings);
+ snapshotSettings->show();
+ MainWindow::instance()->setStatusBarVisibility(true);
+ }
#endif
+ );
+
+ media->snapshot();
+}
void MediaView::fullscreen() {
- videoAreaWidget->setParent(0);
+ videoAreaWidget->setParent(nullptr);
videoAreaWidget->showFullScreen();
}
-void MediaView::startDownloading() {
- Video *video = playlistModel->activeVideo();
- if (!video) return;
- Video *videoCopy = video->clone();
- if (downloadItem) {
- downloadItem->stop();
- delete downloadItem;
- }
- QString tempFile = Temporary::filename();
- downloadItem = new DownloadItem(videoCopy, video->getStreamUrl(), tempFile, this);
- connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()),
- Qt::UniqueConnection);
- connect(downloadItem, SIGNAL(bufferProgress(int)), loadingWidget, SLOT(bufferStatus(int)),
- Qt::UniqueConnection);
- // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
- connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)),
- Qt::UniqueConnection);
- connect(downloadItem, SIGNAL(error(QString)), SLOT(handleError(QString)), Qt::UniqueConnection);
- downloadItem->start();
-}
+void MediaView::resumeWithNewStreamUrl(const QString &streamUrl, const QString &audioUrl) {
+ pauseTime = media->position();
-void MediaView::resumeWithNewStreamUrl(const QUrl &streamUrl) {
- pauseTime = mediaObject->currentTime();
- mediaObject->setCurrentSource(streamUrl);
- mediaObject->play();
+ if (audioUrl.isEmpty()) {
+ qDebug() << "Playing" << streamUrl;
+ media->play(streamUrl);
+ } else {
+ qDebug() << "Playing" << streamUrl << audioUrl;
+ media->playSeparateAudioAndVideo(streamUrl, audioUrl);
+ }
Video *video = static_cast<Video *>(sender());
if (!video) {
video->disconnect(this);
}
-void MediaView::sliderMoved(int value) {
- Q_UNUSED(value);
-#ifdef APP_PHONON
-#ifndef APP_PHONON_SEEK
-
- if (currentVideoSize <= 0 || !downloadItem || !mediaObject->isSeekable()) return;
-
- QSlider *slider = MainWindow::instance()->getSlider();
- if (slider->isSliderDown()) return;
-
- qint64 offset = (currentVideoSize * value) / slider->maximum();
-
- bool needsDownload = downloadItem->needsDownload(offset);
- if (needsDownload) {
- if (downloadItem->isBuffered(offset)) {
- qint64 realOffset = downloadItem->blankAtOffset(offset);
- if (offset < currentVideoSize) downloadItem->seekTo(realOffset, false);
- mediaObject->seek(offsetToTime(offset));
- } else {
- mediaObject->pause();
- downloadItem->seekTo(offset);
- }
- } else {
- // qDebug() << "simple seek";
- mediaObject->seek(offsetToTime(offset));
- }
-#endif
-#endif
-}
-
-qint64 MediaView::offsetToTime(qint64 offset) {
-#ifdef APP_PHONON
- const qint64 totalTime = mediaObject->totalTime();
- return ((offset * totalTime) / currentVideoSize);
-#endif
-}
-
void MediaView::findVideoParts() {
Video *video = playlistModel->activeVideo();
if (!video) return;
QDesktopServices::openUrl(url);
}
-void MediaView::authorPushed(QModelIndex index) {
+void MediaView::onAuthorPushed(QModelIndex index) {
Video *video = playlistModel->videoAt(index.row());
if (!video) return;
search(searchParams);
}
-void MediaView::updateSubscriptionAction(Video *video, bool subscribed) {
+
+void MediaView::updateSubscriptionAction(bool subscribed) {
QAction *subscribeAction = MainWindow::instance()->getAction("subscribeChannel");
QString subscribeTip;
QString subscribeText;
- if (!video) {
+
+ if (currentSubscriptionChannelId.isEmpty()) {
subscribeText = subscribeAction->property("originalText").toString();
subscribeAction->setEnabled(false);
} else if (subscribed) {
- subscribeText = tr("Unsubscribe from %1").arg(video->getChannelTitle());
+ subscribeText = tr("Unsubscribe from %1").arg(currentSubscriptionChannelTitle);
subscribeTip = subscribeText;
subscribeAction->setEnabled(true);
} else {
- subscribeText = tr("Subscribe to %1").arg(video->getChannelTitle());
+ subscribeText = tr("Subscribe to %1").arg(currentSubscriptionChannelTitle);
subscribeTip = subscribeText;
subscribeAction->setEnabled(true);
}
subscribeAction->setStatusTip(subscribeTip);
if (subscribed) {
-#ifdef APP_LINUX_NO
- static QIcon tintedIcon;
- if (tintedIcon.isNull()) {
- QVector<QSize> sizes;
- sizes << QSize(16, 16);
- tintedIcon = IconUtils::tintedIcon("bookmark-new", QColor(254, 240, 0), sizes);
- }
- subscribeAction->setIcon(tintedIcon);
-#else
subscribeAction->setIcon(IconUtils::icon("bookmark-remove"));
-#endif
} else {
subscribeAction->setIcon(IconUtils::icon("bookmark-new"));
}
- IconUtils::setupAction(subscribeAction);
+ MainWindow::instance()->setupAction(subscribeAction);
}
-void MediaView::toggleSubscription() {
+void MediaView::updateSubscriptionActionForChannel(const QString & channelId) {
+ QString channelTitle = tr("channel");
+ YTChannel *channel = YTChannel::forId(channelId);
+ if (nullptr != channel && !channel->getDisplayName().isEmpty()) {
+ channelTitle = channel->getDisplayName();
+ }
+
+ bool subscribed = YTChannel::isSubscribed(channelId);
+
+ currentSubscriptionChannelId = channelId;
+ currentSubscriptionChannelTitle = channelTitle;
+ updateSubscriptionAction(subscribed);
+}
+
+void MediaView::updateSubscriptionActionForVideo(Video *video, bool subscribed) {
+ if (!video) {
+ currentSubscriptionChannelId = "";
+ currentSubscriptionChannelTitle = "";
+ updateSubscriptionAction(false);
+ } else {
+ currentSubscriptionChannelId = video->getChannelId();
+ currentSubscriptionChannelTitle = video->getChannelTitle();
+ updateSubscriptionAction(subscribed);
+ }
+}
+
+void MediaView::reloadCurrentVideo() {
Video *video = playlistModel->activeVideo();
if (!video) return;
- QString userId = video->getChannelId();
- if (userId.isEmpty()) return;
- bool subscribed = YTChannel::isSubscribed(userId);
+
+ int oldFormat = video->getDefinitionCode();
+
+ QObject *context = new QObject();
+ connect(video, &Video::gotStreamUrl, context,
+ [this, oldFormat, video, context](const QString &videoUrl, const QString &audioUrl) {
+ context->deleteLater();
+ if (oldFormat == video->getDefinitionCode()) return;
+ QObject *context2 = new QObject();
+ const qint64 position = media->position();
+ connect(media, &Media::stateChanged, context2,
+ [position, this, context2](Media::State state) {
+ if (state == Media::PlayingState) {
+ media->seek(position);
+ context2->deleteLater();
+ Video *video = playlistModel->activeVideo();
+ QString msg = tr("Switched to %1")
+ .arg(VideoDefinition::forCode(
+ video->getDefinitionCode())
+ .getName());
+ MainWindow::instance()->showMessage(msg);
+ }
+ });
+
+ if (audioUrl.isEmpty()) {
+ media->play(videoUrl);
+ } else {
+ media->playSeparateAudioAndVideo(videoUrl, audioUrl);
+ }
+ });
+ video->loadStreamUrl();
+}
+
+void MediaView::toggleSubscription() {
+ //Video *video = playlistModel->activeVideo();
+ if (currentSubscriptionChannelId.isEmpty()) {
+ return;
+ }
+
+ bool subscribed = YTChannel::isSubscribed(currentSubscriptionChannelId);
if (subscribed) {
- YTChannel::unsubscribe(userId);
+ YTChannel::unsubscribe(currentSubscriptionChannelId);
MainWindow::instance()->showMessage(
- tr("Unsubscribed from %1").arg(video->getChannelTitle()));
+ tr("Unsubscribed from %1").arg(currentSubscriptionChannelTitle));
} else {
- YTChannel::subscribe(userId);
- MainWindow::instance()->showMessage(tr("Subscribed to %1").arg(video->getChannelTitle()));
+ YTChannel::subscribe(currentSubscriptionChannelId);
+ MainWindow::instance()->showMessage(tr("Subscribed to %1").arg(currentSubscriptionChannelTitle));
}
- updateSubscriptionAction(video, !subscribed);
+
+ updateSubscriptionAction(!subscribed);
}
void MediaView::adjustWindowSize() {
+ qDebug() << "Adjusting window size";
Video *video = playlistModel->activeVideo();
if (!video) return;
QWidget *window = this->window();
const double h = (double)videoAreaWidget->height();
const double currentVideoRatio = w / h;
if (currentVideoRatio != ratio) {
+ qDebug() << "Adjust size";
int newHeight = std::round((window->height() - h) + (w / ratio));
window->resize(window->width(), newHeight);
}
$END_LICENSE */
-#ifndef __MEDIAVIEW_H__
-#define __MEDIAVIEW_H__
+#ifndef MEDIAVIEW_H
+#define MEDIAVIEW_H
-#include <QtWidgets>
#include <QtNetwork>
-#ifdef APP_PHONON
-#include <phonon/mediaobject.h>
-#include <phonon/videowidget.h>
-#include <phonon/seekslider.h>
-#endif
+#include <QtWidgets>
+
+#include "media.h"
+
#include "view.h"
class Video;
class PlaylistModel;
class SearchParams;
class LoadingWidget;
-class VideoAreaWidget;
-class DownloadItem;
+class VideoArea;
class PlaylistView;
class SidebarWidget;
class VideoSource;
#endif
class MediaView : public View {
-
Q_OBJECT
public:
- static MediaView* instance();
+ static MediaView *instance();
void initialize();
void appear();
void disappear();
-#ifdef APP_PHONON
- void setMediaObject(Phonon::MediaObject *mediaObject);
-#endif
- const QVector<VideoSource*> & getHistory() { return history; }
+ void setMedia(Media *media);
+ const QVector<VideoSource *> &getHistory() { return history; }
int getHistoryIndex();
- PlaylistModel* getPlaylistModel() { return playlistModel; }
+ PlaylistModel *getPlaylistModel() { return playlistModel; }
const QString &getCurrentVideoId();
- void updateSubscriptionAction(Video *video, bool subscribed);
- VideoAreaWidget* getVideoArea() { return videoAreaWidget; }
+ void updateSubscriptionActionForVideo(Video *video, bool subscribed);
+ void updateSubscriptionActionForChannel(const QString & channelId);
+ VideoArea *getVideoArea() { return videoAreaWidget; }
+ void reloadCurrentVideo();
public slots:
void search(SearchParams *searchParams);
void goForward();
void toggleSubscription();
void adjustWindowSize();
+ void updateSubscriptionAction(bool subscribed);
private slots:
- // list/model
- void itemActivated(const QModelIndex &index);
- void selectionChanged (const QItemSelection & selected, const QItemSelection & deselected);
- void activeRowChanged(int);
- void selectVideos(const QVector<Video*> &videos);
- void gotStreamUrl(QUrl streamUrl);
+ void onItemActivated(const QModelIndex &index);
+ void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
+ void activeVideoChanged(Video *video, Video *previousVideo);
+ void selectVideos(const QVector<Video *> &videos);
+ void gotStreamUrl(const QString &streamUrl, const QString &audioUrl);
void handleError(const QString &message);
- // phonon
-#ifdef APP_PHONON
- void stateChanged(Phonon::State newState, Phonon::State oldState);
-#endif
- void aboutToFinish();
- void startPlaying();
- void downloadStatusChanged();
- void playbackFinished();
- void playbackResume();
- void authorPushed(QModelIndex);
+ void mediaStateChanged(Media::State state);
+ void onAboutToFinish();
+ void onPlaybackFinished();
+ void resumePlayback();
+ void onAuthorPushed(QModelIndex);
void searchAgain();
- void sliderMoved(int value);
- qint64 offsetToTime(qint64 offset);
- void startDownloading();
- void resumeWithNewStreamUrl(const QUrl &streamUrl);
+ void resumeWithNewStreamUrl(const QString &streamUrl, const QString &audioUrl);
private:
- MediaView(QWidget *parent = 0);
- SearchParams* getSearchParams();
+ MediaView(QWidget *parent = nullptr);
+ SearchParams *getSearchParams();
static QRegExp wordRE(const QString &s);
SidebarWidget *sidebar;
PlaylistView *playlistView;
PlaylistModel *playlistModel;
- VideoAreaWidget *videoAreaWidget;
+ VideoArea *videoAreaWidget;
LoadingWidget *loadingWidget;
-#ifdef APP_PHONON
- Phonon::MediaObject *mediaObject;
- Phonon::VideoWidget *videoWidget;
-#endif
+ Media *media;
+ QWidget *videoWidget;
bool stopped;
QTimer *errorTimer;
Video *skippedVideo;
QString currentVideoId;
+ QString currentSubscriptionChannelId;
+ QString currentSubscriptionChannelTitle;
+
#ifdef APP_ACTIVATION
QTimer *demoTimer;
#endif
- DownloadItem *downloadItem;
- QVector<VideoSource*> history;
- QVector<QAction*> currentVideoActions;
+ QVector<VideoSource *> history;
+ QVector<QAction *> currentVideoActions;
qint64 currentVideoSize;
qint64 pauseTime;
};
-#endif // __MEDIAVIEW_H__
+#endif // MEDIAVIEW_H
#include "fontutils.h"
#include "iconutils.h"
-PainterUtils::PainterUtils() { }
+PainterUtils::PainterUtils() {}
-void PainterUtils::centeredMessage(const QString &message, QWidget* widget) {
+void PainterUtils::centeredMessage(const QString &message, QWidget *widget) {
QPainter painter(widget);
painter.setFont(FontUtils::big());
QSize textSize(QFontMetrics(painter.font()).size(Qt::TextSingleLine, message));
- QPoint topLeft(
- (widget->width()-textSize.width())/2,
- ((widget->height()-textSize.height())/2)
- );
+ QPoint topLeft((widget->width() - textSize.width()) / 2,
+ ((widget->height() - textSize.height()) / 2));
QRect rect(topLeft, textSize);
painter.setOpacity(.5);
painter.drawText(rect, Qt::AlignCenter, message);
}
-void PainterUtils::paintBadge(QPainter *painter, const QString &text, bool center, QColor backgroundColor) {
+void PainterUtils::paintBadge(QPainter *painter,
+ const QString &text,
+ bool center,
+ const QColor &backgroundColor,
+ bool literalColor) {
painter->save();
QRect textBox = painter->boundingRect(QRect(), Qt::AlignCenter, text);
if (rect.width() < rect.height() || text.length() == 1) rect.setWidth(rect.height());
painter->setPen(Qt::NoPen);
- painter->setBrush(backgroundColor);
+ QColor bg;
+ if (literalColor)
+ bg = backgroundColor;
+ else
+ bg = backgroundColor.value() > 128 ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
+ painter->setBrush(bg);
painter->setRenderHint(QPainter::Antialiasing);
- qreal borderRadius = rect.height()/2.;
+ qreal borderRadius = rect.height() / 2.;
painter->drawRoundedRect(rect, borderRadius, borderRadius);
+ painter->setFont(FontUtils::small());
painter->setPen(Qt::white);
painter->drawText(rect, Qt::AlignCenter, text);
#include <QtWidgets>
class PainterUtils {
-
public:
- static void centeredMessage(const QString &message, QWidget* widget);
- static void paintBadge(QPainter *painter, const QString &text,
- bool center = false, QColor backgroundColor = QColor(230,36,41));
+ static void centeredMessage(const QString &message, QWidget *widget);
+ static void paintBadge(QPainter *painter,
+ const QString &text,
+ bool center,
+ const QColor &backgroundColor,
+ bool literalColor = false);
private:
PainterUtils();
-
};
#endif // PAINTERUTILS_H
painter->drawText(textBox, 0, elidedText);
return elidedText.length() < text.length();
}
-}
+} // namespace
PlaylistItemDelegate::PlaylistItemDelegate(QObject *parent, bool downloadInfo)
- : QStyledItemDelegate(parent), downloadInfo(downloadInfo), progressBar(0) {
+ : QStyledItemDelegate(parent), downloadInfo(downloadInfo), progressBar(nullptr) {
listView = qobject_cast<PlaylistView *>(parent);
smallerBoldFont = FontUtils::smallBold();
}
void PlaylistItemDelegate::createPlayIcon() {
- qreal maxRatio = IconUtils::maxSupportedPixelRatio();
+ qreal maxRatio = 2.0;
playIcon = QPixmap(thumbWidth * maxRatio, thumbHeight * maxRatio);
playIcon.setDevicePixelRatio(maxRatio);
playIcon.fill(Qt::transparent);
QSize PlaylistItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
const QModelIndex & /*index*/) const {
- return QSize(thumbWidth, thumbHeight + 1);
+ return QSize(thumbWidth, thumbHeight);
}
void PlaylistItemDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
int itemType = index.data(ItemTypeRole).toInt();
- if (itemType == ItemTypeVideo) {
- QStyleOptionViewItem opt = QStyleOptionViewItem(option);
- initStyleOption(&opt, index);
- opt.text.clear();
- opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
- paintBody(painter, opt, index);
- } else
+ if (itemType == ItemTypeVideo)
+ paintBody(painter, option, index);
+ else
QStyledItemDelegate::paint(painter, option, index);
}
void PlaylistItemDelegate::paintBody(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
+ const bool isSelected = option.state & QStyle::State_Selected;
+ if (isSelected)
+ QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
+
painter->save();
painter->translate(option.rect.topLeft());
if (downloadInfo) line.setWidth(line.width() / 2);
const bool isActive = index.data(ActiveTrackRole).toBool();
- const bool isSelected = option.state & QStyle::State_Selected;
// get the video metadata
const Video *video = index.data(VideoRole).value<VideoPointer>().data();
// draw the "current track" highlight underneath the text
if (isActive && !isSelected) paintActiveOverlay(painter, option, line);
- // separator
- painter->setPen(option.palette.color(QPalette::Midlight));
- painter->drawLine(thumbWidth, thumbHeight, option.rect.width(), thumbHeight);
- if (!video->getThumbnail().isNull()) painter->setPen(Qt::black);
- painter->drawLine(0, thumbHeight, thumbWidth - 1, thumbHeight);
-
// thumb
- painter->drawPixmap(0, 0, video->getThumbnail());
+ const QPixmap &thumb = video->getThumbnail();
+ if (!thumb.isNull()) {
+ painter->drawPixmap(0, 0, thumb);
+ if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line);
+ }
const bool thumbsOnly = line.width() <= thumbWidth + 60;
const bool isHovered = index.data(HoveredItemRole).toBool();
// play icon overlayed on the thumb
- if (isActive && (!isHovered && thumbsOnly)) painter->drawPixmap(0, 0, playIcon);
-
- // time
- if (video->getDuration() > 0) drawTime(painter, video->getFormattedDuration(), line);
+ bool needPlayIcon = isActive;
+ if (thumbsOnly) needPlayIcon = needPlayIcon && !isHovered;
+ if (needPlayIcon) painter->drawPixmap(0, 0, playIcon);
if (!thumbsOnly) {
// text color
QStringRef title(&video->getTitle());
QString elidedTitle = video->getTitle();
static const int titleFlags = Qt::AlignTop | Qt::TextWordWrap;
- QRect textBox = line.adjusted(padding + thumbWidth, padding, 0, 0);
+ QRect textBox = line.adjusted(padding + thumbWidth, padding, -padding, 0);
textBox = painter->boundingRect(textBox, titleFlags, elidedTitle);
while (textBox.height() > 55 && elidedTitle.length() > 10) {
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
textSize = QSize(painter->fontMetrics().size(Qt::TextSingleLine, author));
textBox = QRect(textPoint, textSize);
authorRects.insert(index.row(), textBox);
- if (textBox.right() > line.width()) {
+ if (textBox.right() > line.width() - padding) {
textBox.setRight(line.width());
elided = drawElidedText(painter, textBox, flags, author);
} else {
// view count
if (video->getViewCount() > 0) {
- QLocale locale;
- const QString viewCount = tr("%1 views").arg(locale.toString(video->getViewCount()));
+ const QString &viewCount = video->getFormattedViewCount();
textPoint.setX(textBox.right() + padding);
textSize = QSize(fontMetrics.size(Qt::TextSingleLine, viewCount));
- if (elided || textPoint.x() + textSize.width() > line.width()) {
+ if (elided || textPoint.x() + textSize.width() > line.width() - padding) {
textPoint.setX(thumbWidth + padding);
textPoint.setY(textPoint.y() + textSize.height() + padding);
}
QRect downloadButtonRect(const QRect &line) const;
QRect authorRect(const QModelIndex &index) const;
+ static const int thumbWidth;
+ static const int thumbHeight;
+
private:
void createPlayIcon();
void paintBody(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const;
const QRect &line) const;
void drawTime(QPainter *painter, const QString &time, const QRect &line) const;
- static const int thumbWidth;
- static const int thumbHeight;
static const int padding;
QPixmap playIcon;
#include "videosource.h"
#include "ytsearch.h"
-static const int maxItems = 50;
-static const QString recentKeywordsKey = "recentKeywords";
-static const QString recentChannelsKey = "recentChannels";
+namespace {
+const int maxItems = 50;
+const QString recentKeywordsKey = "recentKeywords";
+const QString recentChannelsKey = "recentChannels";
+} // namespace
PlaylistModel::PlaylistModel(QWidget *parent) : QAbstractListModel(parent) {
- videoSource = 0;
+ videoSource = nullptr;
searching = false;
canSearchMore = true;
firstSearch = false;
- m_activeVideo = 0;
+ m_activeVideo = nullptr;
m_activeRow = -1;
startIndex = 1;
max = 0;
return ItemTypeShowMore;
case Qt::DisplayRole:
if (!errorMessage.isEmpty()) return errorMessage;
- if (searching) return tr("Searching...");
+ if (searching) return QString(); // tr("Searching...");
if (canSearchMore) return tr("Show %1 More").arg("").simplified();
if (videos.isEmpty())
return tr("No videos");
void PlaylistModel::setActiveRow(int row, bool notify) {
if (rowExists(row)) {
m_activeRow = row;
+ Video *previousVideo = m_activeVideo;
m_activeVideo = videoAt(row);
int oldactiverow = m_activeRow;
createIndex(oldactiverow, columnCount() - 1));
emit dataChanged(createIndex(m_activeRow, 0), createIndex(m_activeRow, columnCount() - 1));
- if (notify) emit activeRowChanged(row);
+ if (notify) emit activeVideoChanged(m_activeVideo, previousVideo);
} else {
m_activeRow = -1;
- m_activeVideo = 0;
+ m_activeVideo = nullptr;
}
}
Video *PlaylistModel::videoAt(int row) const {
if (rowExists(row)) return videos.at(row);
- return 0;
+ return nullptr;
}
Video *PlaylistModel::activeVideo() const {
void PlaylistModel::setVideoSource(VideoSource *videoSource) {
beginResetModel();
- while (!videos.isEmpty())
- delete videos.takeFirst();
- m_activeVideo = 0;
+
+ qDeleteAll(videos);
+ videos.clear();
+
+ qDeleteAll(deletedVideos);
+ deletedVideos.clear();
+
+ m_activeVideo = nullptr;
m_activeRow = -1;
startIndex = 1;
endResetModel();
Qt::UniqueConnection);
connect(videoSource, SIGNAL(finished(int)), SLOT(searchFinished(int)), Qt::UniqueConnection);
connect(videoSource, SIGNAL(error(QString)), SLOT(searchError(QString)), Qt::UniqueConnection);
+ connect(videoSource, &QObject::destroyed, this,
+ [this, videoSource] {
+ if (this->videoSource == videoSource) {
+ this->videoSource = nullptr;
+ }
+ },
+ Qt::UniqueConnection);
searchMore();
}
void PlaylistModel::searchMore(int max) {
- if (searching) return;
+ if (videoSource == nullptr || searching) return;
searching = true;
firstSearch = startIndex == 1;
this->max = max;
videos.squeeze();
searching = false;
m_activeRow = -1;
- m_activeVideo = 0;
+ m_activeVideo = nullptr;
startIndex = 1;
endResetModel();
}
void PlaylistModel::removeIndexes(QModelIndexList &indexes) {
QVector<Video *> originalList(videos);
- QVector<Video *> delitems;
- delitems.reserve(indexes.size());
for (const QModelIndex &index : indexes) {
if (index.row() >= originalList.size()) continue;
Video *video = originalList.at(index.row());
int idx = videos.indexOf(video);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
- delitems.append(video);
+ deletedVideos.append(video);
+ if (m_activeVideo == video) {
+ m_activeVideo = nullptr;
+ m_activeRow = -1;
+ }
videos.removeAll(video);
- video->deleteLater();
endRemoveRows();
}
}
- qDeleteAll(delitems);
videos.squeeze();
}
AuthorPressedRole
};
-enum ItemTypes {
- ItemTypeVideo = 1,
- ItemTypeShowMore
-};
+enum ItemTypes { ItemTypeVideo = 1, ItemTypeShowMore };
class PlaylistModel : public QAbstractListModel {
-
Q_OBJECT
public:
PlaylistModel(QWidget *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
- int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 4; }
+ int columnCount(const QModelIndex &parent = QModelIndex()) const {
+ Q_UNUSED(parent);
+ return 4;
+ }
QVariant data(const QModelIndex &index, int role) const;
bool removeRows(int position, int rows, const QModelIndex &parent);
QStringList mimeTypes() const;
Qt::DropActions supportedDropActions() const;
Qt::DropActions supportedDragActions() const;
- QMimeData* mimeData( const QModelIndexList &indexes ) const;
+ QMimeData *mimeData(const QModelIndexList &indexes) const;
bool dropMimeData(const QMimeData *data,
- Qt::DropAction action, int row, int column,
+ Qt::DropAction action,
+ int row,
+ int column,
const QModelIndex &parent);
- void setActiveRow(int row , bool notify = true);
- bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); }
+ void setActiveRow(int row, bool notify = true);
+ bool rowExists(int row) const { return ((row >= 0) && (row < videos.size())); }
int activeRow() const { return m_activeRow; } // returns -1 if there is no active row
int nextRow() const;
int previousRow() const;
void removeIndexes(QModelIndexList &indexes);
- int rowForVideo(Video* video);
- QModelIndex indexForVideo(Video* video);
+ int rowForVideo(Video *video);
+ QModelIndex indexForVideo(Video *video);
void move(QModelIndexList &indexes, bool up);
- Video* videoAt( int row ) const;
- Video* activeVideo() const;
+ Video *videoAt(int row) const;
+ Video *activeVideo() const;
int rowForCloneVideo(const QString &videoId) const;
- VideoSource* getVideoSource() { return videoSource; }
+ VideoSource *getVideoSource() { return videoSource; }
void setVideoSource(VideoSource *videoSource);
void abortSearch();
void exitAuthorPressed();
signals:
- void activeRowChanged(int);
- void needSelectionFor(const QVector<Video*> &videos);
+ void activeVideoChanged(Video *video, Video *previousVideo);
+ void needSelectionFor(const QVector<Video *> &videos);
void haveSuggestions(const QStringList &suggestions);
private:
- void handleFirstVideo(Video* video);
+ void handleFirstVideo(Video *video);
void searchMore(int max);
VideoSource *videoSource;
bool canSearchMore;
bool firstSearch;
- QVector<Video*> videos;
+ QVector<Video *> videos;
+ QVector<Video *> deletedVideos;
int startIndex;
int max;
$END_LICENSE */
#include "playlistview.h"
-#include "playlistmodel.h"
-#include "playlistitemdelegate.h"
#include "painterutils.h"
+#include "playlistitemdelegate.h"
+#include "playlistmodel.h"
-PlaylistView::PlaylistView(QWidget *parent) : QListView(parent),
- clickableAuthors(true) {
+PlaylistView::PlaylistView(QWidget *parent) : QListView(parent), clickableAuthors(true) {
setItemDelegate(new PlaylistItemDelegate(this));
setSelectionMode(QAbstractItemView::ExtendedSelection);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
-#if defined(APP_MAC)
- setMinimumWidth(160);
-#else
- setMinimumWidth(175);
-#endif
-
// dragndrop
setDragEnabled(true);
setAcceptDrops(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setUniformItemSizes(true);
- connect(this, SIGNAL(entered(const QModelIndex &)),
- SLOT(itemEntered(const QModelIndex &)));
+ connect(this, SIGNAL(entered(const QModelIndex &)), SLOT(itemEntered(const QModelIndex &)));
setMouseTracking(true);
+
+ QScrollBar *vScrollbar = verticalScrollBar();
+ connect(vScrollbar, &QAbstractSlider::valueChanged, this, [this, vScrollbar](int value) {
+ if (isVisible() && value == vScrollbar->maximum()) {
+ PlaylistModel *listModel = qobject_cast<PlaylistModel *>(model());
+ listModel->searchMore();
+ }
+ });
+ setMinimumHeight(PlaylistItemDelegate::thumbHeight * 4);
+
+ setMinimumWidth(PlaylistItemDelegate::thumbWidth);
+#ifndef APP_MAC
+ setMinimumWidth(minimumWidth() + vScrollbar->width());
+#endif
}
void PlaylistView::itemEntered(const QModelIndex &index) {
if (event->button() == Qt::LeftButton) {
if (isHoveringAuthor(event)) {
QMetaObject::invokeMethod(model(), "enterAuthorPressed");
+ } else if (isHoveringThumbnail(event)) {
+ const QModelIndex index = indexAt(event->pos());
+ emit activated(index);
+ unsetCursor();
+ return;
}
+ QListView::mousePressEvent(event);
}
- QListView::mousePressEvent(event);
}
void PlaylistView::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
QMetaObject::invokeMethod(model(), "exitAuthorPressed");
- const QModelIndex index = indexAt(event->pos());
- if (isHoveringThumbnail(event)) {
- emit activated(index);
- unsetCursor();
- } else if (isHoveringAuthor(event)) {
+ const QModelIndex index = indexAt(event->pos());
+ if (isHoveringAuthor(event)) {
emit authorPushed(index);
} else if (isShowMoreItem(index)) {
PlaylistModel *listModel = qobject_cast<PlaylistModel *>(model());
bool PlaylistView::isHoveringThumbnail(QMouseEvent *event) {
const QModelIndex index = indexAt(event->pos());
const QRect itemRect = visualRect(index);
- static const QRect thumbRect(0, 0, 160, 90);
+ static const QRect thumbRect(0, 0, PlaylistItemDelegate::thumbWidth,
+ PlaylistItemDelegate::thumbHeight);
const int x = event->x() - itemRect.x() - thumbRect.x();
const int y = event->y() - itemRect.y() - thumbRect.y();
return x > 0 && x < thumbRect.width() && y > 0 && y < thumbRect.height();
}
bool PlaylistView::isShowMoreItem(const QModelIndex &index) {
- return model()->rowCount() > 1 &&
- model()->rowCount() == index.row() + 1;
+ return model()->rowCount() > 1 && model()->rowCount() == index.row() + 1;
}
#include "refinesearchbutton.h"
#include "iconutils.h"
-RefineSearchButton::RefineSearchButton(QWidget *parent) :
- QPushButton(parent) {
-
+RefineSearchButton::RefineSearchButton(QWidget *parent) : QPushButton(parent) {
hovered = false;
const int refineButtonSize = 48;
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
-void RefineSearchButton::paintBackground() const {
-
-}
-
void RefineSearchButton::paintEvent(QPaintEvent *) {
+ QColor backgroundColor = palette().windowText().color();
+ backgroundColor.setAlpha(hovered ? 192 : 170);
+
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
- painter.setBrush(QColor(0,0,0, hovered ? 192 : 170));
- painter.drawEllipse(QPoint(width(), height()), width()-2, height()-2);
+ painter.setBrush(backgroundColor);
+ painter.drawEllipse(QPoint(width(), height()), width() - 2, height() - 2);
- QPixmap pixmap = IconUtils::pixmap(":/images/refine-search.png");
+ QPixmap pixmap =
+ IconUtils::iconPixmap("refine-search", 24, backgroundColor, devicePixelRatioF());
int pw = pixmap.width() / pixmap.devicePixelRatio();
int ph = pixmap.height() / pixmap.devicePixelRatio();
- painter.drawPixmap(width() - pw - 6, height() - ph - 6,
- pw, ph,
- pixmap);
+ painter.drawPixmap(width() - pw - 6, height() - ph - 6, pw, ph, pixmap);
}
void RefineSearchButton::enterEvent(QEvent *) {
#include <QtWidgets>
-class RefineSearchButton : public QPushButton
-{
+class RefineSearchButton : public QPushButton {
Q_OBJECT
public:
RefineSearchButton(QWidget *parent = 0);
void paintEvent(QPaintEvent *);
void enterEvent(QEvent *);
void leaveEvent(QEvent *);
-
+
private:
- void paintBackground() const;
bool hovered;
-
};
#endif // REFINESEARCHBUTTON_H
#include "iconutils.h"
#include "mainwindow.h"
-RefineSearchWidget::RefineSearchWidget(QWidget *parent) :
- QWidget(parent) {
+RefineSearchWidget::RefineSearchWidget(QWidget *parent) : QWidget(parent) {
dirty = false;
// Fixes background painting in fullscreen
setAutoFillBackground(true);
QString paramName = "sortBy";
setupLabel(tr("Sort by"), layout, paramName);
QToolBar *sortBar = setupBar(paramName);
- QActionGroup* sortGroup = new QActionGroup(this);
- const QStringList sortOptions = QStringList()
- << tr("Relevance")
- << tr("Date")
- << tr("View Count")
- << tr("Rating");
+ QActionGroup *sortGroup = new QActionGroup(this);
+ const QStringList sortOptions = QStringList() << tr("Relevance") << tr("Date")
+ << tr("View Count") << tr("Rating");
int i = 0;
for (const QString &actionName : sortOptions) {
QAction *action = new QAction(actionName, sortBar);
layout->addSpacing(spacing);
setupLabel(tr("Date"), layout, paramName);
QToolBar *timeBar = setupBar(paramName);
- QActionGroup* timeGroup = new QActionGroup(this);
+ QActionGroup *timeGroup = new QActionGroup(this);
const QStringList timeSpans = QStringList()
- << tr("Anytime")
- << tr("Today")
- << tr("7 Days")
- << tr("30 Days");
+ << tr("Anytime") << tr("Today") << tr("7 Days") << tr("30 Days");
i = 0;
for (const QString &actionName : timeSpans) {
QAction *action = new QAction(actionName, timeBar);
layout->addSpacing(spacing);
setupLabel(tr("Duration"), layout, paramName);
QToolBar *lengthBar = setupBar(paramName);
- QActionGroup* lengthGroup = new QActionGroup(this);
+ QActionGroup *lengthGroup = new QActionGroup(this);
const QStringList lengthOptions = QStringList()
- << tr("All")
- << tr("Short")
- << tr("Medium")
- << tr("Long");
+ << tr("All") << tr("Short") << tr("Medium") << tr("Long");
QStringList tips = QStringList()
- << ""
- << tr("Less than 4 minutes")
- << tr("Between 4 and 20 minutes")
- << tr("Longer than 20 minutes");
+ << "" << tr("Less than 4 minutes") << tr("Between 4 and 20 minutes")
+ << tr("Longer than 20 minutes");
i = 0;
for (const QString &actionName : lengthOptions) {
QAction *action = new QAction(actionName, timeBar);
layout->addSpacing(spacing);
setupLabel(tr("Quality"), layout, paramName);
QToolBar *qualityBar = setupBar(paramName);
- QActionGroup* qualityGroup = new QActionGroup(this);
- const QStringList qualityOptions = QStringList()
- << tr("All")
- << tr("High Definition");
- tips = QStringList()
- << ""
- << tr("720p or higher");
+ QActionGroup *qualityGroup = new QActionGroup(this);
+ const QStringList qualityOptions = QStringList() << tr("All") << tr("High Definition");
+ tips = QStringList() << "" << tr("720p or higher");
i = 0;
for (const QString &actionName : qualityOptions) {
QAction *action = new QAction(actionName, timeBar);
layout->addWidget(doneButton, 0, Qt::AlignLeft);
}
-void RefineSearchWidget::setupLabel(const QString &text, QBoxLayout *layout, const QString ¶mName) {
- QBoxLayout* hLayout = new QHBoxLayout();
+void RefineSearchWidget::setupLabel(const QString &text,
+ QBoxLayout *layout,
+ const QString ¶mName) {
+ QBoxLayout *hLayout = new QHBoxLayout();
hLayout->setSpacing(8);
hLayout->setMargin(0);
hLayout->setAlignment(Qt::AlignVCenter);
QLabel *icon = new QLabel(this);
icon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QString resource = paramName;
- QPixmap pixmap = IconUtils::pixmap(":/images/search-" + resource + ".png");
+ QByteArray iconName = QByteArrayLiteral("search-") + resource.toLatin1();
+ QPixmap pixmap = IconUtils::iconPixmap(iconName.constData(), 16, palette().window().color(),
+ devicePixelRatioF());
+
/*
QPixmap translucentPixmap(pixmap.size());
translucentPixmap.fill(Qt::transparent);
layout->addLayout(hLayout);
}
-QToolBar* RefineSearchWidget::setupBar(const QString ¶mName) {
- QToolBar* bar = new QToolBar(this);
+QToolBar *RefineSearchWidget::setupBar(const QString ¶mName) {
+ QToolBar *bar = new QToolBar(this);
bar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// bar->setProperty("segmented", true);
bar->setProperty("paramName", paramName);
- connect(bar, SIGNAL(actionTriggered(QAction*)), SLOT(actionTriggered(QAction*)));
+ connect(bar, SIGNAL(actionTriggered(QAction *)), SLOT(actionTriggered(QAction *)));
bars.insert(paramName, bar);
layout()->addWidget(bar);
return bar;
if (!params) return;
- QToolBar* bar;
- QAction* action;
+ QToolBar *bar;
+ QAction *action;
bar = bars.value("sortBy");
action = bar->actions().at(params->sortBy());
action = bar->actions().at(params->quality());
if (action) action->setChecked(true);
- disconnect(SIGNAL(paramChanged(QString,QVariant)));
- connect(this, SIGNAL(paramChanged(QString,QVariant)),
- params, SLOT(setParam(QString,QVariant)),
- Qt::UniqueConnection);
+ disconnect(SIGNAL(paramChanged(QString, QVariant)));
+ connect(this, SIGNAL(paramChanged(QString, QVariant)), params,
+ SLOT(setParam(QString, QVariant)), Qt::UniqueConnection);
dirty = false;
$END_LICENSE */
#include "regionsview.h"
-#include "ytregions.h"
#include "mainwindow.h"
+#include "ytregions.h"
RegionsView::RegionsView(QWidget *parent) : View(parent) {
QBoxLayout *l = new QVBoxLayout(this);
l->addLayout(layout);
addRegion(YTRegions::worldwideRegion());
- foreach(YTRegion region, YTRegions::list())
+ foreach (YTRegion region, YTRegions::list())
addRegion(region);
doneButton = new QPushButton(tr("Done"));
QString currentRegionId = YTRegions::currentRegionId();
for (int i = 0; i < layout->count(); i++) {
QLayoutItem *item = layout->itemAt(i);
- QPushButton *b = static_cast<QPushButton*>(item->widget());
+ QPushButton *b = static_cast<QPushButton *>(item->widget());
QString regionId = b->property("regionId").toString();
b->setChecked(currentRegionId == regionId);
}
QWidget::paintEvent(e);
QBrush brush;
if (window()->isActiveWindow()) {
- brush = Qt::white;
+ brush = palette().base();
} else {
brush = palette().window();
}
}
void RegionsView::buttonClicked() {
- QObject* o = sender();
+ QObject *o = sender();
QString regionId = o->property("regionId").toString();
YTRegions::setRegion(regionId);
emit regionChanged();
#include "autocomplete.h"
#include "iconutils.h"
-class SearchButton : public QAbstractButton {
+SearchLineEdit::SearchLineEdit(QWidget *parent) : QLineEdit(parent) {
+ setClearButtonEnabled(true);
+ setPlaceholderText(tr("Search"));
-public:
- SearchButton(QWidget *parent = 0);
- QMenu *m_menu;
-
-protected:
- void paintEvent(QPaintEvent *e);
- void mousePressEvent(QMouseEvent *e);
-
-};
-
-SearchButton::SearchButton(QWidget *parent)
- : QAbstractButton(parent),
- m_menu(0) {
- setObjectName(QLatin1String("SearchButton"));
- setCursor(Qt::ArrowCursor);
- setFocusPolicy(Qt::NoFocus);
-}
-
-void SearchButton::mousePressEvent(QMouseEvent *e) {
- if (m_menu && e->button() == Qt::LeftButton) {
- QWidget *p = parentWidget();
- if (p) {
- QPoint r = p->mapToGlobal(QPoint(0, p->height()));
- m_menu->exec(QPoint(r.x() + height() / 2, r.y()));
- }
- e->accept();
- }
- QAbstractButton::mousePressEvent(e);
-}
-
-void SearchButton::paintEvent(QPaintEvent *e) {
- Q_UNUSED(e);
- QPainter painter(this);
- const int h = height();
- int iconSize = 16;
- if (h > 30) iconSize = 22;
- QPixmap p = IconUtils::icon("edit-find").pixmap(iconSize, iconSize);
- int x = (width() - p.width()) / 2;
- int y = (h - p.height()) / 2;
- painter.drawPixmap(x, y, p);
-}
-
-SearchLineEdit::SearchLineEdit(QWidget *parent) : ExLineEdit(parent), searchButton(new SearchButton(this)) {
- connect(m_lineEdit, SIGNAL(textChanged(const QString &)), SIGNAL(textChanged(const QString &)));
- connect(m_lineEdit, SIGNAL(textEdited(const QString &)), SIGNAL(textEdited(const QString &)));
- connect(m_lineEdit, SIGNAL(returnPressed()), SLOT(returnPressed()));
-
- setLeftWidget(searchButton);
- inactiveText = tr("Search");
-
- QSizePolicy policy = sizePolicy();
- setSizePolicy(QSizePolicy::Preferred, policy.verticalPolicy());
+ QAction *searchAction = new QAction();
+ IconUtils::setIcon(searchAction, "edit-find");
+ addAction(searchAction, QLineEdit::LeadingPosition);
// completion
- autoComplete = new AutoComplete(this, m_lineEdit);
- connect(autoComplete, SIGNAL(suggestionAccepted(Suggestion*)), SIGNAL(suggestionAccepted(Suggestion*)));
-}
-
-void SearchLineEdit::paintEvent(QPaintEvent *e) {
- ExLineEdit::paintEvent(e);
- if (m_lineEdit->text().isEmpty() && !hasFocus() && !inactiveText.isEmpty()) {
- QStyleOptionFrame panel;
- initStyleOption(&panel);
- QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
- QFontMetrics fm = fontMetrics();
- int horizontalMargin = m_lineEdit->x();
- QRect lineRect(horizontalMargin + r.x(), r.y() + (r.height() - fm.height() + 1) / 2,
- r.width() - 2 * horizontalMargin, fm.height());
- QPainter painter(this);
- painter.setPen(palette().brush(QPalette::Disabled, QPalette::Text).color());
- painter.drawText(lineRect, Qt::AlignLeft | Qt::AlignVCenter, inactiveText);
- }
-}
+ autoComplete = new AutoComplete(this, this);
+ connect(autoComplete, SIGNAL(suggestionAccepted(Suggestion *)),
+ SIGNAL(suggestionAccepted(Suggestion *)));
-void SearchLineEdit::resizeEvent(QResizeEvent *e) {
- updateGeometries();
- ExLineEdit::resizeEvent(e);
-}
-
-void SearchLineEdit::updateGeometries() {
- int menuHeight = height();
- int menuWidth = menuHeight + 1;
- if (!searchButton->m_menu)
- menuWidth = (menuHeight / 5) * 4;
- searchButton->resize(QSize(menuWidth, menuHeight));
-}
-
-void SearchLineEdit::setInactiveText(const QString &text) {
- inactiveText = text;
-}
-
-void SearchLineEdit::setText(const QString &text) {
- m_lineEdit->setText(text);
-}
-
-AutoComplete *SearchLineEdit::getAutoComplete() {
- return autoComplete;
-}
-
-void SearchLineEdit::setMenu(QMenu *menu) {
- if (searchButton->m_menu)
- searchButton->m_menu->deleteLater();
- searchButton->m_menu = menu;
- updateGeometries();
+ connect(this, SIGNAL(returnPressed()), SLOT(returnPressed()));
}
QMenu *SearchLineEdit::menu() const {
- if (!searchButton->m_menu) {
- searchButton->m_menu = new QMenu(searchButton);
- if (isVisible())
- (const_cast<SearchLineEdit*>(this))->updateGeometries();
- }
- return searchButton->m_menu;
+ return nullptr;
}
-void SearchLineEdit::returnPressed() {
- QString text = m_lineEdit->text().simplified();
- if (!text.isEmpty()) {
- autoComplete->preventSuggest();
- emit search(text);
- }
+void SearchLineEdit::setMenu(QMenu *menu) {
+ Q_UNUSED(menu);
}
void SearchLineEdit::enableSuggest() {
autoComplete->preventSuggest();
}
-void SearchLineEdit::selectAll() {
- m_lineEdit->selectAll();
-}
-
void SearchLineEdit::setSuggester(Suggester *suggester) {
autoComplete->setSuggester(suggester);
}
-void SearchLineEdit::focusInEvent(QFocusEvent *e) {
- ExLineEdit::focusInEvent(e);
- enableSuggest();
+AutoComplete *SearchLineEdit::getAutoComplete() {
+ return autoComplete;
}
void SearchLineEdit::emitTextChanged(const QString &text) {
autoComplete->enableSuggest();
- emit textEdited(text);
+ emit QLineEdit::textEdited(text);
}
-QString SearchLineEdit::text() {
- return m_lineEdit->text();
+void SearchLineEdit::returnPressed() {
+ QString s = text().simplified();
+ if (!s.isEmpty()) {
+ autoComplete->preventSuggest();
+ emit search(s);
+ }
}
QLineEdit *SearchLineEdit::getLineEdit() {
- return m_lineEdit;
+ return this;
}
-void SearchLineEdit::setEnabled(bool enabled) {
- ExLineEdit::setEnabled(enabled);
- emit enabledChanged(enabled);
+QWidget *SearchLineEdit::toWidget() {
+ return this;
}
#include <QtWidgets>
-#include "exlineedit.h"
#include "searchwidget.h"
-class SearchButton;
-class Suggester;
-class AutoComplete;
-
-class SearchLineEdit : public ExLineEdit, public SearchWidget {
-
+class SearchLineEdit : public QLineEdit, public SearchWidget {
Q_OBJECT
public:
- SearchLineEdit(QWidget *parent = 0);
+ explicit SearchLineEdit(QWidget *parent = nullptr);
+
+ // SearchWidget interface
QMenu *menu() const;
void setMenu(QMenu *menu);
void enableSuggest();
void preventSuggest();
- void selectAll();
void setSuggester(Suggester *suggester);
- void setInactiveText(const QString &text);
- void setText(const QString &text);
AutoComplete *getAutoComplete();
void emitTextChanged(const QString &text);
- QString text();
QLineEdit *getLineEdit();
- QWidget *toWidget() { return qobject_cast<QWidget*>(this); }
+ QWidget *toWidget();
- void setEnabled(bool enabled);
+ void setPlaceholderText(const QString &text) { QLineEdit::setPlaceholderText(text); }
+ void selectAll() { QLineEdit::selectAll(); }
+ void setText(const QString &text) { QLineEdit::setText(text); }
+ QString text() { return QLineEdit::text(); }
public slots:
void returnPressed();
signals:
- void textChanged(const QString &text);
- void textEdited(const QString &text);
void search(const QString &text);
void suggestionAccepted(Suggestion *suggestion);
- void enabledChanged(bool enabled);
-
-protected:
- void updateGeometries();
- void resizeEvent(QResizeEvent *e);
- void paintEvent(QPaintEvent *e);
- void focusInEvent(QFocusEvent *e);
-
private:
- SearchButton *searchButton;
- QString inactiveText;
AutoComplete *autoComplete;
};
#endif // SEARCHLINEEDIT_H
-
#include <QtCore>
class SearchParams : public QObject {
-
Q_OBJECT
Q_PROPERTY(int sortBy READ sortBy WRITE setSortBy)
Q_PROPERTY(int duration READ duration WRITE setDuration)
Q_PROPERTY(int time READ time WRITE setTime)
public:
+ enum SortBy { SortByRelevance = 0, SortByNewest, SortByViewCount, SortByRating };
+
+ enum Duration { DurationAny = 0, DurationShort, DurationMedium, DurationLong };
+
+ enum Quality { QualityAny = 0, QualityHD };
- enum SortBy {
- SortByRelevance = 0,
- SortByNewest,
- SortByViewCount,
- SortByRating
- };
-
- enum Duration {
- DurationAny = 0,
- DurationShort,
- DurationMedium,
- DurationLong
- };
-
- enum Quality {
- QualityAny = 0,
- QualityHD
- };
-
- enum Time {
- TimeAny = 0,
- TimeToday,
- TimeWeek,
- TimeMonth
- };
-
- enum SafeSearch {
- None = 0,
- Moderate,
- Strict
- };
-
- SearchParams(QObject *parent = 0);
+ enum Time { TimeAny = 0, TimeToday, TimeWeek, TimeMonth };
+
+ enum SafeSearch { None = 0, Moderate, Strict };
+
+ SearchParams(QObject *parent = nullptr);
const QString &keywords() const { return m_keywords; }
void setKeywords(const QString &keywords) { m_keywords = keywords; }
void setChannelId(const QString &value) { m_channelId = value; }
int sortBy() const { return m_sortBy; }
- void setSortBy( int sortBy ) { m_sortBy = sortBy; }
+ void setSortBy(int sortBy) { m_sortBy = sortBy; }
int isTransient() const { return m_transient; }
- void setTransient( int transient ) { m_transient = transient; }
+ void setTransient(int transient) { m_transient = transient; }
int duration() const { return m_duration; }
- void setDuration( int duration ) { m_duration = duration; }
+ void setDuration(int duration) { m_duration = duration; }
int quality() const { return m_quality; }
- void setQuality( int quality ) { m_quality = quality; }
+ void setQuality(int quality) { m_quality = quality; }
int time() const { return m_time; }
- void setTime( int time ) { m_time = time; }
+ void setTime(int time) { m_time = time; }
uint publishedAfter() const { return m_publishedAfter; }
void setPublishedAfter(uint value) { m_publishedAfter = value; }
int safeSearch() const { return m_safeSearch; }
- void setSafeSearch( int safeSearch ) { m_safeSearch = safeSearch; }
+ void setSafeSearch(int safeSearch) { m_safeSearch = safeSearch; }
bool operator==(const SearchParams &other) const {
- return m_keywords == other.keywords() &&
- m_channelId == other.channelId();
+ return m_keywords == other.keywords() && m_channelId == other.channelId();
}
public slots:
int m_time;
uint m_publishedAfter;
int m_safeSearch;
-
};
#endif // SEARCHPARAMS_H
$END_LICENSE */
#include "searchview.h"
+#include "channelsuggest.h"
#include "constants.h"
#include "fontutils.h"
#include "searchparams.h"
#include "ytsuggester.h"
-#include "channelsuggest.h"
#ifdef APP_MAC_SEARCHFIELD
#include "searchlineedit_mac.h"
#else
#include "searchlineedit.h"
#endif
+#ifdef APP_MAC
+#include "macutils.h"
+#endif
#ifdef APP_EXTRA
#include "extra.h"
#endif
#include "activation.h"
#include "activationview.h"
#endif
+#include "clickablelabel.h"
+#include "iconutils.h"
#include "mainwindow.h"
#include "painterutils.h"
-#include "iconutils.h"
-#include "clickablelabel.h"
namespace {
-static const QString recentKeywordsKey = "recentKeywords";
-static const QString recentChannelsKey = "recentChannels";
-}
+const QString recentKeywordsKey = "recentKeywords";
+const QString recentChannelsKey = "recentChannels";
+} // namespace
SearchView::SearchView(QWidget *parent) : View(parent) {
- const int padding = 30;
+ setBackgroundRole(QPalette::Base);
+ setForegroundRole(QPalette::Text);
+ setAutoFillBackground(true);
- // speedup painting since we'll paint the whole background
- // by ourselves anyway in paintEvent()
- setAttribute(Qt::WA_OpaquePaintEvent);
+ const int padding = 30;
QBoxLayout *vLayout = new QVBoxLayout(this);
vLayout->setMargin(padding);
hLayout->addStretch();
- logo = new ClickableLabel(this);
- logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
+ logo = new ClickableLabel();
+ auto setLogoPixmap = [this] {
+ logo->setPixmap(IconUtils::pixmap(":/images/app.png", logo->devicePixelRatioF()));
+ };
+ setLogoPixmap();
+ connect(window()->windowHandle(), &QWindow::screenChanged, this, setLogoPixmap);
connect(logo, &ClickableLabel::clicked, MainWindow::instance(), &MainWindow::visitSite);
hLayout->addWidget(logo, 0, Qt::AlignTop);
hLayout->addSpacing(padding);
layout->setAlignment(Qt::AlignCenter);
hLayout->addLayout(layout);
- QColor titleColor = palette().color(QPalette::WindowText);
- titleColor.setAlphaF(.75);
- int r,g,b,a;
- titleColor.getRgb(&r,&g,&b,&a);
- QString cssColor = QString::asprintf("rgba(%d,%d,%d,%d)", r, g, b, a);
-
- QLabel *welcomeLabel =
- new QLabel(QString("<h1 style='font-weight:300;color:%1'>").arg(cssColor) +
- tr("Welcome to <a href='%1'>%2</a>,")
- .replace("<a ", "<a style='text-decoration:none; color:palette(text)' ")
- .arg(Constants::WEBSITE, Constants::NAME)
- + "</h1>");
+ QLabel *welcomeLabel = new QLabel();
+ auto setupWelcomeLabel = [this, welcomeLabel] {
+ QColor titleColor = palette().color(QPalette::WindowText);
+ titleColor.setAlphaF(.75);
+ int r, g, b, a;
+ titleColor.getRgb(&r, &g, &b, &a);
+ QString cssColor = QString::asprintf("rgba(%d,%d,%d,%d)", r, g, b, a);
+ QString text =
+ QString("<h1 style='font-weight:300;color:%1'>").arg(cssColor) +
+ tr("Welcome to <a href='%1'>%2</a>,")
+ .replace("<a ", "<a style='text-decoration:none; color:palette(text)' ")
+ .arg(Constants::WEBSITE, Constants::NAME) +
+ "</h1>";
+ welcomeLabel->setText(text);
+ };
+ setupWelcomeLabel();
+ connect(qApp, &QGuiApplication::paletteChanged, this, setupWelcomeLabel);
welcomeLabel->setOpenExternalLinks(true);
- welcomeLabel->setProperty("heading", true);
- welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize() * 1.25));
+ welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize()));
layout->addWidget(welcomeLabel);
layout->addSpacing(padding / 2);
-#ifndef APP_MAC
- const QFont &biggerFont = FontUtils::big();
-#endif
-
//: "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"
// QLabel *tipLabel = new QLabel(tr("Enter"), this);
-
QString tip;
if (qApp->layoutDirection() == Qt::RightToLeft) {
tip = tr("to start watching videos.") + " " + tr("a keyword") + " " + tr("Enter");
} else {
tip = tr("Enter") + " " + tr("a keyword") + " " + tr("to start watching videos.");
}
- QLabel *tipLabel = new QLabel(tip);
-
-#ifndef APP_MAC
- tipLabel->setFont(biggerFont);
-#endif
- layout->addWidget(tipLabel);
-
- /*
- typeCombo = new QComboBox(this);
- typeCombo->addItem(tr("a keyword"));
- typeCombo->addItem(tr("a channel"));
-#ifndef APP_MAC
- typeCombo->setFont(biggerFont);
-#endif
- connect(typeCombo, SIGNAL(currentIndexChanged(int)), SLOT(searchTypeChanged(int)));
- tipLayout->addWidget(typeCombo);
-
- tipLabel = new QLabel(tr("to start watching videos."), this);
-#ifndef APP_MAC
- tipLabel->setFont(biggerFont);
-#endif
- tipLayout->addWidget(tipLabel);
- */
layout->addSpacing(padding / 2);
- QHBoxLayout *searchLayout = new QHBoxLayout();
+ QBoxLayout *searchLayout = new QHBoxLayout();
searchLayout->setAlignment(Qt::AlignVCenter);
#ifdef APP_MAC_SEARCHFIELD
setFocusProxy(slem);
#else
SearchLineEdit *sle = new SearchLineEdit(this);
- sle->setFont(biggerFont);
+ sle->setFont(FontUtils::medium());
+ int tipWidth = sle->fontMetrics().size(Qt::TextSingleLine, tip).width();
+ sle->setMinimumWidth(tipWidth + sle->fontMetrics().width('m') * 6);
+ sle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
queryEdit = sle;
#endif
- connect(queryEdit->toWidget(), SIGNAL(search(const QString&)), SLOT(watch(const QString&)));
- connect(queryEdit->toWidget(), SIGNAL(textChanged(const QString &)), SLOT(textChanged(const QString &)));
- connect(queryEdit->toWidget(), SIGNAL(textEdited(const QString &)), SLOT(textChanged(const QString &)));
- connect(queryEdit->toWidget(), SIGNAL(suggestionAccepted(Suggestion*)), SLOT(suggestionAccepted(Suggestion*)));
+ connect(queryEdit->toWidget(), SIGNAL(search(const QString &)), SLOT(watch(const QString &)));
+ connect(queryEdit->toWidget(), SIGNAL(textChanged(const QString &)),
+ SLOT(textChanged(const QString &)));
+ connect(queryEdit->toWidget(), SIGNAL(textEdited(const QString &)),
+ SLOT(textChanged(const QString &)));
+ connect(queryEdit->toWidget(), SIGNAL(suggestionAccepted(Suggestion *)),
+ SLOT(suggestionAccepted(Suggestion *)));
+ queryEdit->setPlaceholderText(tip);
youtubeSuggest = new YTSuggester(this);
channelSuggest = new ChannelSuggest(this);
- connect(channelSuggest, SIGNAL(ready(QVector<Suggestion*>)), SLOT(onChannelSuggestions(QVector<Suggestion*>)));
+ connect(channelSuggest, SIGNAL(ready(QVector<Suggestion *>)),
+ SLOT(onChannelSuggestions(QVector<Suggestion *>)));
searchTypeChanged(0);
searchLayout->addWidget(queryEdit->toWidget(), 0, Qt::AlignBaseline);
- searchLayout->addSpacing(padding);
- watchButton = new QPushButton(tr("Watch"));
-#ifndef APP_MAC
- watchButton->setFont(biggerFont);
-#endif
- watchButton->setDefault(true);
- watchButton->setEnabled(false);
- watchButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- connect(watchButton, SIGNAL(clicked()), this, SLOT(watch()));
- searchLayout->addWidget(watchButton, 0, Qt::AlignBaseline);
-
- layout->addItem(searchLayout);
+ layout->addLayout(searchLayout);
layout->addSpacing(padding);
recentKeywordsLayout->setSpacing(0);
recentKeywordsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
recentKeywordsLabel = new QLabel(tr("Recent keywords"));
- recentKeywordsLabel->setEnabled(false);
recentKeywordsLabel->setProperty("recentHeader", true);
recentKeywordsLabel->hide();
+ recentKeywordsLabel->setEnabled(false);
recentKeywordsLayout->addWidget(recentKeywordsLabel);
recentLayout->addLayout(recentKeywordsLayout);
recentChannelsLayout->setSpacing(0);
recentChannelsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
recentChannelsLabel = new QLabel(tr("Recent channels"));
- recentChannelsLabel->setEnabled(false);
recentChannelsLabel->setProperty("recentHeader", true);
recentChannelsLabel->hide();
+ recentChannelsLabel->setEnabled(false);
recentChannelsLayout->addWidget(recentChannelsLabel);
recentLayout->addLayout(recentChannelsLayout);
#ifdef APP_ACTIVATION
if (!Activation::instance().isActivated())
- vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0, Qt::AlignRight);
+ vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0,
+ Qt::AlignRight);
#endif
}
void SearchView::appear() {
MainWindow *w = MainWindow::instance();
- w->showActionInStatusBar(w->getAction("manualplay"), true);
- w->showActionInStatusBar(w->getAction("safeSearch"), true);
- w->showActionInStatusBar(w->getAction("definition"), true);
+ w->showActionsInStatusBar(
+ {w->getAction("manualplay"), w->getAction("safeSearch"), w->getAction("definition")},
+ true);
updateRecentKeywords();
updateRecentChannels();
queryEdit->selectAll();
queryEdit->enableSuggest();
- if (!queryEdit->toWidget()->hasFocus()) queryEdit->toWidget()->setFocus();
-
- connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection);
-
- qApp->processEvents();
- update();
-
-#ifdef APP_MAC
- // Workaround cursor bug on macOS
- window()->unsetCursor();
-#endif
+ QTimer::singleShot(0, queryEdit->toWidget(), SLOT(setFocus()));
}
void SearchView::disappear() {
MainWindow *w = MainWindow::instance();
- w->showActionInStatusBar(w->getAction("safeSearch"), false);
- w->showActionInStatusBar(w->getAction("definition"), false);
- w->showActionInStatusBar(w->getAction("manualplay"), false);
+ w->showActionsInStatusBar(
+ {w->getAction("manualplay"), w->getAction("safeSearch"), w->getAction("definition")},
+ false);
}
void SearchView::updateRecentKeywords() {
// cleanup
QLayoutItem *item;
- while ((item = recentKeywordsLayout->takeAt(1)) != 0) {
+ while (recentKeywordsLayout->count() - 1 > recentKeywords.size() &&
+ (item = recentKeywordsLayout->takeAt(1)) != nullptr) {
item->widget()->close();
delete item;
}
const int maxDisplayLength = 25;
+#ifdef APP_MAC
+ QPalette p = palette();
+ p.setColor(QPalette::Highlight, mac::accentColor());
+#endif
+
+ int counter = 1;
for (const QString &keyword : keywords) {
QString link = keyword;
QString display = keyword;
- if (keyword.startsWith(QLatin1String("http://")) || keyword.startsWith(QLatin1String("https://"))) {
+ if (keyword.startsWith(QLatin1String("http://")) ||
+ keyword.startsWith(QLatin1String("https://"))) {
int separator = keyword.indexOf('|');
if (separator > 0 && separator + 1 < keyword.length()) {
link = keyword.left(separator);
- display = keyword.mid(separator+1);
+ display = keyword.mid(separator + 1);
}
}
- bool needStatusTip = false;
- if (display.length() > maxDisplayLength) {
+ bool needStatusTip = display.length() > maxDisplayLength;
+ if (needStatusTip) {
display.truncate(maxDisplayLength);
display.append(QStringLiteral("\u2026"));
- needStatusTip = true;
}
- QPushButton *itemButton = new QPushButton(display);
- itemButton->setAttribute(Qt::WA_DeleteOnClose);
- itemButton->setProperty("recentItem", true);
- itemButton->setCursor(Qt::PointingHandCursor);
- itemButton->setFocusPolicy(Qt::TabFocus);
- itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+
+ ClickableLabel *item;
+ if (recentKeywordsLayout->count() - 1 >= counter) {
+ item = qobject_cast<ClickableLabel *>(recentKeywordsLayout->itemAt(counter)->widget());
+
+ } else {
+ item = new ClickableLabel();
+#ifdef APP_MAC
+ item->setPalette(p);
+#endif
+ item->setAttribute(Qt::WA_DeleteOnClose);
+ item->setProperty("recentItem", true);
+ item->setFocusPolicy(Qt::TabFocus);
+ connect(item, &ClickableLabel::hovered, this, [item, this](bool value) {
+ item->setForegroundRole(value ? QPalette::Highlight : QPalette::WindowText);
+ if (value) {
+ for (int i = 1; i < recentKeywordsLayout->count(); ++i) {
+ QWidget *w = recentKeywordsLayout->itemAt(i)->widget();
+ if (w != item) {
+ w->setForegroundRole(QPalette::WindowText);
+ }
+ }
+ }
+ });
+ recentKeywordsLayout->addWidget(item);
+ }
+
+ item->setText(display);
if (needStatusTip)
- itemButton->setStatusTip(link);
- connect(itemButton, &QPushButton::clicked, [this,link]() {
- watchKeywords(link);
- });
+ item->setStatusTip(link);
+ else
+ item->setStatusTip(QString());
- recentKeywordsLayout->addWidget(itemButton);
- }
+ disconnect(item, &ClickableLabel::clicked, nullptr, nullptr);
+ connect(item, &ClickableLabel::clicked, this, [this, link]() { watchKeywords(link); });
+ counter++;
+ }
}
void SearchView::updateRecentChannels() {
// cleanup
QLayoutItem *item;
- while ((item = recentChannelsLayout->takeAt(1)) != 0) {
+ while ((item = recentChannelsLayout->takeAt(1)) != nullptr) {
item->widget()->close();
delete item;
}
recentChannelsLabel->setVisible(!keywords.isEmpty());
- // TODO MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty());
+
+#ifdef APP_MAC
+ QPalette p = palette();
+ p.setColor(QPalette::Highlight, mac::accentColor());
+#endif
for (const QString &keyword : keywords) {
QString link = keyword;
int separator = keyword.indexOf('|');
if (separator > 0 && separator + 1 < keyword.length()) {
link = keyword.left(separator);
- display = keyword.mid(separator+1);
+ display = keyword.mid(separator + 1);
}
- QPushButton *itemButton = new QPushButton(display);
- itemButton->setAttribute(Qt::WA_DeleteOnClose);
- itemButton->setProperty("recentItem", true);
- itemButton->setCursor(Qt::PointingHandCursor);
- itemButton->setFocusPolicy(Qt::TabFocus);
- itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
- connect(itemButton, &QPushButton::clicked, [this,link]() {
- watchChannel(link);
+
+ ClickableLabel *item = new ClickableLabel(display);
+#ifdef APP_MAC
+ item->setPalette(p);
+#endif
+ item->setAttribute(Qt::WA_DeleteOnClose);
+ item->setProperty("recentItem", true);
+ item->setFocusPolicy(Qt::TabFocus);
+ connect(item, &ClickableLabel::clicked, [this, link]() { watchChannel(link); });
+ connect(item, &ClickableLabel::hovered, item, [item](bool value) {
+ item->setForegroundRole(value ? QPalette::Highlight : QPalette::WindowText);
});
- recentChannelsLayout->addWidget(itemButton);
+ recentChannelsLayout->addWidget(item);
}
}
}
void SearchView::textChanged(const QString &text) {
- watchButton->setEnabled(!text.simplified().isEmpty());
lastChannelSuggestions.clear();
}
return;
}
- /*
- if (typeCombo->currentIndex() == 1) {
- // Channel search
- MainWindow::instance()->channelSearch(q);
- return;
- }
- */
-
SearchParams *searchParams = new SearchParams();
searchParams->setKeywords(q);
return;
}
- // if (typeCombo->currentIndex() == 0) {
- queryEdit->setText(q);
- watchButton->setEnabled(true);
- // }
+ queryEdit->setText(q);
SearchParams *searchParams = new SearchParams();
searchParams->setKeywords(q);
emit search(searchParams);
}
-void SearchView::paintEvent(QPaintEvent *event) {
- QWidget::paintEvent(event);
- QBrush brush;
- if (window()->isActiveWindow()) {
- brush = palette().base();
- } else {
- brush = palette().window();
- }
- QPainter painter(this);
- painter.fillRect(0, 0, width(), height(), brush);
-}
-
void SearchView::searchTypeChanged(int index) {
if (index == 0) {
queryEdit->setSuggester(youtubeSuggest);
void SearchView::suggestionAccepted(Suggestion *suggestion) {
if (suggestion->type == QLatin1String("channel")) {
watchChannel(suggestion->userData);
- } else watch(suggestion->value);
-}
-
-void SearchView::screenChanged() {
- logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
+ } else
+ watch(suggestion->value);
}
void SearchView::onChannelSuggestions(const QVector<Suggestion *> &suggestions) {
$END_LICENSE */
-#ifndef __SEARCHVIEW_H__
-#define __SEARCHVIEW_H__
+#ifndef SEARCHVIEW_H
+#define SEARCHVIEW_H
#include <QtWidgets>
class ClickableLabel;
class SearchView : public View {
-
Q_OBJECT
public:
- SearchView(QWidget *parent = 0);
+ SearchView(QWidget *parent = nullptr);
void updateRecentKeywords();
void updateRecentChannels();
void watchKeywords(const QString &query);
signals:
- void search(SearchParams*);
-
-protected:
- void paintEvent(QPaintEvent *);
+ void search(SearchParams *);
private slots:
void watch();
void textChanged(const QString &text);
void searchTypeChanged(int index);
void suggestionAccepted(Suggestion *suggestion);
- void screenChanged();
- void onChannelSuggestions(const QVector<Suggestion*> &suggestions);
+ void onChannelSuggestions(const QVector<Suggestion *> &suggestions);
private:
YTSuggester *youtubeSuggest;
ChannelSuggest *channelSuggest;
- QComboBox *typeCombo;
SearchWidget *queryEdit;
QLabel *recentKeywordsLabel;
QBoxLayout *recentKeywordsLayout;
QLabel *recentChannelsLabel;
QBoxLayout *recentChannelsLayout;
QLabel *message;
- QPushButton *watchButton;
QStringList recentKeywords;
QStringList recentChannels;
- QVector<Suggestion*> lastChannelSuggestions;
+ QVector<Suggestion *> lastChannelSuggestions;
ClickableLabel *logo;
};
-#endif // __SEARCHVIEW_H__
+#endif // SEARCHVIEW_H
class AutoComplete;
class SearchWidget {
-
public:
virtual QMenu *menu() const = 0;
virtual void setMenu(QMenu *menu) = 0;
virtual void preventSuggest() = 0;
virtual void selectAll() = 0;
virtual void setSuggester(Suggester *suggester) = 0;
- virtual void setInactiveText(const QString &text) = 0;
+ virtual void setPlaceholderText(const QString &text) = 0;
virtual void setText(const QString &text) = 0;
virtual AutoComplete *getAutoComplete() = 0;
virtual void emitTextChanged(const QString &text) = 0;
void textEdited(const QString &text);
void search(const QString &text);
void suggestionAccepted(Suggestion *suggestion);
-
};
#endif // SEARCHWIDGET
class MyProxyStyle : public QProxyStyle {
public:
- int styleHint(StyleHint hint, const QStyleOption *option = 0,
- const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const {
- if (hint == SH_Slider_AbsoluteSetButtons)
- return Qt::LeftButton;
- return QProxyStyle::styleHint(hint, option, widget, returnData);
- }
+ int styleHint(StyleHint hint,
+ const QStyleOption *option = nullptr,
+ const QWidget *widget = nullptr,
+ QStyleHintReturn *returnData = nullptr) const;
};
+int MyProxyStyle::styleHint(QStyle::StyleHint hint,
+ const QStyleOption *option,
+ const QWidget *widget,
+ QStyleHintReturn *returnData) const {
+ if (hint == SH_Slider_AbsoluteSetButtons) return Qt::LeftButton;
+ return QProxyStyle::styleHint(hint, option, widget, returnData);
+}
+
SeekSlider::SeekSlider(QWidget *parent) : QSlider(parent) {
setStyle(new MyProxyStyle());
}
#include <QtWidgets>
class SeekSlider : public QSlider {
-
Q_OBJECT
public:
- SeekSlider(QWidget *parent = 0);
-
+ SeekSlider(QWidget *parent = nullptr);
};
#endif // SEEKSLIDER_H
$END_LICENSE */
#include "segmentedcontrol.h"
-#include "mainwindow.h"
#include "fontutils.h"
#include "iconutils.h"
+#include "mainwindow.h"
#include "painterutils.h"
-SegmentedControl::SegmentedControl (QWidget *parent) : QWidget(parent) {
+SegmentedControl::SegmentedControl(QWidget *parent) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
setMouseTracking(true);
- hoveredAction = 0;
- checkedAction = 0;
- pressedAction = 0;
+ hoveredAction = nullptr;
+ checkedAction = nullptr;
+ pressedAction = nullptr;
-#ifdef APP_WIN
- selectedColor = palette().color(QPalette::Base);
-#else
- selectedColor = palette().color(QPalette::Window);
-#endif
- int darkerFactor = 105;
- backgroundColor = selectedColor.darker(darkerFactor);
- borderColor = backgroundColor;
- hoveredColor = backgroundColor.darker(darkerFactor);
- pressedColor = hoveredColor.darker(darkerFactor);
+ setupColors();
+ connect(qApp, &QGuiApplication::paletteChanged, this, [this] {
+ setupColors();
+ update();
+ });
}
QAction *SegmentedControl::addAction(QAction *action) {
bool SegmentedControl::setCheckedAction(int index) {
if (index < 0) {
- checkedAction = 0;
+ checkedAction = nullptr;
return true;
}
- QAction* newCheckedAction = actionList.at(index);
+ QAction *newCheckedAction = actionList.at(index);
return setCheckedAction(newCheckedAction);
}
if (checkedAction == action) {
return false;
}
- if (checkedAction)
- checkedAction->setChecked(false);
+ if (checkedAction) checkedAction->setChecked(false);
checkedAction = action;
checkedAction->setChecked(true);
update();
return true;
}
-QSize SegmentedControl::minimumSizeHint (void) const {
+QSize SegmentedControl::minimumSizeHint() const {
int itemsWidth = calculateButtonWidth() * actionList.size() * 1.2;
- return(QSize(itemsWidth, QFontMetrics(font()).height() * 1.8));
+ return (QSize(itemsWidth, QFontMetrics(font()).height() * 1.8));
}
-void SegmentedControl::paintEvent (QPaintEvent * /*event*/) {
+void SegmentedControl::paintEvent(QPaintEvent * /*event*/) {
const int height = rect().height();
const int width = rect().width();
// Calculate Buttons Size & Location
const int buttonWidth = width / actionList.size();
- const qreal pixelRatio = IconUtils::pixelRatio();
-
+ const qreal pixelRatio = devicePixelRatioF();
QPen pen(borderColor);
const qreal penWidth = 1. / pixelRatio;
pen.setWidthF(penWidth);
QAction *action = actionList.at(i);
if (i + 1 == actionCount) {
// last button
- rect.setWidth(width - buttonWidth * (actionCount-1));
+ rect.setWidth(width - buttonWidth * (actionCount - 1));
paintButton(&p, rect, action);
} else {
paintButton(&p, rect, action);
rect.moveLeft(rect.x() + rect.width());
}
}
- const qreal y = height - penWidth;
- p.drawLine(QPointF(0, y), QPointF(width, y));
}
-void SegmentedControl::mouseMoveEvent (QMouseEvent *event) {
+void SegmentedControl::mouseMoveEvent(QMouseEvent *event) {
QAction *action = findHoveredAction(event->pos());
if (!action && hoveredAction) {
- hoveredAction = 0;
+ hoveredAction = nullptr;
update();
} else if (action && action != hoveredAction) {
hoveredAction = action;
void SegmentedControl::mouseReleaseEvent(QMouseEvent *event) {
QWidget::mouseReleaseEvent(event);
- pressedAction = 0;
+ pressedAction = nullptr;
if (hoveredAction) {
bool changed = setCheckedAction(hoveredAction);
if (changed) hoveredAction->trigger();
QWidget::leaveEvent(event);
// status tip
MainWindow::instance()->statusBar()->clearMessage();
- hoveredAction = 0;
- pressedAction = 0;
+ hoveredAction = nullptr;
+ pressedAction = nullptr;
update();
}
-QAction *SegmentedControl::findHoveredAction(const QPoint& pos) const {
+void SegmentedControl::setupColors() {
+ selectedColor = palette().color(QPalette::Base);
+ if (selectedColor.value() > 128) {
+ int factor = 105;
+ backgroundColor = selectedColor.darker(factor);
+ borderColor = backgroundColor;
+ hoveredColor = backgroundColor.darker(factor);
+ pressedColor = hoveredColor.darker(factor);
+ } else {
+ int factor = 130;
+ backgroundColor = selectedColor.lighter(factor);
+ borderColor = backgroundColor;
+ hoveredColor = backgroundColor.lighter(factor);
+ pressedColor = hoveredColor.lighter(factor);
+ }
+}
+
+QAction *SegmentedControl::findHoveredAction(const QPoint &pos) const {
const int w = width();
- if (pos.y() <= 0 || pos.x() >= w || pos.y() >= height())
- return 0;
+ if (pos.y() <= 0 || pos.x() >= w || pos.y() >= height()) return nullptr;
int buttonWidth = w / actionList.size();
int buttonIndex = pos.x() / buttonWidth;
- if (buttonIndex >= actionList.size())
- return 0;
+ if (buttonIndex >= actionList.size()) return nullptr;
return actionList[buttonIndex];
}
return itemWidth;
}
-void SegmentedControl::paintButton(QPainter *painter, const QRect& rect, const QAction *action) {
+void SegmentedControl::paintButton(QPainter *painter, const QRect &rect, const QAction *action) {
painter->save();
painter->translate(rect.topLeft());
painter->drawText(0, 0, width, height, Qt::AlignCenter, text);
if (action->property("notifyCount").isValid()) {
- QRect textBox = painter->boundingRect(rect,
- Qt::AlignCenter,
- text);
+ QRect textBox = painter->boundingRect(rect, Qt::AlignCenter, text);
painter->translate((width + textBox.width()) / 2 + 10, (height - textBox.height()) / 2);
- PainterUtils::paintBadge(painter, action->property("notifyCount").toString(), false, QColor(0,0,0,64));
+ PainterUtils::paintBadge(painter, action->property("notifyCount").toString(), false, c);
}
painter->restore();
#include <QtWidgets>
class SegmentedControl : public QWidget {
-
Q_OBJECT
public:
- SegmentedControl(QWidget *parent = 0);
+ SegmentedControl(QWidget *parent = nullptr);
QAction *addAction(QAction *action);
bool setCheckedAction(int index);
bool setCheckedAction(QAction *action);
QSize minimumSizeHint(void) const;
signals:
- void checkedActionChanged(QAction & action);
+ void checkedActionChanged(QAction &action);
protected:
void paintEvent(QPaintEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void leaveEvent(QEvent *event);
+private slots:
+ void setupColors();
+
private:
- void paintButton(QPainter *painter,
- const QRect& rect,
- const QAction *action);
- QAction *findHoveredAction(const QPoint& pos) const;
+ void paintButton(QPainter *painter, const QRect &rect, const QAction *action);
+ QAction *findHoveredAction(const QPoint &pos) const;
int calculateButtonWidth() const;
QVector<QAction *> actionList;
QColor selectedColor;
QColor hoveredColor;
QColor pressedColor;
-
};
#endif /* !SEGMENTEDCONTROL_H */
}
void ShareToolbar::setLeftMargin(int value) {
- setStyleSheet("border:0;margin-left:" + QString::number(value) + "px");
- disconnect(sender(), 0, this, 0);
+ setStyleSheet("QToolButton {border:0;margin-left:" + QString::number(value) + "px}");
+ disconnect(sender(), nullptr, this, nullptr);
}
$END_LICENSE */
#include "sidebarheader.h"
+#include "fontutils.h"
#include "iconutils.h"
+#include "mainwindow.h"
#include "mediaview.h"
#include "videosource.h"
-#include "fontutils.h"
-SidebarHeader::SidebarHeader(QWidget *parent) : QToolBar(parent) { }
+SidebarHeader::SidebarHeader(QWidget *parent) : QToolBar(parent) {}
void SidebarHeader::setup() {
static bool isSetup = false;
setIconSize(QSize(16, 16));
- backAction = new QAction(
- IconUtils::icon("go-previous"),
- tr("&Back"), this);
+ backAction = new QAction(tr("&Back"), this);
+ IconUtils::setIcon(backAction, "go-previous");
backAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Left));
connect(backAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goBack()));
addAction(backAction);
- forwardAction = new QAction(
- IconUtils::icon("go-next"),
- tr("&Back"), this);
+ forwardAction = new QAction(tr("&Forward"), this);
+ IconUtils::setIcon(forwardAction, "go-next");
forwardAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Right));
connect(forwardAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goForward()));
addAction(forwardAction);
const auto a = actions();
- for (QAction* action : a) {
+ for (QAction *action : a) {
window()->addAction(action);
- IconUtils::setupAction(action);
+ MainWindow::instance()->setupAction(action);
}
QWidget *spacerWidget = new QWidget(this);
void SidebarHeader::updateInfo() {
setup();
- const QVector<VideoSource*> &history = MediaView::instance()->getHistory();
+ const QVector<VideoSource *> &history = MediaView::instance()->getHistory();
int currentIndex = MediaView::instance()->getHistoryIndex();
bool canGoForward = MediaView::instance()->canGoForward();
forwardAction->setEnabled(canGoForward);
if (canGoForward) {
VideoSource *nextVideoSource = history.at(currentIndex + 1);
- forwardAction->setStatusTip(
- tr("Forward to %1")
- .arg(nextVideoSource->getName())
- + " (" + forwardAction->shortcut().toString(QKeySequence::NativeText) + ")"
- );
+ forwardAction->setStatusTip(tr("Forward to %1").arg(nextVideoSource->getName()) + " (" +
+ forwardAction->shortcut().toString(QKeySequence::NativeText) +
+ ")");
}
bool canGoBack = MediaView::instance()->canGoBack();
backAction->setEnabled(canGoBack);
if (canGoBack) {
VideoSource *previousVideoSource = history.at(currentIndex - 1);
- backAction->setStatusTip(
- tr("Back to %1")
- .arg(previousVideoSource->getName())
- + " (" + backAction->shortcut().toString(QKeySequence::NativeText) + ")"
- );
+ backAction->setStatusTip(tr("Back to %1").arg(previousVideoSource->getName()) + " (" +
+ backAction->shortcut().toString(QKeySequence::NativeText) + ")");
}
VideoSource *currentVideoSource = history.at(currentIndex);
- connect(currentVideoSource, SIGNAL(nameChanged(QString)),
- SLOT(updateTitle(QString)), Qt::UniqueConnection);
+ connect(currentVideoSource, SIGNAL(nameChanged(QString)), SLOT(updateTitle(QString)),
+ Qt::UniqueConnection);
setTitle(currentVideoSource->getName());
}
this->title = title;
update();
- QVector<VideoSource*> history = MediaView::instance()->getHistory();
+ QVector<VideoSource *> history = MediaView::instance()->getHistory();
int currentIndex = MediaView::instance()->getHistoryIndex();
VideoSource *currentVideoSource = history.at(currentIndex);
- for (QAction* action : videoSourceActions)
+ for (QAction *action : videoSourceActions)
removeAction(action);
videoSourceActions = currentVideoSource->getActions();
addActions(videoSourceActions);
}
void SidebarHeader::paintEvent(QPaintEvent *event) {
- QToolBar::paintEvent(event);
if (title.isEmpty()) return;
QPainter p(this);
- p.setPen(Qt::white);
+ p.setPen(palette().windowText().color());
const QRect r = rect();
QRect textBox = p.boundingRect(r, Qt::AlignCenter, t);
int i = 1;
const int margin = forwardAction->isVisible() ? 50 : 25;
- while (textBox.width() > r.width() - margin*2 && t.length() > 3) {
+ while (textBox.width() > r.width() - margin * 2 && t.length() > 3) {
t = t.left(t.length() - i).trimmed() + QStringLiteral("\u2026");
textBox = p.boundingRect(r, Qt::AlignCenter, t);
i++;
}
p.drawText(r, Qt::AlignCenter, t);
-
-
}
$END_LICENSE */
#include "sidebarwidget.h"
+#include "mainwindow.h"
#include "refinesearchbutton.h"
#include "refinesearchwidget.h"
#include "sidebarheader.h"
-#include "mainwindow.h"
#ifdef APP_EXTRA
#include "extra.h"
#endif
-SidebarWidget::SidebarWidget(QWidget *parent) :
- QWidget(parent), playlistWidth(0) {
- playlist = 0;
+SidebarWidget::SidebarWidget(QWidget *parent) : QWidget(parent), playlistWidth(0) {
+ playlist = nullptr;
QBoxLayout *layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
+ setBackgroundRole(QPalette::Base);
+ setAutoFillBackground(true);
+
sidebarHeader = new SidebarHeader();
layout->addWidget(sidebarHeader);
messageLabel->setAutoFillBackground(true);
messageLabel->setWordWrap(true);
messageLabel->setTextFormat(Qt::RichText);
- messageLabel->setTextInteractionFlags(
- Qt::LinksAccessibleByKeyboard |
- Qt::LinksAccessibleByMouse);
- connect(messageLabel, SIGNAL(linkActivated(QString)),
- SIGNAL(suggestionAccepted(QString)));
+ messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard |
+ Qt::LinksAccessibleByMouse);
+ connect(messageLabel, SIGNAL(linkActivated(QString)), SIGNAL(suggestionAccepted(QString)));
messageLabel->hide();
layout->addWidget(messageLabel);
void SidebarWidget::setup() {
refineSearchButton = new RefineSearchButton(this);
- refineSearchButton->setStatusTip(tr("Refine Search")
- + " (" + QKeySequence(Qt::CTRL + Qt::Key_R).toString(QKeySequence::NativeText) + ")");
+ refineSearchButton->setStatusTip(
+ tr("Refine Search") + " (" +
+ QKeySequence(Qt::CTRL + Qt::Key_R).toString(QKeySequence::NativeText) + ")");
refineSearchButton->hide();
connect(refineSearchButton, SIGNAL(clicked()), SLOT(showRefineSearchWidget()));
}
void SidebarWidget::toggleRefineSearch(bool show) {
- if (show) showRefineSearchWidget();
- else hideRefineSearchWidget();
+ if (show)
+ showRefineSearchWidget();
+ else
+ hideRefineSearchWidget();
}
void SidebarWidget::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
- refineSearchButton->move(
- playlist->viewport()->width() - refineSearchButton->minimumWidth(),
- height() - refineSearchButton->minimumHeight());
+ refineSearchButton->move(playlist->viewport()->width() - refineSearchButton->minimumWidth(),
+ height() - refineSearchButton->minimumHeight());
}
void SidebarWidget::enterEvent(QEvent *) {
- if (stackedWidget->currentWidget() != refineSearchWidget)
- showRefineSearchButton();
+ if (stackedWidget->currentWidget() != refineSearchWidget) showRefineSearchButton();
}
void SidebarWidget::leaveEvent(QEvent *) {
void SidebarWidget::showRefineSearchButton() {
if (!refineSearchWidget->isEnabled()) return;
- refineSearchButton->move(
- playlist->viewport()->width() - refineSearchButton->minimumWidth(),
- height() - refineSearchButton->minimumHeight());
+ refineSearchButton->move(playlist->viewport()->width() - refineSearchButton->minimumWidth(),
+ height() - refineSearchButton->minimumHeight());
refineSearchButton->show();
}
}
message = message.arg(suggestionLinks);
- QString html =
- "<html>"
- "<style>"
- "a { color: palette(text); text-decoration: none; font-weight: bold }"
- "</style>"
- "<body>%1</body>"
- "</html>";
+ QString html = "<html>"
+ "<style>"
+ "a { color: palette(text); text-decoration: none; font-weight: bold }"
+ "</style>"
+ "<body>%1</body>"
+ "</html>";
html = html.arg(message);
messageLabel->setText(html);
messageLabel->show();
class SidebarHeader;
class SidebarWidget : public QWidget {
-
Q_OBJECT
public:
- SidebarWidget(QWidget *parent = 0);
+ SidebarWidget(QWidget *parent = nullptr);
QListView *getPlaylist() { return playlist; }
void setPlaylist(QListView *playlist);
void showPlaylist();
- RefineSearchWidget* getRefineSearchWidget() { return refineSearchWidget; }
- SidebarHeader* getHeader() { return sidebarHeader; }
+ RefineSearchWidget *getRefineSearchWidget() { return refineSearchWidget; }
+ SidebarHeader *getHeader() { return sidebarHeader; }
void hideSuggestions();
public slots:
#include "snapshotpreview.h"
#include "mainwindow.h"
-SnapshotPreview::SnapshotPreview(QWidget *parent) : QWidget(parent),
-#ifdef APP_PHONON
- mediaObject(0),
- audioOutput(0)
+#ifdef MEDIA_QTAV
+#include "mediaqtav.h"
#endif
- {
+#ifdef MEDIA_MPV
+#include "mediampv.h"
+#endif
+
+SnapshotPreview::SnapshotPreview(QWidget *parent) : QWidget(parent), mediaObject(nullptr) {
+ setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint |
+ Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus);
setAttribute(Qt::WA_ShowWithoutActivating);
- setWindowFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus);
setAttribute(Qt::WA_StaticContents);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_NoSystemBackground);
}
void SnapshotPreview::start(QWidget *widget, const QPixmap &pixmap, bool soundOnly) {
-#ifdef APP_PHONON
if (!mediaObject) {
- mediaObject = new Phonon::MediaObject(this);
- audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, this);
- Phonon::createPath(mediaObject, audioOutput);
+#ifdef MEDIA_QTAV
+ mediaObject = new MediaQtAV(this);
+#elif defined MEDIA_MPV
+ mediaObject = new MediaMPV(this);
+#else
+ qFatal("No media backend defined");
+#endif
+ if (mediaObject) {
+ mediaObject->setAudioOnly(true);
+ mediaObject->init();
+ }
}
- mediaObject->setCurrentSource(QUrl("qrc:///sounds/snapshot.wav"));
- mediaObject->play();
+
+#ifdef APP_MAC
+ QString soundPath = QCoreApplication::applicationDirPath() + "/../Resources";
+#elif defined PKGDATADIR
+ QString soundPath = QLatin1String(PKGDATADIR) + "/sounds";
+#else
+ QString soundPath = QCoreApplication::applicationDirPath() + "/sounds";
#endif
+
+ if (mediaObject) mediaObject->play(soundPath + "/snapshot.wav");
if (soundOnly) return;
resize(pixmap.size());
timeLine->start();
#endif
timer->start();
- if (isVisible()) update();
- else show();
+ if (isVisible())
+ update();
+ else
+ show();
}
void SnapshotPreview::paintEvent(QPaintEvent *e) {
#include <QtWidgets>
-#ifdef APP_PHONON
-#include <phonon/audiooutput.h>
-#include <phonon/mediaobject.h>
-#include <phonon/videowidget.h>
-#endif
+#include "media.h"
class SnapshotPreview : public QWidget {
-
Q_OBJECT
public:
- SnapshotPreview(QWidget *parent = 0);
+ SnapshotPreview(QWidget *parent = nullptr);
void start(QWidget *widget, const QPixmap &pixmap, bool soundOnly);
signals:
QTimeLine *timeLine;
QPoint offset;
QTimer *timer;
-#ifdef APP_PHONON
- Phonon::MediaObject *mediaObject;
- Phonon::AudioOutput *audioOutput;
-#endif
+ Media *mediaObject;
};
#endif // SNAPSHOTPREVIEW_H
#include <QtWidgets>
class Spacer : public QWidget {
-
public:
- Spacer(QWidget *parent = 0, int minWidth = 60);
+ Spacer(QWidget *parent = nullptr, int minWidth = 60);
protected:
QSize sizeHint() const;
#include "ytstandardfeed.h"
StandardFeedsView::StandardFeedsView(QWidget *parent) : View(parent), layout(0) {
- QPalette p = palette();
- p.setBrush(QPalette::Window, Qt::black);
- setPalette(p);
+ setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
connect(MainWindow::instance()->getAction("worldwideRegion"), SIGNAL(triggered()),
}
const int itemCount = items.size();
- const int cols = itemCount / 3;
+ const int cols = 4; // itemCount / 3;
for (int i = itemCount - 1; i >= 0; i--) {
QLayoutItem *item = items.at(i);
int index = itemCount - 1 - i;
layout = new QGridLayout(this);
layout->setMargin(0);
- layout->setSpacing(1);
+ layout->setSpacing(0);
}
YTStandardFeed *
load();
}
QAction *regionAction = MainWindow::instance()->getRegionAction();
- MainWindow::instance()->showActionInStatusBar(regionAction, true);
+ MainWindow::instance()->showActionsInStatusBar({regionAction}, true);
}
void StandardFeedsView::disappear() {
QAction *regionAction = MainWindow::instance()->getRegionAction();
- MainWindow::instance()->showActionInStatusBar(regionAction, false);
+ MainWindow::instance()->showActionsInStatusBar({regionAction}, false);
}
void StandardFeedsView::selectWorldwideRegion() {
#include "toolbarmenu.h"
#include "mainwindow.h"
#include "sharetoolbar.h"
+#include "videodefinition.h"
+#include "yt3.h"
ToolbarMenu::ToolbarMenu(QWidget *parent) : QMenu(parent) {
MainWindow *w = MainWindow::instance();
widgetAction->setDefaultWidget(shareToolbar);
addAction(widgetAction);
addSeparator();
+
addAction(w->getAction("compactView"));
addAction(w->getAction("ontop"));
addSeparator();
+
+ QToolBar *definitionToolbar = new QToolBar();
+ definitionToolbar->setStyleSheet("QToolButton { padding: 0}");
+ definitionToolbar->setToolButtonStyle(Qt::ToolButtonTextOnly);
+ definitionToolbar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ QActionGroup *definitionGroup = new QActionGroup(this);
+ const VideoDefinition &preferredDefinition = YT3::instance().maxVideoDefinition();
+ int counter = 0;
+ for (auto defName : VideoDefinition::getDefinitionNames()) {
+ QAction *a = new QAction(defName);
+ a->setCheckable(true);
+ a->setChecked(preferredDefinition.getName() == defName);
+ connect(a, &QAction::triggered, this, [this, defName] {
+ MainWindow::instance()->setDefinitionMode(defName);
+ close();
+ });
+ definitionGroup->addAction(a);
+ definitionToolbar->addAction(a);
+ if (counter == 0) {
+ QWidget *w = definitionToolbar->widgetForAction(a);
+ w->setProperty("first", true);
+ counter++;
+ }
+ }
+ QWidgetAction *definitionAction = new QWidgetAction(this);
+ definitionAction->setDefaultWidget(definitionToolbar);
+ addAction(definitionAction);
+ addSeparator();
+
addAction(w->getAction("clearRecentKeywords"));
#ifndef APP_MAC
addSeparator();
addAction(w->getAction("toggleMenu"));
-#endif
addSeparator();
addMenu(w->getMenu("help"));
+#endif
}
void ToolbarMenu::showEvent(QShowEvent *e) {
QStyleOptionMenuItem option;
initStyleOption(&option, a);
int leftMargin = option.maxIconWidth;
-#ifndef APP_MAC
- // On Win & Linux the value is wrong
+#ifdef APP_WIN
leftMargin *= 1.5;
#endif
+ setStyleSheet("QToolBar > QToolButton[first] {margin-left:" + QString::number(leftMargin) +
+ "px}");
emit leftMarginChanged(leftMargin);
}
Q_OBJECT
public:
- ToolbarMenu(QWidget *parent = 0);
+ ToolbarMenu(QWidget *parent = nullptr);
signals:
void leftMarginChanged(int value);
#include "http.h"
#include "httputils.h"
#include "jsfunctions.h"
+#include "playlistitemdelegate.h"
#include "videodefinition.h"
#include "ytvideo.h"
Video::Video()
: duration(0), viewCount(-1), license(LicenseYouTube), definitionCode(0),
- loadingThumbnail(false), ytVideo(0) {}
+ loadingThumbnail(false), ytVideo(nullptr) {}
Video::~Video() {
qDebug() << "Deleting" << id;
clone->published = published;
clone->formattedPublished = formattedPublished;
clone->viewCount = viewCount;
+ clone->formattedViewCount = formattedViewCount;
clone->id = id;
clone->definitionCode = definitionCode;
return clone;
formattedDuration = DataUtils::formatDuration(duration);
}
+void Video::setViewCount(int value) {
+ viewCount = value;
+ formattedViewCount = DataUtils::formatCount(viewCount);
+}
+
void Video::setPublished(const QDateTime &value) {
published = value;
formattedPublished = DataUtils::formatDateTime(published);
qreal ratio = qApp->devicePixelRatio();
thumbnail.loadFromData(bytes);
thumbnail.setDevicePixelRatio(ratio);
- const int thumbWidth = 160 * 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 QUrl &streamUrl) {
+void Video::streamUrlLoaded(const QString &streamUrl, const QString &audioUrl) {
+ qDebug() << "Streams loaded";
definitionCode = ytVideo->getDefinitionCode();
this->streamUrl = streamUrl;
- emit gotStreamUrl(this->streamUrl);
- delete ytVideo;
- ytVideo = 0;
+ emit gotStreamUrl(streamUrl, audioUrl);
+ ytVideo->deleteLater();
+ ytVideo = nullptr;
}
void Video::loadStreamUrl() {
}
ytVideo = new YTVideo(id, this);
connect(ytVideo, &YTVideo::gotStreamUrl, this, &Video::streamUrlLoaded);
- connect(ytVideo, &YTVideo::errorStreamUrl, this, &Video::errorStreamUrl);
- connect(ytVideo, &YTVideo::errorStreamUrl, ytVideo, &QObject::deleteLater);
+ connect(ytVideo, &YTVideo::errorStreamUrl, this, [this](const QString &msg) {
+ emit errorStreamUrl(msg);
+ ytVideo->deleteLater();
+ ytVideo = nullptr;
+ });
ytVideo->loadStreamUrl();
}
+
+void Video::abortLoadStreamUrl() {
+ if (ytVideo) {
+ ytVideo->disconnect(this);
+ ytVideo->deleteLater();
+ ytVideo = nullptr;
+ }
+}
const QString &getFormattedDuration() const { return formattedDuration; }
int getViewCount() const { return viewCount; }
- void setViewCount(int value) { viewCount = value; }
+ void setViewCount(int value);
+ const QString &getFormattedViewCount() const { return formattedViewCount; }
const QDateTime &getPublished() const { return published; }
void setPublished(const QDateTime &value);
int getDefinitionCode() const { return definitionCode; }
void loadStreamUrl();
- const QUrl &getStreamUrl() { return streamUrl; }
+ const QString &getStreamUrl() { return streamUrl; }
+ bool isLoadingStreamUrl() const { return ytVideo != nullptr; }
+ void abortLoadStreamUrl();
const QString &getId() const { return id; }
void setId(const QString &value) { id = value; }
void gotThumbnail();
void gotMediumThumbnail(const QByteArray &bytes);
void gotLargeThumbnail(const QByteArray &bytes);
- void gotStreamUrl(const QUrl &streamUrl);
+ void gotStreamUrl(const QString &videoUrl, const QString &audioUrl);
void errorStreamUrl(const QString &message);
private slots:
void setThumbnail(const QByteArray &bytes);
- void streamUrlLoaded(const QUrl &streamUrl);
+ void streamUrlLoaded(const QString &streamUrl, const QString &audioUrl);
private:
QString title;
QString channelTitle;
QString channelId;
QString webpage;
- QUrl streamUrl;
+ QString streamUrl;
QPixmap thumbnail;
QString thumbnailUrl;
QString mediumThumbnailUrl;
QDateTime published;
QString formattedPublished;
int viewCount;
+ QString formattedViewCount;
License license;
QString id;
int definitionCode;
--- /dev/null
+/* $BEGIN_LICENSE
+
+This file is part of Minitube.
+Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
+
+Minitube 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.
+
+Minitube 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 Minitube. If not, see <http://www.gnu.org/licenses/>.
+
+$END_LICENSE */
+
+#include "videoarea.h"
+#include "loadingwidget.h"
+#include "mainwindow.h"
+#include "playlistmodel.h"
+#include "video.h"
+#include "videomimedata.h"
+#ifdef Q_OS_MAC
+#include "macutils.h"
+#endif
+#include "fontutils.h"
+#include "snapshotpreview.h"
+
+namespace {
+
+class PickMessage : public QWidget {
+public:
+ PickMessage(QWidget *parent = nullptr) : QWidget(parent) {
+ setAutoFillBackground(true);
+
+ QBoxLayout *l = new QHBoxLayout(this);
+ l->setMargin(32);
+ l->setSpacing(32);
+ l->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
+
+ QLabel *arrowLabel = new QLabel("←");
+ arrowLabel->setFont(FontUtils::light(64));
+ l->addWidget(arrowLabel);
+
+ QLabel *msgLabel = new QLabel(tr("Pick a video"));
+ msgLabel->setFont(FontUtils::light(32));
+ l->addWidget(msgLabel);
+ }
+};
+} // namespace
+
+VideoArea::VideoArea(QWidget *parent)
+ : QWidget(parent), videoWidget(nullptr), messageWidget(nullptr) {
+ setAttribute(Qt::WA_OpaquePaintEvent);
+
+ QBoxLayout *layout = new QVBoxLayout(this);
+ layout->setMargin(0);
+ layout->setSpacing(0);
+
+ stackedLayout = new QStackedLayout();
+ layout->addLayout(stackedLayout);
+
+#ifdef APP_SNAPSHOT
+ snapshotPreview = new SnapshotPreview();
+ connect(stackedLayout, SIGNAL(currentChanged(int)), snapshotPreview, SLOT(hide()));
+#endif
+
+ setAcceptDrops(true);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
+ SLOT(showContextMenu(const QPoint &)));
+}
+
+void VideoArea::setVideoWidget(QWidget *videoWidget) {
+ this->videoWidget = videoWidget;
+ stackedLayout->addWidget(videoWidget);
+}
+
+void VideoArea::setLoadingWidget(LoadingWidget *loadingWidget) {
+ this->loadingWidget = loadingWidget;
+ stackedLayout->addWidget(loadingWidget);
+ stackedLayout->setCurrentWidget(loadingWidget);
+}
+
+void VideoArea::showVideo() {
+ if (videoWidget) stackedLayout->setCurrentWidget(videoWidget);
+ loadingWidget->clear();
+}
+
+void VideoArea::showPickMessage() {
+ if (!messageWidget) {
+ messageWidget = new PickMessage();
+ stackedLayout->addWidget(messageWidget);
+ }
+ stackedLayout->setCurrentWidget(messageWidget);
+}
+
+void VideoArea::showLoading(Video *video) {
+ loadingWidget->setVideo(video);
+ stackedLayout->setCurrentWidget(loadingWidget);
+}
+
+#ifdef APP_SNAPSHOT
+void VideoArea::showSnapshotPreview(const QPixmap &pixmap) {
+ bool soundOnly = MainWindow::instance()->isReallyFullScreen();
+ snapshotPreview->start(videoWidget, pixmap, soundOnly);
+}
+#endif
+
+void VideoArea::clear() {
+ loadingWidget->clear();
+ stackedLayout->setCurrentWidget(loadingWidget);
+}
+
+void VideoArea::mouseDoubleClickEvent(QMouseEvent *event) {
+ if (event->button() == Qt::LeftButton) emit doubleClicked();
+}
+
+void VideoArea::dragEnterEvent(QDragEnterEvent *event) {
+ // qDebug() << event->mimeData()->formats();
+ if (event->mimeData()->hasFormat("application/x-minitube-video")) {
+ event->acceptProposedAction();
+ }
+}
+
+void VideoArea::dropEvent(QDropEvent *event) {
+ const VideoMimeData *videoMimeData = qobject_cast<const VideoMimeData *>(event->mimeData());
+ if (!videoMimeData) return;
+
+ QVector<Video *> droppedVideos = videoMimeData->getVideos();
+ if (droppedVideos.isEmpty()) return;
+ Video *video = droppedVideos.at(0);
+ int row = listModel->rowForVideo(video);
+ if (row != -1) listModel->setActiveRow(row);
+ event->acceptProposedAction();
+}
+
+void VideoArea::showContextMenu(const QPoint &point) {
+ QMenu *menu = MainWindow::instance()->getMenu("video");
+ menu->exec(mapToGlobal(point));
+}
--- /dev/null
+/* $BEGIN_LICENSE
+
+This file is part of Minitube.
+Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
+
+Minitube 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.
+
+Minitube 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 Minitube. If not, see <http://www.gnu.org/licenses/>.
+
+$END_LICENSE */
+
+#ifndef VIDEOAREAWIDGET_H
+#define VIDEOAREAWIDGET_H
+
+#include <QtWidgets>
+
+class Video;
+class LoadingWidget;
+class PlaylistModel;
+class SnapshotPreview;
+
+class VideoArea : public QWidget {
+ Q_OBJECT
+
+public:
+ VideoArea(QWidget *parent = nullptr);
+ void setVideoWidget(QWidget *videoWidget);
+ void setLoadingWidget(LoadingWidget *loadingWidget);
+ void showLoading(Video *video);
+ void showVideo();
+ void showPickMessage();
+ void clear();
+ void setListModel(PlaylistModel *listModel) { this->listModel = listModel; }
+#ifdef APP_SNAPSHOT
+ void showSnapshotPreview(const QPixmap &pixmap);
+#endif
+ bool isVideoShown() { return stackedLayout->currentWidget() == videoWidget; }
+
+signals:
+ void doubleClicked();
+ void rightClicked();
+
+protected:
+ void mouseDoubleClickEvent(QMouseEvent *event);
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dropEvent(QDropEvent *event);
+
+private slots:
+ void showContextMenu(const QPoint &point);
+
+private:
+ QStackedLayout *stackedLayout;
+ QWidget *videoWidget;
+ LoadingWidget *loadingWidget;
+ QWidget *messageWidget;
+#ifdef APP_SNAPSHOT
+ SnapshotPreview *snapshotPreview;
+#endif
+ PlaylistModel *listModel;
+};
+
+#endif // VIDEOAREAWIDGET_H
+++ /dev/null
-/* $BEGIN_LICENSE
-
-This file is part of Minitube.
-Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
-
-Minitube 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.
-
-Minitube 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 Minitube. If not, see <http://www.gnu.org/licenses/>.
-
-$END_LICENSE */
-
-#include "videoareawidget.h"
-#include "loadingwidget.h"
-#include "mainwindow.h"
-#include "playlistmodel.h"
-#include "video.h"
-#include "videomimedata.h"
-#ifdef Q_OS_MAC
-#include "macutils.h"
-#endif
-#include "snapshotpreview.h"
-#include "fontutils.h"
-
-namespace {
-
-class MessageWidget : public QWidget {
-public:
- MessageWidget(QWidget *parent = nullptr) : QWidget(parent) {
- QPalette p = palette();
- p.setColor(QPalette::Window, Qt::black);
- p.setColor(QPalette::WindowText, Qt::darkGray);
- p.setColor(QPalette::Base, Qt::black);
- p.setColor(QPalette::Text, Qt::darkGray);
- setPalette(p);
- setAutoFillBackground(true);
-
- QBoxLayout *l = new QHBoxLayout(this);
- l->setMargin(32);
- l->setSpacing(32);
- l->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
-
- QLabel *arrowLabel = new QLabel("←");
- arrowLabel->setFont(FontUtils::light(64));
- arrowLabel->setPalette(p);
- l->addWidget(arrowLabel);
-
- QLabel *msgLabel = new QLabel(tr("Pick a video"));
- msgLabel->setFont(FontUtils::light(32));
- msgLabel->setPalette(p);
- l->addWidget(msgLabel);
- }
-};
-}
-
-VideoAreaWidget::VideoAreaWidget(QWidget *parent)
- : QWidget(parent), videoWidget(0), messageWidget(0) {
- setAttribute(Qt::WA_OpaquePaintEvent);
-
- QBoxLayout *layout = new QVBoxLayout(this);
- layout->setMargin(0);
- layout->setSpacing(0);
-
- // hidden message widget
- messageLabel = new QLabel(this);
- messageLabel->setOpenExternalLinks(true);
- messageLabel->setMargin(7);
- messageLabel->setBackgroundRole(QPalette::ToolTipBase);
- messageLabel->setForegroundRole(QPalette::ToolTipText);
- messageLabel->setAutoFillBackground(true);
- messageLabel->setWordWrap(true);
- messageLabel->hide();
- layout->addWidget(messageLabel);
-
- stackedLayout = new QStackedLayout();
- layout->addLayout(stackedLayout);
-
-#ifdef APP_SNAPSHOT
- snapshotPreview = new SnapshotPreview();
- connect(stackedLayout, SIGNAL(currentChanged(int)), snapshotPreview, SLOT(hide()));
-#endif
-
- setAcceptDrops(true);
- setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- setContextMenuPolicy(Qt::CustomContextMenu);
- connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
- SLOT(showContextMenu(const QPoint &)));
-}
-
-void VideoAreaWidget::setVideoWidget(QWidget *videoWidget) {
- this->videoWidget = videoWidget;
- stackedLayout->addWidget(videoWidget);
-}
-
-void VideoAreaWidget::setLoadingWidget(LoadingWidget *loadingWidget) {
- this->loadingWidget = loadingWidget;
- stackedLayout->addWidget(loadingWidget);
- stackedLayout->setCurrentWidget(loadingWidget);
-}
-
-void VideoAreaWidget::showVideo() {
- if (videoWidget) stackedLayout->setCurrentWidget(videoWidget);
- loadingWidget->clear();
-}
-
-void VideoAreaWidget::showError(const QString &message) {
- messageLabel->setText(message);
- messageLabel->show();
- stackedLayout->setCurrentWidget(loadingWidget);
-}
-
-void VideoAreaWidget::showPickMessage() {
- if (!messageWidget) {
- messageWidget = new MessageWidget();
- stackedLayout->addWidget(messageWidget);
- }
- stackedLayout->setCurrentWidget(messageWidget);
-}
-
-void VideoAreaWidget::showLoading(Video *video) {
- messageLabel->hide();
- messageLabel->clear();
- stackedLayout->setCurrentWidget(loadingWidget);
- loadingWidget->setVideo(video);
-}
-
-#ifdef APP_SNAPSHOT
-void VideoAreaWidget::showSnapshotPreview(const QPixmap &pixmap) {
- bool soundOnly = false;
-#ifdef APP_MAC
- soundOnly = MainWindow::instance()->isReallyFullScreen();
-#endif
- snapshotPreview->start(videoWidget, pixmap, soundOnly);
-}
-
-void VideoAreaWidget::hideSnapshotPreview() {}
-#endif
-
-void VideoAreaWidget::clear() {
- loadingWidget->clear();
- messageLabel->hide();
- messageLabel->clear();
- stackedLayout->setCurrentWidget(loadingWidget);
-}
-
-void VideoAreaWidget::mouseDoubleClickEvent(QMouseEvent *event) {
- if (event->button() == Qt::LeftButton) emit doubleClicked();
-}
-
-void VideoAreaWidget::dragEnterEvent(QDragEnterEvent *event) {
- // qDebug() << event->mimeData()->formats();
- if (event->mimeData()->hasFormat("application/x-minitube-video")) {
- event->acceptProposedAction();
- }
-}
-
-void VideoAreaWidget::dropEvent(QDropEvent *event) {
- const VideoMimeData *videoMimeData = qobject_cast<const VideoMimeData *>(event->mimeData());
- if (!videoMimeData) return;
-
- QVector<Video *> droppedVideos = videoMimeData->getVideos();
- if (droppedVideos.isEmpty()) return;
- Video *video = droppedVideos.at(0);
- int row = listModel->rowForVideo(video);
- if (row != -1) listModel->setActiveRow(row);
- event->acceptProposedAction();
-}
-
-void VideoAreaWidget::showContextMenu(const QPoint &point) {
- QMenu *menu = MainWindow::instance()->getMenu("video");
- menu->exec(mapToGlobal(point));
-}
+++ /dev/null
-/* $BEGIN_LICENSE
-
-This file is part of Minitube.
-Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
-
-Minitube 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.
-
-Minitube 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 Minitube. If not, see <http://www.gnu.org/licenses/>.
-
-$END_LICENSE */
-
-#ifndef VIDEOAREAWIDGET_H
-#define VIDEOAREAWIDGET_H
-
-#include <QtWidgets>
-
-class Video;
-class LoadingWidget;
-class PlaylistModel;
-class SnapshotPreview;
-
-class VideoAreaWidget : public QWidget {
-
- Q_OBJECT
-
-public:
- VideoAreaWidget(QWidget *parent = 0);
- void setVideoWidget(QWidget *videoWidget);
- void setLoadingWidget(LoadingWidget *loadingWidget);
- void showLoading(Video* video);
- void showVideo();
- void showError(const QString &message);
- void showPickMessage();
- void clear();
- void setListModel(PlaylistModel *listModel) {
- this->listModel = listModel;
- }
-#ifdef APP_SNAPSHOT
- void showSnapshotPreview(const QPixmap &pixmap);
-#endif
- bool isVideoShown() { return stackedLayout->currentWidget() == videoWidget; }
-
-signals:
- void doubleClicked();
- void rightClicked();
-
-protected:
- void mouseDoubleClickEvent(QMouseEvent *event);
- void dragEnterEvent(QDragEnterEvent *event);
- void dropEvent(QDropEvent *event);
-
-private slots:
- void showContextMenu(const QPoint &point);
-#ifdef APP_SNAPSHOT
- void hideSnapshotPreview();
-#endif
-
-private:
- QStackedLayout *stackedLayout;
- QWidget *videoWidget;
- LoadingWidget *loadingWidget;
-
-#ifdef APP_SNAPSHOT
- SnapshotPreview *snapshotPreview;
-#endif
-
- PlaylistModel *listModel;
- QLabel *messageLabel;
-
- QWidget *messageWidget;
-
- QPoint dragPosition;
-};
-
-#endif // VIDEOAREAWIDGET_H
#include "videodefinition.h"
namespace {
-static const int kEmptyDefinitionCode = -1;
-
-static const VideoDefinition kEmptyDefinition(QString(), kEmptyDefinitionCode);
+const int kEmptyDefinitionCode = -1;
template <typename T, T (VideoDefinition::*Getter)() const>
const VideoDefinition &getDefinitionForImpl(T matchValue) {
const auto &defs = VideoDefinition::getDefinitions();
+
+ for (auto i = defs.rbegin(); i != defs.rend(); ++i) {
+ if ((*i.*Getter)() == matchValue) return *i;
+ }
+ /*
for (const VideoDefinition &def : defs) {
if ((def.*Getter)() == matchValue) return def;
}
+ */
+ static const VideoDefinition kEmptyDefinition(QString(), kEmptyDefinitionCode);
return kEmptyDefinition;
}
-}
+} // namespace
-// static
const QVector<VideoDefinition> &VideoDefinition::getDefinitions() {
+ // List preferred equivalent format last:
+ // algo selects the last format with same name first
static const QVector<VideoDefinition> definitions = {
- VideoDefinition(QLatin1String("360p"), 18), VideoDefinition(QLatin1String("720p"), 22),
- VideoDefinition(QLatin1String("1080p"), 37)};
+ VideoDefinition("240p", 242), VideoDefinition("240p", 133),
+ VideoDefinition("360p", 243), VideoDefinition("360p", 396),
+ VideoDefinition("360p", 18, true), VideoDefinition("480p", 244),
+ VideoDefinition("480p", 135), VideoDefinition("720p", 247),
+ VideoDefinition("720p", 136), VideoDefinition("720p", 22, true),
+ VideoDefinition("1080p", 248), VideoDefinition("1080p", 137),
+ VideoDefinition("1440p", 271), VideoDefinition("2160p", 313),
+ };
return definitions;
}
-// static
+const QVector<QString> &VideoDefinition::getDefinitionNames() {
+ static const QVector<QString> names = {"480p", "720p", "1080p", "1440p", "2160p"};
+ return names;
+}
+
const VideoDefinition &VideoDefinition::forName(const QString &name) {
return getDefinitionForImpl<const QString &, &VideoDefinition::getName>(name);
}
-// static
const VideoDefinition &VideoDefinition::forCode(int code) {
return getDefinitionForImpl<int, &VideoDefinition::getCode>(code);
}
-VideoDefinition::VideoDefinition(const QString &name, int code) : m_name(name), m_code(code) {}
+VideoDefinition::VideoDefinition(const QString &name, int code, bool hasAudioStream)
+ : name(name), code(code), hasAudioStream(hasAudioStream) {}
bool VideoDefinition::isEmpty() const {
- return m_code == kEmptyDefinitionCode && m_name.isEmpty();
+ return code == kEmptyDefinitionCode && name.isEmpty();
}
class VideoDefinition {
public:
static const QVector<VideoDefinition> &getDefinitions();
+ static const QVector<QString> &getDefinitionNames();
static const VideoDefinition &forName(const QString &name);
static const VideoDefinition &forCode(int code);
- VideoDefinition(const QString &name, int code);
+ VideoDefinition(const QString &name, int code, bool hasAudioStream = false);
- const QString &getName() const { return m_name; }
- int getCode() const { return m_code; }
+ const QString &getName() const { return name; }
+ int getCode() const { return code; }
+ bool hasAudio() const { return hasAudioStream; }
bool isEmpty() const;
VideoDefinition &operator=(const VideoDefinition &);
private:
- const QString m_name;
- const int m_code;
+ const QString name;
+ const int code;
+ const bool hasAudioStream;
};
inline bool operator==(const VideoDefinition &lhs, const VideoDefinition &rhs) {
#include <algorithm>
#include <ctime>
-#include "jsfunctions.h"
+#include "constants.h"
#include "http.h"
#include "httputils.h"
-#include "constants.h"
+#include "jsfunctions.h"
#include "mainwindow.h"
+#include "videodefinition.h"
#ifdef APP_EXTRA
#include "extra.h"
#endif
#ifdef APP_EXTRA
- if (keys.isEmpty())
- keys << Extra::apiKeys();
+ if (keys.isEmpty()) keys << Extra::apiKeys();
#endif
if (keys.isEmpty()) {
qWarning() << "No available API keys";
#ifdef APP_LINUX
- QMetaObject::invokeMethod(MainWindow::instance(), "missingKeyWarning", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(MainWindow::instance(), "missingKeyWarning",
+ Qt::QueuedConnection);
#endif
} else {
key = keys.takeFirst();
return url;
}
+const VideoDefinition &YT3::maxVideoDefinition() {
+ const QString name = QSettings().value("definition", "720p").toString();
+ const VideoDefinition &definition = VideoDefinition::forName(name);
+ return definition;
+}
+
+void YT3::setMaxVideoDefinition(const QString &name) {
+ QSettings settings;
+ settings.setValue("definition", name);
+ emit maxVideoDefinitionChanged(name);
+}
+
void YT3::testResponse(const HttpReply &reply) {
int status = reply.statusCode();
if (status != 200) {
#include <QtCore>
class HttpReply;
+class VideoDefinition;
class YT3 : public QObject {
-
Q_OBJECT
public:
void addApiKey(QUrl &url);
QUrl method(const QString &name);
+ const VideoDefinition &maxVideoDefinition();
+ void setMaxVideoDefinition(const QString &name);
+
+signals:
+ void maxVideoDefinitionChanged(const QString &name);
+
private slots:
void testResponse(const HttpReply &reply);
#include "datautils.h"
#include "video.h"
+#include <QRegularExpression>
+
+namespace {
+
+QString decodeEntities(const QString &s) {
+ return QTextDocumentFragment::fromHtml(s).toPlainText();
+}
+
+} // namespace
+
YT3ListParser::YT3ListParser(const QByteArray &bytes) {
QJsonDocument doc = QJsonDocument::fromJson(bytes);
QJsonObject obj = doc.object();
video->setChannelId(snippet[QLatin1String("channelId")].toString());
- video->setTitle(snippet[QLatin1String("title")].toString());
+ QString title = snippet[QLatin1String("title")].toString();
+ static const QChar ampersand('&');
+ if (title.contains(ampersand)) title = decodeEntities(title);
+ video->setTitle(title);
video->setDescription(snippet[QLatin1String("description")].toString());
QJsonObject thumbnails = snippet[QLatin1String("thumbnails")].toObject();
$END_LICENSE */
#include "ytchannel.h"
+#include "database.h"
#include "http.h"
#include "httputils.h"
-#include "database.h"
#include <QtSql>
#include "yt3.h"
#include "iconutils.h"
-YTChannel::YTChannel(const QString &channelId, QObject *parent) : QObject(parent),
- id(0),
- channelId(channelId),
- loadingThumbnail(false),
- notifyCount(0),
- checked(0),
- watched(0),
- loaded(0),
- loading(false) { }
+YTChannel::YTChannel(const QString &channelId, QObject *parent)
+ : QObject(parent), id(0), channelId(channelId), loadingThumbnail(false), notifyCount(0),
+ checked(0), watched(0), loaded(0), loading(false) {}
-QHash<QString, YTChannel*> YTChannel::cache;
+QHash<QString, YTChannel *> YTChannel::cache;
-YTChannel* YTChannel::forId(const QString &channelId) {
+YTChannel *YTChannel::forId(const QString &channelId) {
if (channelId.isEmpty()) return 0;
auto i = cache.constFind(channelId);
bool success = query.exec();
if (!success) qWarning() << query.lastQuery() << query.lastError().text();
- YTChannel* channel = 0;
+ YTChannel *channel = 0;
if (query.next()) {
// Change userId to ChannelId
channel->checked = query.value(6).toUInt();
channel->loaded = query.value(7).toUInt();
channel->thumbnail = QPixmap(channel->getThumbnailLocation());
- channel->thumbnail.setDevicePixelRatio(IconUtils::maxSupportedPixelRatio());
+ channel->thumbnail.setDevicePixelRatio(2.0);
channel->maybeLoadfromAPI();
cache.insert(channelId, channel);
}
connect(reply, SIGNAL(error(QString)), SLOT(requestError(QString)));
}
-const QString & YTChannel::getThumbnailDir() {
- static const QString thumbDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/channels/";
+const QString &YTChannel::getThumbnailDir() {
+ static const QString thumbDir =
+ QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/channels/";
return thumbDir;
}
QString YTChannel::latestVideoId() {
QSqlDatabase db = Database::instance().getConnection();
QSqlQuery query(db);
- query.prepare("select video_id from subscriptions_videos where user_id=? order by published desc limit 1");
+ query.prepare("select video_id from subscriptions_videos where user_id=? order by published "
+ "desc limit 1");
query.bindValue(0, channelId);
bool success = query.exec();
if (!success) qWarning() << query.lastQuery() << query.lastError().text();
void YTChannel::storeThumbnail(const QByteArray &bytes) {
thumbnail.loadFromData(bytes);
- qreal maxRatio = IconUtils::maxSupportedPixelRatio();
+ qreal maxRatio = 2.0;
thumbnail.setDevicePixelRatio(maxRatio);
const int maxWidth = 88 * maxRatio;
query.bindValue(0, channelId);
bool success = query.exec();
if (!success) qWarning() << query.lastQuery() << query.lastError().text();
- if (query.next())
- return query.value(0).toInt() > 0;
+ if (query.next()) return query.value(0).toInt() > 0;
return false;
}
QSqlDatabase db = Database::instance().getConnection();
QSqlQuery query(db);
- query.prepare("update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
+ query.prepare(
+ "update subscriptions set watched=?, notify_count=0, views=views+1 where user_id=?");
query.bindValue(0, now);
query.bindValue(1, channelId);
bool success = query.exec();
}
void YTChannel::storeNotifyCount(int count) {
- if (notifyCount != count)
- emit notifyCountChanged();
+ if (notifyCount != count) emit notifyCountChanged();
notifyCount = count;
QSqlDatabase db = Database::instance().getConnection();
$END_LICENSE */
#include "ytregions.h"
+#include "iconutils.h"
YTRegions::YTRegions() : QObject() {}
}
const YTRegion &YTRegions::localRegion() {
- static const YTRegion region = [] {
+ static const YTRegion region = []() -> YTRegion {
QString country = QLocale::system().name().right(2);
for (const YTRegion &r : list()) {
if (r.id == country) return r;
}
QIcon YTRegions::iconForRegionId(const QString ®ionId) {
- if (regionId.isEmpty()) return QIcon(":images/worldwide.png");
+ if (regionId.isEmpty()) return IconUtils::icon("worldwide");
return QIcon(":flags/" + regionId.toLower() + ".png");
}
#include "yt3.h"
#include "yt3listparser.h"
-YTSingleVideoSource::YTSingleVideoSource(QObject *parent) : PaginatedVideoSource(parent),
- video(0),
- startIndex(0),
- max(0) { }
+YTSingleVideoSource::YTSingleVideoSource(QObject *parent)
+ : PaginatedVideoSource(parent), video(nullptr), startIndex(0), max(0) {}
void YTSingleVideoSource::loadVideos(int max, int startIndex) {
aborted = false;
QUrl url;
if (startIndex == 1) {
-
if (video) {
- QVector<Video*> videos;
+ QVector<Video *> videos;
videos << video->clone();
if (name.isEmpty()) {
name = videos.at(0)->getTitle();
if (aborted) return;
YT3ListParser parser(data);
- const QVector<Video*> &videos = parser.getVideos();
+ const QVector<Video *> &videos = parser.getVideos();
bool tryingWithNewToken = setPageToken(parser.getNextPageToken());
if (tryingWithNewToken) return;
if (asyncDetails) {
emit gotVideos(videos);
- if (startIndex == 2) emit finished(videos.size() + 1);
- else emit finished(videos.size());
+ if (startIndex == 2)
+ emit finished(videos.size() + 1);
+ else
+ emit finished(videos.size());
}
loadVideoDetails(videos);
}
#include "jsfunctions.h"
#include "temporary.h"
#include "videodefinition.h"
+#include "yt3.h"
#include <QJSEngine>
#include <QJSValue>
QUrl url;
if (elIndex == elTypes.size()) {
- // qDebug() << "Trying special embedded el param";
+ qDebug() << "Trying special embedded el param";
url = QUrl("https://www.youtube.com/get_video_info");
QUrlQuery q;
q.addQueryItem("video_id", videoId);
}
QString videoToken = videoTokeRE.cap(1);
- qDebug() << "got token" << videoToken;
while (videoToken.contains('%'))
videoToken = QByteArray::fromPercentEncoding(videoToken.toLatin1());
qDebug() << "videoToken" << videoToken;
}
void YTVideo::parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage) {
- const QString definitionName = QSettings().value("definition", "360p").toString();
- const VideoDefinition &definition = VideoDefinition::forName(definitionName);
+ int videoFormat = 0;
+ const VideoDefinition &definition = YT3::instance().maxVideoDefinition();
- qDebug() << "fmtUrlMap" << fmtUrlMap;
+ // qDebug() << "fmtUrlMap" << fmtUrlMap;
const QVector<QStringRef> formatUrls = fmtUrlMap.splitRef(',', QString::SkipEmptyParts);
- QMap<int, QString> urlMap;
+
for (const QStringRef &formatUrl : formatUrls) {
// qDebug() << "formatUrl" << formatUrl;
const QVector<QStringRef> urlParams = formatUrl.split('&', QString::SkipEmptyParts);
int format = -1;
QString url;
QString sig;
+ QStringRef sp;
for (const QStringRef &urlParam : urlParams) {
- // qWarning() << urlParam;
+ qDebug() << "urlParam" << urlParam;
+ if (sp.isNull() && urlParam.startsWith(QLatin1String("sp"))) {
+ int separator = urlParam.indexOf('=');
+ sp = urlParam.mid(separator + 1);
+ }
if (urlParam.startsWith(QLatin1String("itag="))) {
int separator = urlParam.indexOf('=');
format = urlParam.mid(separator + 1).toInt();
if (fromWebPage || ageGate) {
int separator = urlParam.indexOf('=');
sig = QByteArray::fromPercentEncoding(urlParam.mid(separator + 1).toUtf8());
- if (ageGate)
- sig = JsFunctions::instance()->decryptAgeSignature(sig);
- else {
- sig = decryptSignature(sig);
- if (sig.isEmpty()) sig = JsFunctions::instance()->decryptSignature(sig);
- }
+ sig = decryptSignature(sig);
+ if (sig.isEmpty()) sig = JsFunctions::instance()->decryptSignature(sig);
+ if (sig.isEmpty()) qWarning() << "Empty signature";
} else {
- QUrl url("https://www.youtube.com/watch");
- QUrlQuery q;
- q.addQueryItem("v", videoId);
- q.addQueryItem("gl", "US");
- q.addQueryItem("hl", "en");
- q.addQueryItem("has_verified", "1");
- url.setQuery(q);
- 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)));
- // see you in scrapWebPage(QByteArray)
+ loadWebPage();
return;
}
}
}
if (format == -1 || url.isNull()) continue;
- url += QLatin1String("&signature=") + sig;
+ if (!sig.isEmpty()) {
+ if (sp.isEmpty())
+ url += QLatin1String("&signature=") + sig;
+ else
+ url += '&' + sp + '=' + sig;
+ }
if (!url.contains(QLatin1String("ratebypass"))) url += QLatin1String("&ratebypass=yes");
- qDebug() << url;
-
+ qDebug() << format;
if (format == definition.getCode()) {
- qDebug() << "Found format" << definitionCode;
- saveDefinitionForUrl(url, definition);
- return;
+ qDebug() << "Found format" << format;
+ if (definition.hasAudio()) {
+ // we found the exact match with an audio/video stream
+ saveDefinitionForUrl(url, definition);
+ return;
+ }
+ videoFormat = format;
}
-
urlMap.insert(format, url);
}
+ if (!fromWebPage && !ageGate) {
+ loadWebPage();
+ return;
+ }
+
+ if (videoFormat != 0) {
+ // exact match with video stream was found
+ const VideoDefinition &definition = VideoDefinition::forCode(videoFormat);
+ saveDefinitionForUrl(urlMap.value(videoFormat), definition);
+ return;
+ }
+
const QVector<VideoDefinition> &definitions = VideoDefinition::getDefinitions();
int previousIndex = std::max(definitions.indexOf(definition) - 1, 0);
for (; previousIndex >= 0; previousIndex--) {
const VideoDefinition &previousDefinition = definitions.at(previousIndex);
+ qDebug() << "Testing format" << previousDefinition.getCode();
if (urlMap.contains(previousDefinition.getCode())) {
- // qDebug() << "Found format" << definitionCode;
+ qDebug() << "Found format" << previousDefinition.getCode();
saveDefinitionForUrl(urlMap.value(previousDefinition.getCode()), previousDefinition);
return;
}
emit errorStreamUrl(tr("Cannot get video stream for %1").arg(videoId));
}
+void YTVideo::loadWebPage() {
+ QUrl url("https://www.youtube.com/watch");
+ QUrlQuery q;
+ q.addQueryItem("v", videoId);
+ q.addQueryItem("gl", "US");
+ q.addQueryItem("hl", "en");
+ q.addQueryItem("has_verified", "1");
+ q.addQueryItem("bpctr", "9999999999");
+ url.setQuery(q);
+ 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)));
+ // see you in scrapWebPage(QByteArray)
+}
+
void YTVideo::errorVideoInfo(const QString &message) {
loadingStreamUrl = false;
emit errorStreamUrl(message);
}
void YTVideo::scrapeWebPage(const QByteArray &bytes) {
+ qDebug() << "scrapeWebPage";
+
const QString html = QString::fromUtf8(bytes);
static const QRegExp ageGateRE(JsFunctions::instance()->ageGateRE());
if (ageGateRE.indexIn(html) != -1) {
- // qDebug() << "Found ageGate";
+ qDebug() << "Found ageGate";
ageGate = true;
elIndex = 4;
getVideoInfo();
return;
}
+ // "\"url_encoded_fmt_stream_map\":\s*\"([^\"]+)\""
static const QRegExp fmtMapRE(JsFunctions::instance()->webPageFmtMapRE());
- if (fmtMapRE.indexIn(html) == -1) {
- qWarning() << "Error parsing video page";
- // emit errorStreamUrl("Error parsing video page");
- // loadingStreamUrl = false;
+ if (fmtMapRE.indexIn(html) != -1) {
+ fmtUrlMap = fmtMapRE.cap(1);
+ fmtUrlMap.replace("\\u0026", "&");
+ }
+
+ QRegExp adaptiveFormatsRE("\"adaptive_fmts\":\\s*\"([^\"]+)\"");
+ if (adaptiveFormatsRE.indexIn(html) != -1) {
+ qDebug() << "Found adaptive_fmts";
+ if (!fmtUrlMap.isEmpty()) fmtUrlMap += ',';
+ fmtUrlMap += adaptiveFormatsRE.cap(1).replace("\\u0026", "&");
+ }
+
+ if (fmtUrlMap.isEmpty()) {
+ qWarning() << "Cannot get fmtUrlMap from video page. Trying next el";
elIndex++;
getVideoInfo();
return;
}
- fmtUrlMap = fmtMapRE.cap(1);
- fmtUrlMap.replace("\\u0026", "&");
-// parseFmtUrlMap(fmtUrlMap, true);
-
-#ifdef APP_DASH
- QSettings settings;
- QString definitionName = settings.value("definition", "360p").toString();
- if (definitionName == QLatin1String("1080p")) {
- QRegExp dashManifestRe("\"dashmpd\":\\s*\"([^\"]+)\"");
- if (dashManifestRe.indexIn(html) != -1) {
- dashManifestUrl = dashManifestRe.cap(1);
- dashManifestUrl.remove('\\');
- qDebug() << "dashManifestUrl" << dashManifestUrl;
- } else {
- qWarning() << "DASH manifest not found in webpage";
- if (dashManifestRe.indexIn(fmtUrlMap) != -1) {
- dashManifestUrl = dashManifestRe.cap(1);
- dashManifestUrl.remove('\\');
- qDebug() << "dashManifestUrl" << dashManifestUrl;
- } else
- qWarning() << "DASH manifest not found in fmtUrlMap" << fmtUrlMap;
- }
- }
-#endif
static const QRegExp jsPlayerRe(JsFunctions::instance()->jsPlayerRE());
if (jsPlayerRe.indexIn(html) != -1) {
// qDebug() << "jsPlayer" << jsPlayer;
// QRegExp funcNameRe("[\"']signature[\"']\\s*,\\s*([" + jsNameChars + "]+)\\(");
- static const QRegExp funcNameRe(
- JsFunctions::instance()->signatureFunctionNameRE().arg(jsNameChars));
-
- if (funcNameRe.indexIn(jsPlayer) == -1) {
- qWarning() << "Cannot capture signature function name" << jsPlayer;
- } else {
- sigFuncName = funcNameRe.cap(1);
- captureFunction(sigFuncName, jsPlayer);
- // qWarning() << sigFunctions << sigObjects;
- }
-
-#ifdef APP_DASH
- if (!dashManifestUrl.isEmpty()) {
- QRegExp sigRe("/s/([\\w\\.]+)");
- if (sigRe.indexIn(dashManifestUrl) != -1) {
- qDebug() << "Decrypting signature for dash manifest";
- QString sig = sigRe.cap(1);
- sig = decryptSignature(sig);
- dashManifestUrl.replace(sigRe, "/signature/" + sig);
- qWarning() << "dash manifest" << dashManifestUrl;
-
- if (true) {
- // let phonon play the manifest
- m_streamUrl = dashManifestUrl;
- this->definitionCode = 37;
- emit gotStreamUrl(m_streamUrl);
- loadingStreamUrl = false;
- } else {
- // download the manifest
- QObject *reply = HttpUtils::yt().get(QUrl::fromEncoded(dashManifestUrl.toUtf8()));
- connect(reply, SIGNAL(data(QByteArray)), SLOT(parseDashManifest(QByteArray)));
- connect(reply, SIGNAL(error(QString)), SLOT(errorVideoInfo(QString)));
+ static const QVector<QRegExp> funcNameRes = [] {
+ QVector<QRegExp> res;
+ for (const QString &s : JsFunctions::instance()->signatureFunctionNameREs()) {
+ res << QRegExp(s.arg(jsNameChars));
+ }
+ return res;
+ }();
+ for (const QRegExp &funcNameRe : funcNameRes) {
+ if (funcNameRe.indexIn(jsPlayer) == -1) {
+ qWarning() << "Cannot capture signature function name" << funcNameRe;
+ continue;
+ } else {
+ sigFuncName = funcNameRe.cap(1);
+ qDebug() << "Captures" << funcNameRe.captureCount() << funcNameRe.capturedTexts();
+ if (sigFuncName.isEmpty()) {
+ qDebug() << "Empty capture for" << funcNameRe;
+ continue;
}
-
- return;
+ captureFunction(sigFuncName, jsPlayer);
+ qDebug() << sigFunctions << sigObjects;
+ break;
}
}
-#endif
+ if (sigFuncName.isEmpty()) qDebug() << "Empty signature function name";
parseFmtUrlMap(fmtUrlMap, true);
}
-void YTVideo::parseDashManifest(const QByteArray &bytes) {
- QFile file(Temporary::filename() + ".mpd");
- if (!file.open(QIODevice::WriteOnly)) qWarning() << file.errorString() << file.fileName();
- QDataStream stream(&file);
- stream.writeRawData(bytes.constData(), bytes.size());
-
- m_streamUrl = "file://" + file.fileName();
- this->definitionCode = 37;
- emit gotStreamUrl(m_streamUrl);
- loadingStreamUrl = false;
-}
-
void YTVideo::captureFunction(const QString &name, const QString &js) {
qDebug() << __PRETTY_FUNCTION__ << name;
const QString argsAndBody =
}
void YTVideo::saveDefinitionForUrl(const QString &url, const VideoDefinition &definition) {
- m_streamUrl = QUrl::fromEncoded(url.toUtf8(), QUrl::StrictMode);
+ qDebug() << "Selected video format" << definition.getCode() << definition.getName()
+ << definition.hasAudio();
+ m_streamUrl = url;
definitionCode = definition.getCode();
- emit gotStreamUrl(m_streamUrl);
+
+ QString audioUrl;
+ if (!definition.hasAudio()) {
+ qDebug() << "Finding audio format";
+ static const QVector<int> audioFormats({251, 171, 140});
+ for (int audioFormat : audioFormats) {
+ qDebug() << "Trying audio format" << audioFormat;
+ auto i = urlMap.constFind(audioFormat);
+ if (i != urlMap.constEnd()) {
+ qDebug() << "Found audio format" << i.value();
+ audioUrl = i.value();
+ break;
+ }
+ }
+ }
+
loadingStreamUrl = false;
+ emit gotStreamUrl(url, audioUrl);
}
int getDefinitionCode() const { return definitionCode; }
signals:
- void gotStreamUrl(const QUrl &streamUrl);
+ void gotStreamUrl(const QString &videoUrl, const QString &audioUrl);
void errorStreamUrl(const QString &message);
private slots:
void errorVideoInfo(const QString &message);
void scrapeWebPage(const QByteArray &bytes);
void parseJsPlayer(const QByteArray &bytes);
- void parseDashManifest(const QByteArray &bytes);
private:
void getVideoInfo();
void parseFmtUrlMap(const QString &fmtUrlMap, bool fromWebPage = false);
+ void loadWebPage();
void captureFunction(const QString &name, const QString &js);
void captureObject(const QString &name, const QString &js);
QString decryptSignature(const QString &s);
QUrl m_streamUrl;
int definitionCode;
bool loadingStreamUrl;
- // current index for the elTypes list
- // needed to iterate on elTypes
int elIndex;
bool ageGate;
QString videoToken;
QHash<QString, QString> sigObjects;
QString dashManifestUrl;
QString jsPlayer;
+ QMap<int, QString> urlMap;
};
#endif // YTVIDEO_H
QMainWindow > QToolBar QToolButton::menu-indicator { image: none; }
+QStatusBar {
+ border: 0;
+ padding: 0;
+ background: palette(base);
+ color: palette(text);
+}
+
QStatusBar QToolBar {
padding:0;
spacing:0;
}
QStatusBar::item {
- border:0;
+ border: 0;
+ padding: 0;
+ margin: 0;
}
QStatusBar QToolButton::menu-indicator {
AppsWidget {
background-color: palette(window);
- border-top: 1px solid #808080;
+ border-top: 1px solid palette(mid);
}
RegionsView QPushButton[regionId] {
border-radius: 3px;
}
-RegionsView QPushButton[regionId=""] {
-
-}
-
-RegionsView QPushButton[regionId]:hover {
- background: rgba(0,0,0,16);
+RegionsView QPushButton[regionId]:hover, RegionsView QPushButton[regionId]:checked {
+ background: palette(highlight);
+ color: palette(highlighted-text);
}
RegionsView QPushButton[regionId]:focus {
- background: palette(highlight);
- color: palette(highlightText);
+ background: palette(mid);
outline: 0;
}
-RegionsView QPushButton[regionId]:checked {
- background: rgba(0,0,0,32)
-}
-
SidebarHeader {
- background: #3c3c3c;
padding: 0;
margin: 0;
spacing: 0;
SidebarHeader QPushButton {
background: transparent;
- color: #fff;
text-align: center;
}
border-style: none;
}
-SearchView QLabel[recentHeader="true"] {
- margin-left: -3px;
- margin-bottom: 4px;
- padding: 0;
-}
-
-SearchView QPushButton[recentItem="true"] {
+SearchView *[recentItem="true"], SearchView *[recentHeader="true"] {
border: 0;
- padding: 4px;
- text-align: left;
- outline: 0;
-}
-
-SearchView QPushButton[recentItem="true"]:hover {
- background: rgba(0,0,0,16);
-}
-
-SearchView QPushButton[recentItem="true"]:pressed {
- background: palette(highlight);
- color: white; /* SHOULD BE color: palette(highlightText); */
+ padding: 4px 0;
}
-SearchView QPushButton[recentItem="true"]:focus {
+SearchView *[recentItem="true"]:focus {
outline: 1px solid palette(highlight);
- outline-offset: 4px;
}