]> git.sur5r.net Git - minitube/commitdiff
HD video support
authorFlavio Tordini <flavio.tordini@gmail.com>
Fri, 30 Oct 2009 23:15:05 +0000 (00:15 +0100)
committerFlavio Tordini <flavio.tordini@gmail.com>
Fri, 30 Oct 2009 23:15:05 +0000 (00:15 +0100)
src/MainWindow.cpp
src/MainWindow.h
src/MediaView.cpp
src/networkaccess.cpp
src/networkaccess.h
src/video.cpp
src/video.h

index d97a2b904c5a094698a6e436a3ad5eed4d72c7c7..f442acabad3c40cc610a7243152253fa8335c33d 100755 (executable)
@@ -201,6 +201,15 @@ void MainWindow::createActions() {
     connect(volumeMuteAct, SIGNAL(triggered()), this, SLOT(volumeMute()));
     addAction(volumeMuteAct);
 
+    QAction *hdAct = new QAction(this);
+    hdAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_H));
+    hdAct->setIcon(createHDIcon());
+    hdAct->setCheckable(true);
+    actions->insert("hd", hdAct);
+    QSettings settings;
+    connect(hdAct, SIGNAL(toggled(bool)), this, SLOT(saveHdSetting(bool)));
+    addAction(hdAct);
+
     // common action properties
     foreach (QAction *action, actions->values()) {
 
@@ -211,7 +220,11 @@ void MainWindow::createActions() {
         // never autorepeat.
         // unexperienced users tend to keep keys pressed for a "long" time
         action->setAutoRepeat(false);
-        action->setToolTip(action->statusTip());
+
+        // set to something more meaningful then the toolbar text
+        // HELP! how to remove tooltips altogether?!
+        if (!action->statusTip().isEmpty())
+            action->setToolTip(action->statusTip());
 
         // show keyboard shortcuts in the status bar
         if (!action->shortcut().isEmpty())
@@ -322,7 +335,16 @@ void MainWindow::createStatusBar() {
     statusBar()->addPermanentWidget(totalTime);
 
     // remove ugly borders on OSX
-    statusBar()->setStyleSheet("::item{border:0 solid}");
+    // and remove some excessive padding
+    statusBar()->setStyleSheet("::item{border:0 solid} QStatusBar, QToolBar {padding:0;margin:0} QToolButton {padding:1px}");
+
+    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"));
+    statusBar()->addPermanentWidget(toolBar);
 
     statusBar()->show();
 }
@@ -330,6 +352,7 @@ void MainWindow::createStatusBar() {
 void MainWindow::readSettings() {
     QSettings settings;
     restoreGeometry(settings.value("geometry").toByteArray());
+    The::globalActions()->value("hd")->setChecked(settings.value("hd").toBool());
 }
 
 void MainWindow::writeSettings() {
@@ -767,3 +790,76 @@ void MainWindow::replyMetaDataChanged() {
 }
 
 */
+
+
+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);
+    painter.setPen(pen);
+
+    if (enabled) {
+        QPalette palette;
+        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(Qt::white);
+    } else {
+        QPalette palette;
+        pen.setColor(palette.highlightedText().color());
+    }
+    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() {
+    // QIcon icon;
+    hdOffIcon.addPixmap(createHDPixmap(false));
+    hdOnIcon.addPixmap(createHDPixmap(true));
+    return hdOffIcon;
+}
+
+void MainWindow::saveHdSetting(bool enabled) {
+    QSettings settings;
+    settings.setValue("hd", enabled);
+    QAction *hdAct = The::globalActions()->value("hd");
+    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) + ")");
+    }
+}
+
+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"));
+    }
+}
index 967a2289c767a72f5efa6c939a9bd657955768ed..f8b81e0690052185dbcc25ab72928343022d5a12 100755 (executable)
@@ -24,6 +24,9 @@ public:
     MainWindow();
     ~MainWindow();
 
+public slots:
+    void hdIndicator(bool isHd);
+
 protected:
     void closeEvent(QCloseEvent *);
 
@@ -51,6 +54,7 @@ private slots:
     void searchFocus();
     void tick(qint64 time);
     void totalTimeChanged(qint64 time);
+    void saveHdSetting(bool enabled);
 
     // volume shortcuts
     void volumeUp();
