improve video uploading
authorStepan777 <stepik-777@mail.ru>
Mon, 06 Aug 2012 00:44:32 +0400
changeset 7507 3032a5739fe1
parent 7503 deaeac102355
child 7525 5c840e221993
improve video uploading
QTfrontend/net/recorder.cpp
QTfrontend/ui/dialog/upload_video.cpp
QTfrontend/ui/dialog/upload_video.h
QTfrontend/ui/page/pagevideos.cpp
QTfrontend/ui/page/pagevideos.h
--- a/QTfrontend/net/recorder.cpp	Mon Aug 06 00:40:26 2012 +0400
+++ b/QTfrontend/net/recorder.cpp	Mon Aug 06 00:44:32 2012 +0400
@@ -26,6 +26,13 @@
 #include "game.h"
 #include "libav_iteraction.h"
 
+// Encoding is memory expensive process, so we need to limit maximum number
+// of simultaneous encoders.
+static const int maxRecorders = 3;
+static int numRecorders = 0;
+
+static QList<HWRecorder*> queue;
+
 HWRecorder::HWRecorder(GameUIConfig * config, const QString &prefix) :
     TCPBase(false)
 {
@@ -38,6 +45,10 @@
 HWRecorder::~HWRecorder()
 {
     emit encodingFinished(finished);
+    if (queue.empty())
+        numRecorders--;
+    else
+        queue.takeFirst()->Start();
 }
 
 void HWRecorder::onClientDisconnect()
@@ -76,8 +87,13 @@
     toSendBuf.replace(QByteArray("\x02TN"), QByteArray("\x02TV"));
     toSendBuf.replace(QByteArray("\x02TS"), QByteArray("\x02TV"));
 
-    // run engine
-    Start();
+    if (numRecorders < maxRecorders)
+    {
+        numRecorders++;
+        Start(); // run engine
+    }
+    else
+        queue.push_back(this);
 }
 
 QStringList HWRecorder::getArguments()
--- a/QTfrontend/ui/dialog/upload_video.cpp	Mon Aug 06 00:40:26 2012 +0400
+++ b/QTfrontend/ui/dialog/upload_video.cpp	Mon Aug 06 00:44:32 2012 +0400
@@ -29,6 +29,10 @@
 #include <QNetworkAccessManager>
 #include <QNetworkRequest>
 #include <QNetworkReply>
+#include <QMessageBox>
+#include <QRegExp>
+#include <QRegExpValidator>
+#include <QMessageBox>
 
 #include "upload_video.h"
 #include "hwconsts.h"
@@ -50,86 +54,107 @@
 
     // Google requires us to display this, see https://developers.google.com/youtube/terms
     QString GoogleNotice =
-        "By clicking 'upload,' you certify that you own all rights to the content or that\n"
-        "you are authorized by the owner to make the content publicly available on YouTube,\n"
-        "and that it otherwise complies with the YouTube Terms of Service located at\n"
+        "By clicking 'upload,' you certify that you own all rights to the content or that "
+        "you are authorized by the owner to make the content publicly available on YouTube, "
+        "and that it otherwise complies with the YouTube Terms of Service located at "
         "http://www.youtube.com/t/terms.";
 
+    // youtube doesn't understand this characters, even when they are properly escaped
+    // (either with CDATA or with &lt or &gt)
+    QRegExp rx("[^<>]*");
+
+    int row = 0;
+
     QGridLayout * layout = new QGridLayout(this);
 
     QLabel * lbLabel = new QLabel(this);
+    lbLabel->setWordWrap(true);
     lbLabel->setText(QLabel::tr(
-                         "Please provide either the YouTube account name\n"
+                         "Please provide either the YouTube account name "
                          "or the email address associated with the Google Account."));
-    layout->addWidget(lbLabel, 0, 0, 1, 2);
+    layout->addWidget(lbLabel, row++, 0, 1, 2);
 
     lbLabel = new QLabel(this);
     lbLabel->setText(QLabel::tr("Account name (or email): "));
