From: Flavio Tordini Date: Tue, 20 Apr 2010 14:10:17 +0000 (+0200) Subject: Support for 1080p videos X-Git-Tag: 1.0~14 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=4868cb395fc845cbf34d12d301ad19d4d4f7f352;p=minitube Support for 1080p videos --- diff --git a/minitube.pro b/minitube.pro index 1568135..09f3649 100755 --- a/minitube.pro +++ b/minitube.pro @@ -41,7 +41,8 @@ HEADERS += src/MainWindow.h \ src/videoareawidget.h \ src/googlesuggest.h \ src/videowidget.h \ - src/flickcharm.h + src/flickcharm.h \ + src/videodefinition.h SOURCES += src/main.cpp \ src/MainWindow.cpp \ src/SearchView.cpp \ @@ -67,7 +68,8 @@ SOURCES += src/main.cpp \ src/videoareawidget.cpp \ src/googlesuggest.cpp \ src/videowidget.cpp \ - src/flickcharm.cpp + src/flickcharm.cpp \ + src/videodefinition.cpp RESOURCES += resources.qrc DESTDIR = build/target/ OBJECTS_DIR = build/obj/ diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 897f1da..31398c4 100755 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -3,13 +3,13 @@ #include "Constants.h" #include "iconloader/qticonloader.h" #include "global.h" +#include "videodefinition.h" MainWindow::MainWindow() : mediaObject(0), audioOutput(0), - aboutView(0) { - - m_fullscreen = false; + aboutView(0), + m_fullscreen(false) { // views mechanism history = new QStack(); @@ -178,7 +178,7 @@ void MainWindow::createActions() { actions->insert("site", siteAct); connect(siteAct, SIGNAL(triggered()), this, SLOT(visitSite())); - donateAct = new QAction(tr("&Donate"), this); + donateAct = new QAction(tr("Make a &donation"), this); donateAct->setStatusTip(tr("Please support the continued development of %1").arg(Constants::APP_NAME)); actions->insert("donate", donateAct); connect(donateAct, SIGNAL(triggered()), this, SLOT(donate())); @@ -217,13 +217,18 @@ void MainWindow::createActions() { connect(volumeMuteAct, SIGNAL(triggered()), this, SLOT(volumeMute())); addAction(volumeMuteAct); - QAction *hdAct = new QAction(this); - hdAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_D)); - hdAct->setIcon(createHDIcon()); - hdAct->setCheckable(true); - actions->insert("hd", hdAct); - connect(hdAct, SIGNAL(toggled(bool)), this, SLOT(hdMode(bool))); - addAction(hdAct); + QAction *definitionAct = new QAction(this); + definitionAct->setShortcuts(QList() << QKeySequence(Qt::CTRL + Qt::Key_D)); + /* + QMenu *definitionMenu = new QMenu(this); + foreach (QString definition, VideoDefinition::getDefinitionNames()) { + definitionMenu->addAction(definition); + } + definitionAct->setMenu(definitionMenu); + */ + actions->insert("definition", definitionAct); + connect(definitionAct, SIGNAL(triggered()), SLOT(toggleDefinitionMode())); + addAction(definitionAct); // common action properties foreach (QAction *action, actions->values()) { @@ -346,24 +351,20 @@ void MainWindow::createToolBars() { } void MainWindow::createStatusBar() { + + // remove ugly borders on OSX + // also remove excessive spacing + statusBar()->setStyleSheet("::item{border:0 solid} QToolBar {padding:0;spacing:0;margin:0}"); + currentTime = new QLabel(this); statusBar()->addPermanentWidget(currentTime); totalTime = new QLabel(this); statusBar()->addPermanentWidget(totalTime); - // remove ugly borders on OSX - // and remove some excessive padding - statusBar()->setStyleSheet("::item{border:0 solid} " - "QStatusBar, QToolBar, QToolButton {spacing:0;padding:0;margin:0} " - ); - QToolBar *toolBar = new QToolBar(this); - int iconHeight = 24; // statusBar()->height(); - int iconWidth = 36; // iconHeight * 3 / 2; - toolBar->setIconSize(QSize(iconWidth, iconHeight)); - toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); - toolBar->addAction(The::globalActions()->value("hd")); + toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + toolBar->addAction(The::globalActions()->value("definition")); statusBar()->addPermanentWidget(toolBar); statusBar()->show(); @@ -372,7 +373,7 @@ void MainWindow::createStatusBar() { void MainWindow::readSettings() { QSettings settings; restoreGeometry(settings.value("geometry").toByteArray()); - hdMode(settings.value("hd").toBool()); + setDefinitionMode(settings.value("definition", VideoDefinition::getDefinitionNames().first()).toString()); audioOutput->setVolume(settings.value("volume", 1).toDouble()); audioOutput->setMuted(settings.value("volumeMute").toBool()); } @@ -383,7 +384,6 @@ void MainWindow::writeSettings() { return; QSettings settings; settings.setValue("geometry", saveGeometry()); - settings.setValue("hd", The::globalActions()->value("hd")->isChecked()); settings.setValue("volume", audioOutput->volume()); settings.setValue("volumeMute", audioOutput->isMuted()); mediaView->saveSplitterState(); @@ -754,76 +754,27 @@ void MainWindow::volumeMutedChanged(bool muted) { statusBar()->showMessage(tr("Volume is unmuted")); } -QPixmap MainWindow::createHDPixmap(bool enabled) { - QPixmap pixmap = QPixmap(24,24); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - painter.setRenderHints(QPainter::Antialiasing, true); - - QRect rect(0, 3, 24, 18); - - QPen pen; - pen.setColor(Qt::black); - pen.setWidth(1); - painter.setPen(pen); - - if (enabled) { - painter.setBrush(palette().highlight()); - } else { - QLinearGradient gradient(QPointF(0, 0), QPointF(0, rect.height() / 2)); - gradient.setColorAt(0, QColor(0x6d, 0x6d, 0x6d)); - gradient.setColorAt(1, QColor(0x25, 0x25, 0x25)); - painter.setBrush(QBrush(gradient)); - } - painter.drawRoundedRect(rect, 5, 5); - - if (enabled) { - pen.setColor(palette().highlightedText().color()); - } else { - pen.setColor(Qt::white); - } - painter.setPen(pen); - - QFont font; - font.setPixelSize(12); - font.setBold(true); - painter.setFont(font); - painter.drawText(rect, Qt::AlignCenter, "HD"); - - return pixmap; -} - -static QIcon hdOnIcon; -static QIcon hdOffIcon; - -QIcon MainWindow::createHDIcon() { - hdOffIcon.addPixmap(createHDPixmap(false)); - hdOnIcon.addPixmap(createHDPixmap(true)); - return hdOffIcon; -} - -void MainWindow::hdMode(bool enabled) { - QAction *hdAct = The::globalActions()->value("hd"); - hdAct->setChecked(enabled); - if (enabled) { - hdAct->setStatusTip(tr("High Definition video is enabled") + " (" + hdAct->shortcut().toString(QKeySequence::NativeText) + ")"); - } else { - hdAct->setStatusTip(tr("High Definition video is not enabled") + " (" + hdAct->shortcut().toString(QKeySequence::NativeText) + ")"); - } - statusBar()->showMessage(hdAct->statusTip()); +void MainWindow::setDefinitionMode(QString definitionName) { + QAction *definitionAct = The::globalActions()->value("definition"); + definitionAct->setText(definitionName); + definitionAct->setStatusTip(tr("Maximum video definition set to %1").arg(definitionAct->text()) + + " (" + definitionAct->shortcut().toString(QKeySequence::NativeText) + ")"); + statusBar()->showMessage(definitionAct->statusTip()); QSettings settings; - settings.setValue("hd", enabled); + settings.setValue("definition", definitionName); } -void MainWindow::hdIndicator(bool isHd) { - QAction *hdAct = The::globalActions()->value("hd"); - if (isHd) { - hdAct->setIcon(hdOnIcon); - hdAct->setToolTip(tr("The current video is in High Definition")); - } else { - hdAct->setIcon(hdOffIcon); - hdAct->setToolTip(tr("The current video is not in High Definition")); +void MainWindow::toggleDefinitionMode() { + QSettings settings; + QString currentDefinition = settings.value("definition").toString(); + QStringList definitionNames = VideoDefinition::getDefinitionNames(); + int currentIndex = definitionNames.indexOf(currentDefinition); + int nextIndex = 0; + if (currentIndex != definitionNames.size() - 1) { + nextIndex = currentIndex + 1; } + QString nextDefinition = definitionNames.at(nextIndex); + setDefinitionMode(nextDefinition); } void MainWindow::showFullscreenToolbar(bool show) { diff --git a/src/MainWindow.h b/src/MainWindow.h index bdb1396..1eb0251 100755 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -21,9 +21,6 @@ public: MainWindow(); ~MainWindow(); -public slots: - void hdIndicator(bool isHd); - protected: void closeEvent(QCloseEvent *); bool eventFilter(QObject *obj, QEvent *event); @@ -44,7 +41,8 @@ private slots: void searchFocus(); void tick(qint64 time); void totalTimeChanged(qint64 time); - void hdMode(bool enabled); + void setDefinitionMode(QString definitionName); + void toggleDefinitionMode(); void clearRecentKeywords(); // volume shortcuts @@ -67,8 +65,6 @@ private: void readSettings(); void writeSettings(); void showWidget(QWidget*); - QPixmap createHDPixmap(bool enabled); - QIcon createHDIcon(); static QString formatTime(qint64 time); // view mechanism diff --git a/src/video.cpp b/src/video.cpp index bb86374..635c95c 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1,6 +1,7 @@ #include "video.h" #include "networkaccess.h" #include +#include "videodefinition.h" namespace The { NetworkAccess* http(); @@ -8,7 +9,7 @@ namespace The { Video::Video() : m_duration(0), m_viewCount(-1), -m_hd(false), +definitionCode(0), elIndex(0) { } void Video::preloadThumbnail() { @@ -26,33 +27,22 @@ const QImage Video::thumbnail() const { return m_thumbnail; } -void Video::loadStreamUrl() { - // if (m_streamUrl.isEmpty()) - this->scrapeStreamUrl(); - // else emit gotStreamUrl(m_streamUrl); -} - static const QStringList elTypes = QStringList() << "embedded" << "vevo" << "detailpage"; -void Video::scrapeStreamUrl() { +void Video::loadStreamUrl() { // https://develop.participatoryculture.org/trac/democracy/browser/trunk/tv/portable/flashscraper.py - QUrl webpage = m_webpage; - // qDebug() << webpage.toString(); - // Get Video ID // youtube-dl line 428 // QRegExp re("^((?:http://)?(?:\\w+\\.)?youtube\\.com/(?:(?:v/)|(?:(?:watch(?:\\.php)?)?\\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$"); QRegExp re("^http://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+).*"); - bool match = re.exactMatch(webpage.toString()); + bool match = re.exactMatch(m_webpage.toString()); if (!match || re.numCaptures() < 1) { - emit errorStreamUrl(QString("Cannot get video id for %1").arg(webpage.toString())); + emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString())); return; } videoId = re.cap(1); - // if (!videoId) return false; - // qDebug() << videoId; getVideoInfo(); @@ -64,7 +54,7 @@ void Video::getVideoInfo() { // Don't panic! We have a plan B. // get the youtube video webpage QObject *reply = The::http()->get(webpage().toString()); - connect(reply, SIGNAL(data(QByteArray)), SLOT(scrapWebPage(QByteArray))); + connect(reply, SIGNAL(data(QByteArray)), SLOT(scrapeWebPage(QByteArray))); connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*))); // see you in scrapWebPage(QByteArray) return; @@ -100,33 +90,30 @@ void Video::gotVideoInfo(QByteArray data) { QString videoToken = re.cap(1); // FIXME proper decode videoToken = videoToken.replace("%3D", "="); + // we'll need this in gotHeadHeaders() + this->videoToken = videoToken; + // qDebug() << "token" << videoToken; QSettings settings; - if (settings.value("hd").toBool()) - findHdVideo(videoToken); - else - standardVideoUrl(videoToken); + QString definitionName = settings.value("definition").toString(); + int definitionCode = VideoDefinition::getDefinitionCode(definitionName); + if (definitionCode == 18) { + // This is assumed always available + foundVideoUrl(videoToken, 18); + } else { + findVideoUrl(definitionCode); + } } -void Video::standardVideoUrl(QString videoToken) { - QUrl videoUrl = QUrl(QString("http://www.youtube.com/get_video?video_id=") - .append(videoId) - .append("&t=").append(videoToken) - .append("&eurl=&el=embedded&ps=default&fmt=18")); - m_streamUrl = videoUrl; - m_hd = false; - emit gotStreamUrl(videoUrl); -} +void Video::foundVideoUrl(QString videoToken, int definitionCode) { + + QUrl videoUrl = QUrl(QString( + "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=embedded&ps=default&fmt=%3" + ).arg(videoId, videoToken, QString::number(definitionCode))); -void Video::hdVideoUrl(QString videoToken) { - QUrl videoUrl = QUrl(QString("http://www.youtube.com/get_video?video_id=") - .append(videoId) - .append("&t=").append(videoToken) - .append("&eurl=&el=embedded&ps=default&fmt=22")); m_streamUrl = videoUrl; - m_hd = true; emit gotStreamUrl(videoUrl); } @@ -134,7 +121,7 @@ void Video::errorVideoInfo(QNetworkReply *reply) { emit errorStreamUrl(tr("Network error: %1 for %2").arg(reply->errorString(), reply->url().toString())); } -void Video::scrapWebPage(QByteArray data) { +void Video::scrapeWebPage(QByteArray data) { QString videoHTML = QString::fromUtf8(data); QRegExp re(".*, \"t\": \"([^\"]+)\".*"); @@ -149,40 +136,78 @@ void Video::scrapWebPage(QByteArray data) { QString videoToken = re.cap(1); // FIXME proper decode videoToken = videoToken.replace("%3D", "="); + + // we'll need this in gotHeadHeaders() + this->videoToken = videoToken; + // qDebug() << "token" << videoToken; QSettings settings; - if (settings.value("hd").toBool()) - findHdVideo(videoToken); - else - standardVideoUrl(videoToken); + QString definitionName = settings.value("definition").toString(); + int definitionCode = VideoDefinition::getDefinitionCode(definitionName); + if (definitionCode == 18) { + // This is assumed always available + foundVideoUrl(videoToken, 18); + } else { + findVideoUrl(definitionCode); + } } -void Video::findHdVideo(QString videoToken) { +void Video::gotHeadHeaders(QNetworkReply* reply) { + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // qDebug() << "gotHeaders" << statusCode; + if (statusCode == 200) { + foundVideoUrl(videoToken, definitionCode); + } else { + + // try next (lower quality) definition + /* + QStringList definitionNames = VideoDefinition::getDefinitionNames(); + int currentIndex = definitionNames.indexOf(currentDefinition); + int previousIndex = 0; + if (currentIndex > 0) { + previousIndex = currentIndex - 1; + } + if (previousIndex > 0) { + QString nextDefinitionName = definitionNames.at(previousIndex); + findVideoUrl(nextDefinitionName); + } else { + foundVideoUrl(videoToken, 18); + }*/ + + + QList definitionCodes = VideoDefinition::getDefinitionCodes(); + int currentIndex = definitionCodes.indexOf(definitionCode); + int previousIndex = 0; + if (currentIndex > 0) { + previousIndex = currentIndex - 1; + int definitionCode = definitionCodes.at(previousIndex); + if (definitionCode == 18) { + // This is assumed always available + foundVideoUrl(videoToken, 18); + } else { + findVideoUrl(definitionCode); + } + + } else { + foundVideoUrl(videoToken, 18); + } - // we'll need this in gotHeaders() - this->videoToken = videoToken; + } +} + +void Video::findVideoUrl(int definitionCode) { + this->definitionCode = definitionCode; - // try HD: fmt=22 - QUrl videoUrl = QUrl(QString("http://www.youtube.com/get_video?video_id=") - .append(videoId) - .append("&t=").append(videoToken) - .append("&eurl=&el=embedded&ps=default&fmt=22")); + QUrl videoUrl = QUrl(QString( + "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=embedded&ps=default&fmt=%3" + ).arg(videoId, videoToken, QString::number(definitionCode))); QObject *reply = The::http()->head(videoUrl); - connect(reply, SIGNAL(finished(QNetworkReply*)), SLOT(gotHdHeaders(QNetworkReply*))); + connect(reply, SIGNAL(finished(QNetworkReply*)), SLOT(gotHeadHeaders(QNetworkReply*))); // connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*))); - // see you in gotHeaders() -} + // see you in gotHeadHeaders() -void Video::gotHdHeaders(QNetworkReply* reply) { - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - // qDebug() << "gotHeaders" << statusCode; - if (statusCode == 200) { - hdVideoUrl(videoToken); - } else { - standardVideoUrl(videoToken); - } } diff --git a/src/video.h b/src/video.h index 5f3aa46..2702d7a 100644 --- a/src/video.h +++ b/src/video.h @@ -40,7 +40,7 @@ public: const QDateTime published() const { return m_published; } void setPublished( QDateTime published ) { m_published = published; } - bool isHd() const { return m_hd; } + bool getDefinitionCode() const { return definitionCode; } void loadStreamUrl(); QUrl getStreamUrl() { return m_streamUrl; } @@ -56,25 +56,21 @@ signals: private slots: void gotVideoInfo(QByteArray); void errorVideoInfo(QNetworkReply*); - void scrapWebPage(QByteArray); - void gotHdHeaders(QNetworkReply*); + void scrapeWebPage(QByteArray); + void gotHeadHeaders(QNetworkReply*); private: - void scrapeStreamUrl(); void getVideoInfo(); - void findHdVideo(QString videoToken); - void standardVideoUrl(QString videoToken); - void hdVideoUrl(QString videoToken); + void findVideoUrl(int definitionCode); + void foundVideoUrl(QString videoToken, int definitionCode); QString m_title; QString m_description; QString m_author; - // QUrl m_authorUrl; QUrl m_webpage; QUrl m_streamUrl; QImage m_thumbnail; QList m_thumbnailUrls; - // QList m_thumbnails; int m_duration; QDateTime m_published; int m_viewCount; @@ -84,7 +80,7 @@ private: QString videoId; QString videoToken; - int m_hd; + int definitionCode; // current index for the elTypes list // needed to iterate on elTypes diff --git a/src/videodefinition.cpp b/src/videodefinition.cpp new file mode 100644 index 0000000..c2180e2 --- /dev/null +++ b/src/videodefinition.cpp @@ -0,0 +1,25 @@ +#include "videodefinition.h" + +QStringList VideoDefinition::getDefinitionNames() { + static QStringList definitionNames = QStringList() << "360p" << "720p" << "1080p"; + return definitionNames; +} + +QList VideoDefinition::getDefinitionCodes() { + static QList definitionCodes = QList() << 18 << 22 << 37; + return definitionCodes; +} + +QHash VideoDefinition::getDefinitions() { + static QHash definitions; + if (definitions.isEmpty()) { + definitions.insert("360p", 18); + definitions.insert("720p", 22); + definitions.insert("1080p", 37); + } + return definitions; +} + +int VideoDefinition::getDefinitionCode(QString name) { + return VideoDefinition::getDefinitions().value(name); +} diff --git a/src/videodefinition.h b/src/videodefinition.h new file mode 100644 index 0000000..8e2e4c0 --- /dev/null +++ b/src/videodefinition.h @@ -0,0 +1,16 @@ +#ifndef VIDEODEFINITION_H +#define VIDEODEFINITION_H + +#include + +class VideoDefinition { + +public: + static QStringList getDefinitionNames(); + static QList getDefinitionCodes(); + static QHash getDefinitions(); + static int getDefinitionCode(QString name); + +}; + +#endif // VIDEODEFINITION_H