@@ -80,6 +84,8 @@ private:
     void readSettings();
     void writeSettings();
     void showWidget(QWidget*);
+    QPixmap createHDPixmap(bool enabled);
+    QIcon createHDIcon();
 
     // view mechanism
     QPointer<FaderWidget> faderWidget;
index d5b5ec17afea25b2d99d70ea907a8d32f0622ab6..9d804519d371a8c0e12d2d11b669d2f44a99151c 100644 (file)
@@ -247,6 +247,7 @@ void MediaView::activeRowChanged(int row) {
     connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
     // TODO handle signal in a proper slot and impl item error status
     connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
+
     video->loadStreamUrl();
 
     // reset the timer flag
@@ -277,6 +278,16 @@ void MediaView::gotStreamUrl(QUrl streamUrl) {
         listView->scrollTo(index, QAbstractItemView::EnsureVisible);
     }
 
+    // HD indicator
+
+    // get the Video that sent the signal
+    Video *video = static_cast<Video *>(sender());
+    if (!video) {
+        qDebug() << "Cannot get sender";
+        return;
+    }
+    bool ret = QMetaObject::invokeMethod(qApp->topLevelWidgets().first(), "hdIndicator", Qt::QueuedConnection, Q_ARG(bool, video->isHd()));
+
 }
 
 void MediaView::itemActivated(const QModelIndex &index) {
index 921fa79f378ceb2f0198817bcbc3f6a923dab52f..47d09df54ea9fafe7aa127021d7d26f7b0793b6c 100644 (file)
@@ -10,30 +10,25 @@ NetworkReply::NetworkReply(QNetworkReply *networkReply) : QObject(networkReply)
     this->networkReply = networkReply;
 }
 
-void NetworkReply::metaDataChanged() {
+void NetworkReply::finished() {
 
     QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
     if (redirection.isValid()) {
 
-        qDebug() << "Redirect" << redirection;
+        // qDebug() << "Redirect!"; // << redirection;
 
-        QNetworkReply *redirectReply = The::http()->simpleGet(redirection);
+        QNetworkReply *redirectReply = The::http()->simpleGet(redirection, networkReply->operation());
 
         setParent(redirectReply);
         networkReply->deleteLater();
         networkReply = redirectReply;
 
-        // handle redirections
-        connect(networkReply, SIGNAL(metaDataChanged()),
-                this, SLOT(metaDataChanged()), Qt::QueuedConnection);
-
         // when the request is finished we'll invoke the target method
-        connect(networkReply, SIGNAL(finished()), this, SLOT(finished()), Qt::QueuedConnection);
+        connect(networkReply, SIGNAL(finished()), this, SLOT(finished()), Qt::AutoConnection);
 
+        return;
     }
-}
 
-void NetworkReply::finished() {
 
     emit finished(networkReply);
 
@@ -55,15 +50,32 @@ void NetworkReply::requestError(QNetworkReply::NetworkError code) {
 
 NetworkAccess::NetworkAccess( QObject* parent) : QObject( parent ) {}
 
-QNetworkReply* NetworkAccess::simpleGet(QUrl url) {
+QNetworkReply* NetworkAccess::simpleGet(QUrl url, int operation) {
 
     QNetworkAccessManager *manager = The::networkAccessManager();
 
     QNetworkRequest request(url);
     request.setRawHeader("User-Agent", Constants::USER_AGENT.toUtf8());
     request.setRawHeader("Connection", "Keep-Alive");
-    qDebug() << "GET" << url.toString();
-    QNetworkReply *networkReply = manager->get(request);
+
+    QNetworkReply *networkReply;
+    switch (operation) {
+
+    case QNetworkAccessManager::GetOperation:
+        qDebug() << "GET" << url.toString();
+        networkReply = manager->get(request);
+        break;
+
+    case QNetworkAccessManager::HeadOperation:
+        qDebug() << "HEAD" << url.toString();
+        networkReply = manager->head(request);
+        break;
+
+    default:
+        qDebug() << "Unknown operation:" << operation;
+        return 0;
+
+    }
 
     // error handling
     connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
@@ -78,30 +90,45 @@ NetworkReply* NetworkAccess::get(const QUrl url) {
     QNetworkReply *networkReply = simpleGet(url);
     NetworkReply *reply = new NetworkReply(networkReply);
 
-    // handle redirections
-    connect(networkReply, SIGNAL(metaDataChanged()),
-            reply, SLOT(metaDataChanged()), Qt::QueuedConnection);
+    // error signal
+    connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
+            reply, SLOT(requestError(QNetworkReply::NetworkError)));
+
+    // when the request is finished we'll invoke the target method
+    connect(networkReply, SIGNAL(finished()), reply, SLOT(finished()), Qt::AutoConnection);
+
+    return reply;
+
+}
+
+NetworkReply* NetworkAccess::head(const QUrl url) {
+
+    QNetworkReply *networkReply = simpleGet(url, QNetworkAccessManager::HeadOperation);
+    NetworkReply *reply = new NetworkReply(networkReply);
 
     // error signal
     connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
             reply, SLOT(requestError(QNetworkReply::NetworkError)));
 
     // when the request is finished we'll invoke the target method
-    connect(networkReply, SIGNAL(finished()), reply, SLOT(finished()), Qt::QueuedConnection);
+    connect(networkReply, SIGNAL(finished()), reply, SLOT(finished()), Qt::AutoConnection);
 
     return reply;
 
 }
 
+/*** sync ***/
+
+
 QNetworkReply* NetworkAccess::syncGet(QUrl url) {
 
     working = true;
 
     networkReply = simpleGet(url);
     connect(networkReply, SIGNAL(metaDataChanged()),
-            this, SLOT(syncMetaDataChanged()), Qt::QueuedConnection);
+            this, SLOT(syncMetaDataChanged()), Qt::AutoConnection);
     connect(networkReply, SIGNAL(finished()),
-            this, SLOT(syncFinished()), Qt::QueuedConnection);
+            this, SLOT(syncFinished()), Qt::AutoConnection);
     connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
             this, SLOT(error(QNetworkReply::NetworkError)));
 
@@ -130,9 +157,9 @@ void NetworkAccess::syncMetaDataChanged() {
         networkReply->deleteLater();
         networkReply = manager->get(QNetworkRequest(redirection));
         connect(networkReply, SIGNAL(metaDataChanged()),
-                this, SLOT(metaDataChanged()), Qt::QueuedConnection);
+                this, SLOT(metaDataChanged()), Qt::AutoConnection);
         connect(networkReply, SIGNAL(finished()),
-                this, SLOT(finished()), Qt::QueuedConnection);
+                this, SLOT(finished()), Qt::AutoConnection);
         */
     }
 