-    layout->addWidget(lbLabel, 1, 0);
+    layout->addWidget(lbLabel, row, 0);
 
     leAccount = new QLineEdit(this);
-    layout->addWidget(leAccount, 1, 1);
+    layout->addWidget(leAccount, row++, 1);
 
     lbLabel = new QLabel(this);
     lbLabel->setText(QLabel::tr("Password: "));
-    layout->addWidget(lbLabel, 2, 0);
+    layout->addWidget(lbLabel, row, 0);
 
     lePassword = new QLineEdit(this);
     lePassword->setEchoMode(QLineEdit::Password);
-    layout->addWidget(lePassword, 2, 1);
+    layout->addWidget(lePassword, row++, 1);
 
     cbSave = new QCheckBox(this);
     cbSave->setText(QCheckBox::tr("Save account name and password"));
-    layout->addWidget(cbSave, 3, 0, 1, 2);
+    layout->addWidget(cbSave, row++, 0, 1, 2);
 
     QFrame * hr = new QFrame(this);
     hr->setFrameStyle(QFrame::HLine);
     hr->setLineWidth(3);
     hr->setFixedHeight(10);
-    layout->addWidget(hr, 4, 0, 1, 2);
+    layout->addWidget(hr, row++, 0, 1, 2);
 
     lbLabel = new QLabel(this);
     lbLabel->setText(QLabel::tr("Video title: "));
-    layout->addWidget(lbLabel, 5, 0);
+    layout->addWidget(lbLabel, row, 0);
 
     leTitle = new QLineEdit(this);
     leTitle->setText(filename);
-    layout->addWidget(leTitle, 5, 1);
+    leTitle->setValidator(new QRegExpValidator(rx));
+    layout->addWidget(leTitle, row++, 1);
 
     lbLabel = new QLabel(this);
     lbLabel->setText(QLabel::tr("Video description: "));
-    layout->addWidget(lbLabel, 6, 0, 1, 2);
+    layout->addWidget(lbLabel, row++, 0, 1, 2);
 
     teDescription = new QPlainTextEdit(this);
-    layout->addWidget(teDescription, 7, 0, 1, 2);
+    layout->addWidget(teDescription, row++, 0, 1, 2);
+
+    lbLabel = new QLabel(this);
+    lbLabel->setText(QLabel::tr("Tags (comma separated): "));
+    layout->addWidget(lbLabel, row, 0);
+
+    leTags = new QLineEdit(this);
+    leTags->setText("hedgewars");
+    leTags->setMaxLength(500);
+    leTags->setValidator(new QRegExpValidator(rx));
+    layout->addWidget(leTags, row++, 1);
+
+    cbPrivate = new QCheckBox(this);
+    cbPrivate->setText(QCheckBox::tr("Video is private"));
+    layout->addWidget(cbPrivate, row++, 0, 1, 2);
 
     hr = new QFrame(this);
         hr->setFrameStyle(QFrame::HLine);
         hr->setLineWidth(3);
         hr->setFixedHeight(10);
-        layout->addWidget(hr, 8, 0, 1, 2);
+        layout->addWidget(hr, row++, 0, 1, 2);
 
     lbLabel = new QLabel(this);
+    lbLabel->setWordWrap(true);
     lbLabel->setText(GoogleNotice);
-    layout->addWidget(lbLabel, 9, 0, 1, 2);
-
-    labelLog = new QLabel(this);
-    layout->addWidget(labelLog, 10, 0, 1, 2);
+    layout->addWidget(lbLabel, row++, 0, 1, 2);
 
     QDialogButtonBox* dbbButtons = new QDialogButtonBox(this);
     btnUpload = dbbButtons->addButton(tr("Upload"), QDialogButtonBox::ActionRole);
     QPushButton * pbCancel = dbbButtons->addButton(QDialogButtonBox::Cancel);