@@ -151,6 +178,10 @@ void NetworkAccess::error(QNetworkReply::NetworkError code) {
         return;
     }
 
+    // Ignore HEADs
+    if (networkReply->operation() == QNetworkAccessManager::HeadOperation)
+        return;
+
     // report the error in the status bar
     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(qApp->topLevelWidgets().first());
     if (mainWindow) mainWindow->statusBar()->showMessage(
index 385b3cac4f00a8404a4f445d964f0c2bd61b565d..64cf5086c35025ed38235bad7bca04cfb9a4bc34 100644 (file)
@@ -16,7 +16,6 @@ public:
 
 public slots:
     void finished();
-    void metaDataChanged();
     void requestError(QNetworkReply::NetworkError);
 
 signals:
@@ -36,8 +35,9 @@ class NetworkAccess : public QObject {
 
 public:
     NetworkAccess( QObject* parent=0);
-    QNetworkReply* simpleGet(QUrl url);
+    QNetworkReply* simpleGet(QUrl url, int operation = QNetworkAccessManager::GetOperation);
     NetworkReply* get(QUrl url);
+    NetworkReply* head(QUrl url);
     QNetworkReply* syncGet(QUrl url);
     QByteArray syncGetBytes(QUrl url);
     QString syncGetString(QUrl url);
index 3345445fb8f4f1f1b67438e477566de8e2abc0be..3028962e282ea8fcbd91f4ed47d63764c390dae1 100644 (file)
@@ -6,10 +6,9 @@ namespace The {
     NetworkAccess* http();
 }
 
-Video::Video() : m_thumbnailUrls(QList<QUrl>()) {
-    m_duration = 0;
-    m_viewCount = -1;
-}
+Video::Video() : m_duration(0),
+m_viewCount(-1),
+m_hd(false) { }
 
 void Video::preloadThumbnail() {
     if (m_thumbnailUrls.isEmpty()) return;
@@ -26,6 +25,12 @@ const QImage Video::thumbnail() const {
     return m_thumbnail;
 }
 
+void Video::loadStreamUrl() {
+    // if (m_streamUrl.isEmpty())
+        this->scrapeStreamUrl();
+    // else emit gotStreamUrl(m_streamUrl);
+}
+
 void Video::scrapeStreamUrl() {
 
     // https://develop.participatoryculture.org/trac/democracy/browser/trunk/tv/portable/flashscraper.py
@@ -102,13 +107,31 @@ void  Video::gotVideoInfo(QByteArray data) {
     videoToken = videoToken.replace("%3D", "=");
     // qDebug() << "token" << videoToken;
 
+    QSettings settings;
+    if (settings.value("hd").toBool())
+        findHdVideo(videoToken);
+    else
+        standardVideoUrl(videoToken);
+
+}
+
+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::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);
 }
 
@@ -131,15 +154,40 @@ void Video::scrapWebPage(QByteArray data) {
     QString videoToken = re.cap(1);
     // FIXME proper decode
     videoToken = videoToken.replace("%3D", "=");
-    qDebug() << "token" << videoToken;
+    // qDebug() << "token" << videoToken;
+
+    QSettings settings;
+    if (settings.value("hd").toBool())
+        findHdVideo(videoToken);
+    else
+        standardVideoUrl(videoToken);
+
+}
+
+void Video::findHdVideo(QString videoToken) {
 
+    // we'll need this in gotHeaders()
+    this->videoToken = videoToken;
+
+    // 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=detailpage&ps=default&fmt=18"));
+                         .append("&eurl=&el=embedded&ps=default&fmt=22"));
 
-    m_streamUrl = videoUrl;
+    QObject *reply = The::http()->head(videoUrl);
+    connect(reply, SIGNAL(finished(QNetworkReply*)), SLOT(gotHdHeaders(QNetworkReply*)));
+    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(errorVideoInfo(QNetworkReply*)));
 
-    emit gotStreamUrl(videoUrl);
+    // see you in gotHeaders()
+}
 