-    layout->addWidget(dbbButtons, 11, 0, 1, 2);
+    layout->addWidget(dbbButtons, row++, 0, 1, 2);
+
+   /* hr = new QFrame(this);
+        hr->setFrameStyle(QFrame::HLine);
+        hr->setLineWidth(3);
+        hr->setFixedHeight(10);
+        layout->addWidget(hr, row++, 0, 1, 2);*/
 
     connect(btnUpload, SIGNAL(clicked()), this, SLOT(upload()));
     connect(pbCancel, SIGNAL(clicked()), this, SLOT(reject()));
 }
 
-void HWUploadVideoDialog::log(const QString& text)
-{
-    labelLog->setText(labelLog->text() + text);
-}
-
 void HWUploadVideoDialog::setEditable(bool editable)
 {
     leTitle->setEnabled(editable);
@@ -142,9 +167,6 @@
 {
     setEditable(false);
 
-    labelLog->clear();
-    log(tr("Authenticating at www.google.com... "));
-
     // Documentation is at https://developers.google.com/youtube/2.0/developers_guide_protocol_clientlogin#ClientLogin_Authentication
     QNetworkRequest request;
     request.setUrl(QUrl("https://www.google.com/accounts/ClientLogin"));
@@ -159,9 +181,12 @@
     connect(reply, SIGNAL(finished()), this, SLOT(authFinished()));
 }
 
-QString XmlEscape(const QString& str)
+static QString XmlEscape(const QString& str)
 {
     QString str2 = str;
+    // youtube doesn't understand this characters, even when they are properly escaped
+    // (either with CDATA or with &lt; &gt;)
+    str2.replace('<', ' ').replace('>', ' ');
     return "<![CDATA[" + str2.replace("]]>", "]]]]><![CDATA[>") + "]]>";
 }
 
@@ -170,6 +195,8 @@
     QNetworkReply *reply = (QNetworkReply*)sender();
     reply->deleteLater();
 
+    int HttpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
     QByteArray answer = reply->readAll();
     QString authToken = "";
     QList<QByteArray> lines = answer.split('\n');
@@ -184,14 +211,15 @@
     }
     if (authToken.isEmpty())
     {
-        log(tr("failed\n"));
-        log(reply->errorString() + "\n");
+        QString errorStr = QMessageBox::tr("Error while authenticating at google.com:\n");
+        if (HttpCode == 403)
+            errorStr += QMessageBox::tr("Login or password is incorrect");
+        else
+            errorStr += reply->errorString();
+        QMessageBox::warning(this, QMessageBox::tr("Error"), errorStr);
         setEditable(true);
         return;
     }
-    log(tr("Ok\n"));
-
-    log(tr("Sending metadata... "));
 
     QByteArray auth = ("GoogleLogin auth=" + authToken).toAscii();
 
@@ -203,13 +231,20 @@
                 "xmlns:media=\"http://search.yahoo.com/mrss/\" "
                 "xmlns:yt=\"http://gdata.youtube.com/schemas/2007\">"
                 "<media:group>"
-                    "<yt:incomplete/>"
+                  //  "<yt:incomplete/>"
                     "<media:category "
                         "scheme=\"http://gdata.youtube.com/schemas/2007/categories.cat\">Games"
                     "</media:category>"
                     "<media:title type=\"plain\">"
                         + XmlEscape(leTitle->text()).toUtf8() +
                     "</media:title>"
+                    "<media:description type=\"plain\">"
+                        + XmlEscape(teDescription->toPlainText()).toUtf8() +
+                    "</media:description>"
+                    "<media:keywords type=\"plain\">"
+                        + XmlEscape(leTags->text()).toUtf8() +
+                    "</media:keywords>"
+                    + (cbPrivate->isChecked()? "<yt:private/>" : "") +
                 "</media:group>"
             "</entry>";
 
@@ -234,12 +269,12 @@
     location = QString::fromAscii(reply->rawHeader("Location"));
     if (location.isEmpty())
     {
-        log(tr("failed\n"));
-        log(reply->errorString() + "\n");
+        QString errorStr = QMessageBox::tr("Error while sending metadata to youtube.com:\n");
+        errorStr += reply->errorString();
+        QMessageBox::warning(this, QMessageBox::tr("Error"), errorStr);
         setEditable(true);
         return;
     }
 
-    log(tr("Ok\n"));
     accept();
 }
--- a/QTfrontend/ui/dialog/upload_video.h	Mon Aug 06 00:40:26 2012 +0400
+++ b/QTfrontend/ui/dialog/upload_video.h	Mon Aug 06 00:44:32 2012 +0400
@@ -39,18 +39,17 @@
 
         QLineEdit* leTitle;
         QPlainTextEdit* teDescription;
+        QLineEdit* leTags;
+        QCheckBox* cbPrivate;
 
         QPushButton* btnUpload;
 
-        QLabel* labelLog;
-
         QString location;
 
     private:
         QNetworkAccessManager* netManager;
         QString filename;
 
-        void log(const QString& text);
         void setEditable(bool editable);
 
     private slots:
--- a/QTfrontend/ui/page/pagevideos.cpp	Mon Aug 06 00:40:26 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.cpp	Mon Aug 06 00:44:32 2012 +0400
@@ -42,6 +42,7 @@
 #include <QNetworkAccessManager>
 #include <QNetworkRequest>
 #include <QNetworkReply>
+#include <QXmlStreamReader>
 
 #include "hwconsts.h"
 #include "pagevideos.h"
@@ -59,11 +60,12 @@
 {
     vcName,
     vcSize,
-    vcProgress,
+    vcProgress, // either encoding or uploading
 
     vcNumColumns,
 };
 
+// this class is used for items in first column in file-table
 class VideoItem : public QTableWidgetItem
 {
     // note: QTableWidgetItem is not Q_OBJECT
@@ -73,9 +75,10 @@
         ~VideoItem();
 
         QString name;
-        QString desc; // description
-        QString uploadReply;
-        HWRecorder * pRecorder; // non NULL if file is being encoded
+        QString prefix; // original filename without extension
+        QString desc;   // description (duration, resolution, etc...)
+        QString uploadUrl; // http://youtu.be/???????
+        HWRecorder    * pRecorder; // non NULL if file is being encoded
         QNetworkReply * pUploading; // non NULL if file is being uploaded
         bool seen; // used when updating directory
         float lastSizeUpdate;
@@ -112,7 +115,7 @@
     // options
     {
         IconedGroupBox* pOptionsGroup = new IconedGroupBox(this);
-        pOptionsGroup->setIcon(QIcon(":/res/Settings.png"));
+        pOptionsGroup->setIcon(QIcon(":/res/Settings.png")); // FIXME
         pOptionsGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
         pOptionsGroup->setTitle(QGroupBox::tr("Video recording options"));
         QGridLayout * pOptLayout = new QGridLayout(pOptionsGroup);
@@ -211,7 +214,7 @@
     // list of videos
     {
         IconedGroupBox* pTableGroup = new IconedGroupBox(this);
-        pTableGroup->setIcon(QIcon(":/res/graphicsicon.png"));
+        pTableGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME
         pTableGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
         pTableGroup->setTitle(QGroupBox::tr("Videos"));
 
@@ -247,7 +250,7 @@
     // description
     {
         IconedGroupBox* pDescGroup = new IconedGroupBox(this);
-        pDescGroup->setIcon(QIcon(":/res/graphicsicon.png"));
+        pDescGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME
         pDescGroup->setTitle(QGroupBox::tr("Description"));
 
         QVBoxLayout* pDescLayout = new QVBoxLayout(pDescGroup);
@@ -271,6 +274,11 @@
         // label with file description
         labelDesc = new QLabel(pDescGroup);
         labelDesc->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+        labelDesc->setTextInteractionFlags(Qt::TextSelectableByMouse |
+                                           Qt::TextSelectableByKeyboard	|
+                                           Qt::LinksAccessibleByMouse |
+                                           Qt::LinksAccessibleByKeyboard);
+        labelDesc->setTextFormat(Qt::RichText);
         pTopDescLayout->addWidget(labelDesc, 1);
 
         // buttons: play and delete
@@ -313,6 +321,7 @@
     connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles()));
     connect(btnToYouTube, SIGNAL(clicked()), this, SLOT(uploadToYouTube()));
     connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory()));