+void Video::gotHdHeaders(QNetworkReply* reply) {
+    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+    // qDebug() << "gotHeaders" << statusCode;
+    if (statusCode == 200) {
+        hdVideoUrl(videoToken);
+    } else {
+        standardVideoUrl(videoToken);
+    }
 }
index bdd555eafde382795556459fd62240df6a913a80..0ae15faf772786b606451a1183648be14f402f85 100644 (file)
@@ -23,12 +23,6 @@ public:
     const QUrl webpage() const { return m_webpage; }
     void setWebpage( QUrl webpage ) { m_webpage = webpage; }
 
-    void loadStreamUrl() {
-        if (m_streamUrl.isEmpty())
-            this->scrapeStreamUrl();
-        else emit gotStreamUrl(m_streamUrl);
-    }
-
     QList<QUrl> thumbnailUrls() const { return m_thumbnailUrls; }
     void addThumbnailUrl(QUrl url) {
         m_thumbnailUrls << url;
@@ -46,6 +40,10 @@ public:
     const QDateTime published() const { return m_published; }
     void setPublished( QDateTime published ) { m_published = published; }
 
+    bool isHd() const { return m_hd; }
+
+    void loadStreamUrl();
+
 public slots:
     void setThumbnail(QByteArray bytes);
 
@@ -58,9 +56,13 @@ private slots:
     void gotVideoInfo(QByteArray);
     void errorVideoInfo(QNetworkReply*);
     void scrapWebPage(QByteArray);
+    void gotHdHeaders(QNetworkReply*);
 
 private:
     void scrapeStreamUrl();
+    void findHdVideo(QString videoToken);
+    void standardVideoUrl(QString videoToken);
+    void hdVideoUrl(QString videoToken);
 
     QString m_title;
     QString m_description;
@@ -78,6 +80,9 @@ private:
     // The YouTube video id
     // This is needed by the gotVideoInfo callback
     QString videoId;
+
+    QString videoToken;
+    int m_hd;
 };
 
 // This is required in order to use QPointer<Video> as a QVariant