+    connect(labelDesc, SIGNAL(linkActivated(const QString&)), this, SLOT(linkActivated(const QString&)));
  }
 
 PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent),
@@ -435,6 +444,7 @@
     if (iFormat == -1)
         return false;
     comboAVFormats->setCurrentIndex(iFormat);
+    // format was changed, so lists of codecs were automatically updated to codecs supported by this format
 
     // try to find video codec
     int iVCodec = comboVideoCodecs->findData(vcodec);
@@ -453,7 +463,7 @@
 }
 
 // get file size as string
-QString FileSizeStr(const QString & path)
+static QString FileSizeStr(const QString & path)
 {
     quint64 size = QFileInfo(path).size();
 
@@ -478,6 +488,8 @@
     filesTable->item(row, vcSize)->setText(FileSizeStr(path));
 }
 
+// There is a button 'Open videos dir', so it is possible that user will open
+// this dir and rename/delete some files there, so we should handle this.
 void PageVideos::updateFileList(const QString & path)
 {
     // mark all files as non seen
@@ -545,7 +557,7 @@
 void PageVideos::updateProgress(float value)
 {
     HWRecorder * pRecorder = (HWRecorder*)sender();
-    VideoItem * item = (VideoItem*)pRecorder->item;
+    VideoItem * item = pRecorder->item;
     int row = filesTable->row(item);
 
     // update file size every percent
@@ -609,6 +621,7 @@
     QString newName = item->text();
     if (!newName.contains('.')) // user forgot an extension
     {
+        // restore old extension
         int pt = oldName.lastIndexOf('.');
         if (pt != -1)
         {
@@ -676,7 +689,7 @@
 
 void PageVideos::clearThumbnail()
 {
-    // add empty image for proper sizing
+    // add empty (transparent) image for proper sizing
     QPixmap pic(ThumbnailSize);
     pic.fill(QColor(0,0,0,0));
     labelThumbnail->setPixmap(pic);
@@ -687,6 +700,7 @@
     VideoItem * item = nameItem(filesTable->currentRow());
     if (!item)
     {
+        // nothing is selected => clear description and return
         labelDesc->clear();
         clearThumbnail();
         btnPlay->setEnabled(false);
@@ -696,51 +710,75 @@
     }
 
     btnPlay->setEnabled(item->ready());
+    btnToYouTube->setEnabled(item->ready());
     btnDelete->setEnabled(true);
-    btnToYouTube->setEnabled(item->ready());
+    btnDelete->setText(item->ready()? QPushButton::tr("Delete") :  QPushButton::tr("Cancel"));
+    btnToYouTube->setText(item->pUploading? QPushButton::tr("Cancel uploading") :  QPushButton::tr("Upload to YouTube"));
 
+    // construct string with desctiption of this file to display it
     QString desc = item->name + "\n\n";
-    QString thumbName = "";
 
-    if (item->ready())
+    if (!item->ready())
+        desc += tr("(in progress...)");
+    else
     {
         QString path = item->path();
-        desc += tr("Date: ") + QFileInfo(path).created().toString(Qt::DefaultLocaleLongDate) + "\n";
-        desc += tr("Size: ") + FileSizeStr(path) + "\n";
-        if (item->desc == "")
+        desc += tr("Date: ") + QFileInfo(path).created().toString(Qt::DefaultLocaleLongDate) + '\n';
+        desc += tr("Size: ") + FileSizeStr(path) + '\n';
+        if (item->desc.isEmpty())
+        {
+            // Extract description from file;
+            // It will contain duration, resolution, etc and also comment added by hwengine.
             item->desc = LibavIteraction::instance().getFileInfo(path);
-        desc += item->desc;
-
-        // extract thumbnail name fron description
-        int prefixBegin = desc.indexOf("prefix[");
-        int prefixEnd = desc.indexOf("]prefix");
-        if (prefixBegin != -1 && prefixEnd != -1)
-        {
-            QString prefix = desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7));
-            desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin);
-            thumbName = prefix;
-        }
 
-        desc += item->uploadReply;
-    }
-    else
-        desc += tr("(in progress...)");
-
-    if (thumbName.isEmpty())
-    {
-        if (item->ready())
-            thumbName = item->name;
-        else
-            thumbName = item->pRecorder->name;
-        // remove extension
-        int pt = thumbName.lastIndexOf('.');
-        if (pt != -1)
-            thumbName.truncate(pt);
+            // extract prefix (original name) from description (it is enclosed in prefix[???]prefix)
+            int prefixBegin = item->desc.indexOf("prefix[");
+            int prefixEnd   = item->desc.indexOf("]prefix");
+            if (prefixBegin != -1 && prefixEnd != -1)
+            {
+                item->prefix = desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7));
+                item->desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin);
+            }
+        }
+        desc += item->desc + '\n';
     }
 
-    if (!thumbName.isEmpty())
+    if (item->prefix.isEmpty())
+    {
+        // try to extract prefix from file name instead
+        if (item->ready())
+            item->prefix = item->name;
+        else
+            item->prefix = item->pRecorder->name;
+
+        // remove extension
+        int pt = item->prefix.lastIndexOf('.');
+        if (pt != -1)
+            item->prefix.truncate(pt);
+    }
+
+    if (item->ready() && item->uploadUrl.isEmpty())
     {
-        thumbName = cfgdir->absoluteFilePath("VideoTemp/" + thumbName);
+        // try to load url from file
+        QFile * file = new QFile(cfgdir->absoluteFilePath("VideoTemp/" + item->prefix + "-url.txt"), this);
+        if (!file->open(QIODevice::ReadOnly))
+            item->uploadUrl = "no";
+        else
+        {
+            QByteArray data = file->readAll();
+            file->close();
+            item->uploadUrl = QString::fromUtf8(data.data());
+        }
+    }
+    if (item->uploadUrl != "no")
+        desc += QString("<a href=\"%1\">%1</a>").arg(item->uploadUrl);
+    desc.replace("\n", "<br/>");
+
+    labelDesc->setText(desc);
+
+    if (!item->prefix.isEmpty())
+    {
+        QString thumbName = cfgdir->absoluteFilePath("VideoTemp/" + item->prefix);
         QPixmap pic;
         if (pic.load(thumbName + ".png") || pic.load(thumbName + ".bmp"))
         {
@@ -753,7 +791,6 @@
         else
             clearThumbnail();
     }
-    labelDesc->setText(desc);
 }
 
 // user selected another cell, so we should change description
@@ -766,10 +803,15 @@
 void PageVideos::play(int row)
 {
     VideoItem * item = nameItem(row);
-    if (item->ready())
+    if (item && item->ready())
         QDesktopServices::openUrl(QUrl("file:///" + QDir::toNativeSeparators(item->path())));
 }
 
+void PageVideos::linkActivated(const QString & link)
+{
+    QDesktopServices::openUrl(QUrl(link));
+}
+
 void PageVideos::playSelectedFile()
 {
     int index = filesTable->currentRow();
@@ -779,6 +821,30 @@
 
 void PageVideos::deleteSelectedFiles()
 {
+    int index = filesTable->currentRow();
+    if (index == -1)
+        return;
+
+    VideoItem * item = nameItem(index);
+    if (!item)
+        return;
+
+    // ask user if (s)he is serious
+    if (QMessageBox::question(this,
+                              tr("Are you sure?"),
+                              tr("Do you really want do remove %1?").arg(item->name),
+                              QMessageBox::Yes | QMessageBox::No)
+            != QMessageBox::Yes)
+        return;
+
+    // remove
+    if (!item->ready())
+        item->pRecorder->deleteLater();
+    else
+        cfgdir->remove("Videos/" + item->name);
+
+// this code is for removing several files when multiple selection is enabled
+#if 0
     QList<QTableWidgetItem*> items = filesTable->selectedItems();
     int num = items.size() / vcNumColumns;
     if (num == 0)
@@ -803,6 +869,7 @@
         else
             cfgdir->remove("Videos/" + item->name);
     }
+#endif
 }
 
 void PageVideos::keyPressEvent(QKeyEvent * pEvent)
@@ -829,14 +896,14 @@
     QDesktopServices::openUrl(QUrl("file:///" + path));
 }
 
-// clear VideoTemp directory (except for thumbnails)
+// clear VideoTemp directory (except for thumbnails and upload links)
 void PageVideos::clearTemp()
 {
     QDir temp(cfgdir->absolutePath() + "/VideoTemp");
     QStringList files = temp.entryList(QDir::Files);
     foreach (const QString& file, files)
     {
-        if (!file.endsWith(".bmp") && !file.endsWith(".png"))
+        if (!file.endsWith(".bmp") && !file.endsWith(".png") && !file.endsWith("-url.txt"))
             temp.remove(file);
     }
 }
@@ -912,14 +979,11 @@
     }
 }
 
-void PageVideos::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
+VideoItem * PageVideos::itemFromReply(QNetworkReply* reply, int & row)
 {
-    QNetworkReply* reply = (QNetworkReply*)sender();
-
     VideoItem * item = NULL;
-    int row;
     int count = filesTable->rowCount();
-    // find corresponding item (maybe there is a better wat to implement this?)
+    // find corresponding item (maybe there is a better way to implement this?)
     for (int i = 0; i < count; i++)
     {
         item = nameItem(i);
@@ -929,48 +993,125 @@
             break;
         }
     }
-    Q_ASSERT(item);
+    return item;
+}
 
+void PageVideos::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
+{
+    QNetworkReply* reply = (QNetworkReply*)sender();
+    int row;
+    VideoItem * item = itemFromReply(reply, row);
     setProgress(row, item, bytesSent*1.0/bytesTotal);
 }
 
 void PageVideos::uploadFinished()
 {
     QNetworkReply* reply = (QNetworkReply*)sender();
+    reply->deleteLater();
 
-    VideoItem * item = NULL;
     int row;
-    int count = filesTable->rowCount();
-    for (int i = 0; i < count; i++)
+    VideoItem * item = itemFromReply(reply, row);
+    if (!item)
+        return;
+
+    item->pUploading = NULL;
+
+    // extract video id from reply
+    QString videoid;
+    QXmlStreamReader xml(reply);
+    while (!xml.atEnd())
     {
-        item = nameItem(i);
-        if (item->pUploading == reply)
+        xml.readNext();
+        if (xml.qualifiedName() == "yt:videoid")
         {
-            row = i;
+            videoid = xml.readElementText();
             break;
         }
     }
-    Q_ASSERT(item);
+
+    if (!videoid.isEmpty())
+    {
+        item->uploadUrl = "http://youtu.be/" + videoid;
+        updateDescription();
 
-    item->pUploading = NULL;
-    QByteArray answer = reply->readAll();
-    item->uploadReply = QString::fromUtf8(answer.data());
-   // QMessageBox::information(this,"",item->uploadReply,0);
+        // save url in file
+        QFile * file = new QFile(cfgdir->absoluteFilePath("VideoTemp/" + item->prefix + "-url.txt"), this);
+        if (file->open(QIODevice::WriteOnly))
+        {
+            file->write(item->uploadUrl.toUtf8());
+            file->close();
+        }
+    }
+
     filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar
     numUploads--;
 }
 
+// this will protect saved youtube password from those who cannot read source code
+static QString protectPass(QString str)
+{
+    QByteArray array = str.toUtf8();
+    for (int i = 0; i < array.size(); i++)
+        array[i] = array[i] ^ 0xC4 ^ i;
+    array = array.toBase64();
+    return QString::fromAscii(array.data());
+}
+
+static QString unprotectPass(QString str)
+{
+    QByteArray array = QByteArray::fromBase64(str.toAscii());
+    for (int i = 0; i < array.size(); i++)
+        array[i] = array[i] ^ 0xC4 ^ i;
+    return QString::fromUtf8(array);
+}
+
 void PageVideos::uploadToYouTube()
 {
     int row = filesTable->currentRow();
     VideoItem * item = nameItem(row);
 
+    if (item->pUploading)
+    {
+        if (QMessageBox::question(this,
+                                  tr("Are you sure?"),
+                                  tr("Do you really want do cancel uploading %1?").arg(item->name),
+                                  QMessageBox::Yes | QMessageBox::No)
+                != QMessageBox::Yes)
+            return;
+        item->pUploading->deleteLater();
+        filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar
+        numUploads--;
+        return;
+    }
+
     if (!netManager)
         netManager = new QNetworkAccessManager(this);
 
     HWUploadVideoDialog* dlg = new HWUploadVideoDialog(this, item->name, netManager);
     dlg->deleteLater();
-    if (!dlg->exec())
+    if (config->value("youtube/save").toBool())
+    {
+        dlg->cbSave->setChecked(true);
+        dlg->leAccount->setText(config->value("youtube/name").toString());
+        dlg->lePassword->setText(unprotectPass(config->value("youtube/pswd").toString()));
+    }
+
+    bool result = dlg->exec();
+
+    if (dlg->cbSave->isChecked())
+    {
+        config->setValue("youtube/save", true);
+        config->setValue("youtube/name", dlg->leAccount->text());
+        config->setValue("youtube/pswd", protectPass(dlg->lePassword->text()));
+    }
+    else
+    {
+        config->setValue("youtube/save", false);
+        config->setValue("youtube/name", "");
+        config->setValue("youtube/pswd", "");
+    }
+
+    if (!result)
         return;
 
     QNetworkRequest request(QUrl(dlg->location));
@@ -985,7 +1126,7 @@
     progressBar->setMinimum(0);
     progressBar->setMaximum(10000);
     progressBar->setValue(0);
-    // make it different from encoding progress-bar
+    // make it different from progress-bar used during encoding (use blue color)
     progressBar->setStyleSheet("* {color: #00ccff; selection-background-color: #00ccff;}" );
     filesTable->setCellWidget(row, vcProgress, progressBar);
 
@@ -994,4 +1135,6 @@
     connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
     connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished()));
     numUploads++;
+
+    updateDescription();
 }
--- a/QTfrontend/ui/page/pagevideos.h	Mon Aug 06 00:40:26 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.h	Mon Aug 06 00:44:32 2012 +0400
@@ -23,6 +23,7 @@
 #include "AbstractPage.h"
 
 class QNetworkAccessManager;
+class QNetworkReply;
 class GameUIConfig;
 class HWRecorder;
 class VideoItem;
@@ -76,6 +77,7 @@
         void clearTemp();
         void clearThumbnail();
         void setProgress(int row, VideoItem* item, float value);
+        VideoItem * itemFromReply(QNetworkReply* reply, int & row);
 
         GameUIConfig * config;
         QNetworkAccessManager* netManager;
@@ -118,6 +120,7 @@
         void uploadToYouTube();
         void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
         void uploadFinished();
+        void linkActivated(const QString & link);
 };
 
 #endif // PAGE_VIDEOS_H