# HG changeset patch # User sheepluva # Date 1346352149 -7200 # Node ID 01b599d6f72d234d721d9e9dea4c71c3a99670b0 # Parent 13fa53bb3134c2369d5134a470467194d8c9afe5 dos2unix newline fixes on 7 (video recording-)merged files diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/dialog/ask_quit.cpp --- a/QTfrontend/ui/dialog/ask_quit.cpp Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/dialog/ask_quit.cpp Thu Aug 30 20:42:29 2012 +0200 @@ -1,79 +1,79 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#include -#include -#include -#include -#include - -#include "hwform.h" -#include "ask_quit.h" -#include "pagevideos.h" - -HWAskQuitDialog::HWAskQuitDialog(QWidget* parent, HWForm * form) : QDialog(parent) -{ - this->form = form; - - setWindowTitle(tr("Do yot really want to quit?")); - - QVBoxLayout * layout = new QVBoxLayout(this); - - QLabel * lbLabel = new QLabel(this); - lbLabel->setText(QLabel::tr("There are videos that are currently being processed.\n" - "Exiting now will abort them.\n" - "Do yot really want to quit?")); - layout->addWidget(lbLabel); - - lbList = new QLabel(this); - layout->addWidget(lbList); - updateList(); - - QDialogButtonBox* dbbButtons = new QDialogButtonBox(this); - QPushButton * pbYes = dbbButtons->addButton(QDialogButtonBox::Yes); - QPushButton * pbNo = dbbButtons->addButton(QDialogButtonBox::No); - QPushButton * pbMore = dbbButtons->addButton(QPushButton::tr("More info"), QDialogButtonBox::HelpRole); - layout->addWidget(dbbButtons); - - connect(pbYes, SIGNAL(clicked()), this, SLOT(accept())); - connect(pbNo, SIGNAL(clicked()), this, SLOT(reject())); - connect(pbMore, SIGNAL(clicked()), this, SLOT(goToPageVideos())); - - // update list periodically - QTimer * timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(updateList())); - timer->start(200); -} - -void HWAskQuitDialog::goToPageVideos() -{ - reject(); - form->GoToVideos(); -} - -void HWAskQuitDialog::updateList() -{ - QString text = form->ui.pageVideos->getVideosInProgress(); - if (text.isEmpty()) - { - // automatically exit when everything is finished - accept(); - return; - } - lbList->setText(text); -} +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include + +#include "hwform.h" +#include "ask_quit.h" +#include "pagevideos.h" + +HWAskQuitDialog::HWAskQuitDialog(QWidget* parent, HWForm * form) : QDialog(parent) +{ + this->form = form; + + setWindowTitle(tr("Do yot really want to quit?")); + + QVBoxLayout * layout = new QVBoxLayout(this); + + QLabel * lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("There are videos that are currently being processed.\n" + "Exiting now will abort them.\n" + "Do yot really want to quit?")); + layout->addWidget(lbLabel); + + lbList = new QLabel(this); + layout->addWidget(lbList); + updateList(); + + QDialogButtonBox* dbbButtons = new QDialogButtonBox(this); + QPushButton * pbYes = dbbButtons->addButton(QDialogButtonBox::Yes); + QPushButton * pbNo = dbbButtons->addButton(QDialogButtonBox::No); + QPushButton * pbMore = dbbButtons->addButton(QPushButton::tr("More info"), QDialogButtonBox::HelpRole); + layout->addWidget(dbbButtons); + + connect(pbYes, SIGNAL(clicked()), this, SLOT(accept())); + connect(pbNo, SIGNAL(clicked()), this, SLOT(reject())); + connect(pbMore, SIGNAL(clicked()), this, SLOT(goToPageVideos())); + + // update list periodically + QTimer * timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(updateList())); + timer->start(200); +} + +void HWAskQuitDialog::goToPageVideos() +{ + reject(); + form->GoToVideos(); +} + +void HWAskQuitDialog::updateList() +{ + QString text = form->ui.pageVideos->getVideosInProgress(); + if (text.isEmpty()) + { + // automatically exit when everything is finished + accept(); + return; + } + lbList->setText(text); +} diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/dialog/ask_quit.h --- a/QTfrontend/ui/dialog/ask_quit.h Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/dialog/ask_quit.h Thu Aug 30 20:42:29 2012 +0200 @@ -1,45 +1,45 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#ifndef ASK_QUIT_H -#define ASK_QUIT_H - -#include - -class QLabel; -class HWForm; -class PageVideos; - -class HWAskQuitDialog : public QDialog -{ - Q_OBJECT - - public: - HWAskQuitDialog(QWidget* parent, HWForm *form); - - private slots: - void goToPageVideos(); - void updateList(); - - private: - HWForm * form; - QLabel * lbList; -}; - - -#endif // INPUT_PASSWORD_H +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef ASK_QUIT_H +#define ASK_QUIT_H + +#include + +class QLabel; +class HWForm; +class PageVideos; + +class HWAskQuitDialog : public QDialog +{ + Q_OBJECT + + public: + HWAskQuitDialog(QWidget* parent, HWForm *form); + + private slots: + void goToPageVideos(); + void updateList(); + + private: + HWForm * form; + QLabel * lbList; +}; + + +#endif // INPUT_PASSWORD_H diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/dialog/upload_video.cpp --- a/QTfrontend/ui/dialog/upload_video.cpp Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/dialog/upload_video.cpp Thu Aug 30 20:42:29 2012 +0200 @@ -1,297 +1,297 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "upload_video.h" -#include "hwconsts.h" - -// User-agent string used in http requests. -// Don't make it a global varibale - crash on linux because of cVersionString -#define USER_AGENT ("Hedgewars-QtFrontend/" + *cVersionString).toAscii() - -// This is developer key obtained from http://code.google.com/apis/youtube/dashboard/ -// If you are reusing this code outside Hedgewars, don't use this developer key, -// obtain you own at http://code.google.com/apis/youtube/dashboard/ -static const QByteArray devKey = "AI39si5pKjxR0XgNIlmrEFF-LyYD31rps4g2O5dZTxLgD0fvJ2rHxrMrNFY8FYTZrzeI3VlaFVQLKfFnSBugvdZmy8vFzRDefQ"; - -HWUploadVideoDialog::HWUploadVideoDialog(QWidget* parent, const QString &filename, QNetworkAccessManager* netManager) : QDialog(parent) -{ - this->filename = filename; - this->netManager = netManager; - - setWindowTitle(tr("Upload video")); - - // 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 " - "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 < or >) - QRegExp rx("[^<>]*"); - - int row = 0; - - QGridLayout * layout = new QGridLayout(this); - layout->setColumnStretch(0, 1); - layout->setColumnStretch(1, 2); - - QLabel * lbLabel = new QLabel(this); - lbLabel->setWordWrap(true); - lbLabel->setText(QLabel::tr( - "Please provide either the YouTube account name " - "or the email address associated with the Google Account.")); - layout->addWidget(lbLabel, row++, 0, 1, 2); - - lbLabel = new QLabel(this); - lbLabel->setText(QLabel::tr("Account name (or email): ")); - layout->addWidget(lbLabel, row, 0); - - leAccount = new QLineEdit(this); - layout->addWidget(leAccount, row++, 1); - - lbLabel = new QLabel(this); - lbLabel->setText(QLabel::tr("Password: ")); - layout->addWidget(lbLabel, row, 0); - - lePassword = new QLineEdit(this); - lePassword->setEchoMode(QLineEdit::Password); - layout->addWidget(lePassword, row++, 1); - - cbSave = new QCheckBox(this); - cbSave->setText(QCheckBox::tr("Save account name and password")); - 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, row++, 0, 1, 2); - - lbLabel = new QLabel(this); - lbLabel->setText(QLabel::tr("Video title: ")); - layout->addWidget(lbLabel, row, 0); - - leTitle = new QLineEdit(this); - leTitle->setText(filename); - leTitle->setValidator(new QRegExpValidator(rx, leTitle)); - layout->addWidget(leTitle, row++, 1); - - lbLabel = new QLabel(this); - lbLabel->setText(QLabel::tr("Video description: ")); - layout->addWidget(lbLabel, row++, 0, 1, 2); - - teDescription = new QPlainTextEdit(this); - 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, leTags)); - 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, row++, 0, 1, 2); - - lbLabel = new QLabel(this); - lbLabel->setWordWrap(true); - lbLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); - lbLabel->setTextFormat(Qt::RichText); - lbLabel->setOpenExternalLinks(true); - lbLabel->setText(GoogleNotice); - 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, 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::showEvent(QShowEvent * event) -{ - QDialog::showEvent(event); - - // set width to the same value as height (otherwise dialog has too small width) - QSize s = size(); - QPoint p = pos(); - resize(s.height(), s.height()); - move(p.x() - (s.height() - s.width())/2, p.y()); -} - -void HWUploadVideoDialog::setEditable(bool editable) -{ - leTitle->setEnabled(editable); - leAccount->setEnabled(editable); - lePassword->setEnabled(editable); - btnUpload->setEnabled(editable); -} - -void HWUploadVideoDialog::upload() -{ - setEditable(false); - - // 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")); - request.setRawHeader("User-Agent", USER_AGENT); - request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); - - QString account(QUrl::toPercentEncoding(leAccount->text())); - QString pass(QUrl::toPercentEncoding(lePassword->text())); - QByteArray data = QString("Email=%1&Passwd=%2&service=youtube&source=Hedgewars").arg(account).arg(pass).toAscii(); - - QNetworkReply *reply = netManager->post(request, data); - connect(reply, SIGNAL(finished()), this, SLOT(authFinished())); -} - -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 < >) - str2.replace('<', ' ').replace('>', ' '); - return "", "]]]]>") + "]]>"; -} - -void HWUploadVideoDialog::authFinished() -{ - QNetworkReply *reply = (QNetworkReply*)sender(); - reply->deleteLater(); - - int HttpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - QByteArray answer = reply->readAll(); - QString authToken = ""; - QList lines = answer.split('\n'); - foreach (const QByteArray& line, lines) - { - QString str(line); - if (!str.startsWith("Auth=", Qt::CaseInsensitive)) - continue; - str.remove(0, 5); - authToken = str; - break; - } - if (authToken.isEmpty()) - { - 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; - } - - QByteArray auth = ("GoogleLogin auth=" + authToken).toAscii(); - - // We have authenticated, now we can send metadata and start upload - // Documentation is here: https://developers.google.com/youtube/2.0/developers_guide_protocol_resumable_uploads#Resumable_uploads - QByteArray body = - "" - "" - "" - // "" - "Games" - "" - "" - + XmlEscape(leTitle->text()).toUtf8() + - "" - "" - + XmlEscape(teDescription->toPlainText()).toUtf8() + - "" - "" - + XmlEscape(leTags->text()).toUtf8() + - "" - + (cbPrivate->isChecked()? "" : "") + - "" - ""; - - QNetworkRequest request; - request.setUrl(QUrl("http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads")); - request.setRawHeader("User-Agent", USER_AGENT); - request.setRawHeader("Authorization", auth); - request.setRawHeader("GData-Version", "2"); - request.setRawHeader("X-GData-Key", "key=" + devKey); - request.setRawHeader("Slug", filename.toUtf8()); - request.setRawHeader("Content-Type", "application/atom+xml; charset=UTF-8"); - - reply = netManager->post(request, body); - connect(reply, SIGNAL(finished()), this, SLOT(startUpload())); -} - -void HWUploadVideoDialog::startUpload() -{ - QNetworkReply *reply = (QNetworkReply*)sender(); - reply->deleteLater(); - - location = QString::fromAscii(reply->rawHeader("Location")); - if (location.isEmpty()) - { - 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; - } - - accept(); -} +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "upload_video.h" +#include "hwconsts.h" + +// User-agent string used in http requests. +// Don't make it a global varibale - crash on linux because of cVersionString +#define USER_AGENT ("Hedgewars-QtFrontend/" + *cVersionString).toAscii() + +// This is developer key obtained from http://code.google.com/apis/youtube/dashboard/ +// If you are reusing this code outside Hedgewars, don't use this developer key, +// obtain you own at http://code.google.com/apis/youtube/dashboard/ +static const QByteArray devKey = "AI39si5pKjxR0XgNIlmrEFF-LyYD31rps4g2O5dZTxLgD0fvJ2rHxrMrNFY8FYTZrzeI3VlaFVQLKfFnSBugvdZmy8vFzRDefQ"; + +HWUploadVideoDialog::HWUploadVideoDialog(QWidget* parent, const QString &filename, QNetworkAccessManager* netManager) : QDialog(parent) +{ + this->filename = filename; + this->netManager = netManager; + + setWindowTitle(tr("Upload video")); + + // 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 " + "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 < or >) + QRegExp rx("[^<>]*"); + + int row = 0; + + QGridLayout * layout = new QGridLayout(this); + layout->setColumnStretch(0, 1); + layout->setColumnStretch(1, 2); + + QLabel * lbLabel = new QLabel(this); + lbLabel->setWordWrap(true); + lbLabel->setText(QLabel::tr( + "Please provide either the YouTube account name " + "or the email address associated with the Google Account.")); + layout->addWidget(lbLabel, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Account name (or email): ")); + layout->addWidget(lbLabel, row, 0); + + leAccount = new QLineEdit(this); + layout->addWidget(leAccount, row++, 1); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Password: ")); + layout->addWidget(lbLabel, row, 0); + + lePassword = new QLineEdit(this); + lePassword->setEchoMode(QLineEdit::Password); + layout->addWidget(lePassword, row++, 1); + + cbSave = new QCheckBox(this); + cbSave->setText(QCheckBox::tr("Save account name and password")); + 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, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Video title: ")); + layout->addWidget(lbLabel, row, 0); + + leTitle = new QLineEdit(this); + leTitle->setText(filename); + leTitle->setValidator(new QRegExpValidator(rx, leTitle)); + layout->addWidget(leTitle, row++, 1); + + lbLabel = new QLabel(this); + lbLabel->setText(QLabel::tr("Video description: ")); + layout->addWidget(lbLabel, row++, 0, 1, 2); + + teDescription = new QPlainTextEdit(this); + 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, leTags)); + 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, row++, 0, 1, 2); + + lbLabel = new QLabel(this); + lbLabel->setWordWrap(true); + lbLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + lbLabel->setTextFormat(Qt::RichText); + lbLabel->setOpenExternalLinks(true); + lbLabel->setText(GoogleNotice); + 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, 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::showEvent(QShowEvent * event) +{ + QDialog::showEvent(event); + + // set width to the same value as height (otherwise dialog has too small width) + QSize s = size(); + QPoint p = pos(); + resize(s.height(), s.height()); + move(p.x() - (s.height() - s.width())/2, p.y()); +} + +void HWUploadVideoDialog::setEditable(bool editable) +{ + leTitle->setEnabled(editable); + leAccount->setEnabled(editable); + lePassword->setEnabled(editable); + btnUpload->setEnabled(editable); +} + +void HWUploadVideoDialog::upload() +{ + setEditable(false); + + // 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")); + request.setRawHeader("User-Agent", USER_AGENT); + request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); + + QString account(QUrl::toPercentEncoding(leAccount->text())); + QString pass(QUrl::toPercentEncoding(lePassword->text())); + QByteArray data = QString("Email=%1&Passwd=%2&service=youtube&source=Hedgewars").arg(account).arg(pass).toAscii(); + + QNetworkReply *reply = netManager->post(request, data); + connect(reply, SIGNAL(finished()), this, SLOT(authFinished())); +} + +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 < >) + str2.replace('<', ' ').replace('>', ' '); + return "", "]]]]>") + "]]>"; +} + +void HWUploadVideoDialog::authFinished() +{ + QNetworkReply *reply = (QNetworkReply*)sender(); + reply->deleteLater(); + + int HttpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QByteArray answer = reply->readAll(); + QString authToken = ""; + QList lines = answer.split('\n'); + foreach (const QByteArray& line, lines) + { + QString str(line); + if (!str.startsWith("Auth=", Qt::CaseInsensitive)) + continue; + str.remove(0, 5); + authToken = str; + break; + } + if (authToken.isEmpty()) + { + 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; + } + + QByteArray auth = ("GoogleLogin auth=" + authToken).toAscii(); + + // We have authenticated, now we can send metadata and start upload + // Documentation is here: https://developers.google.com/youtube/2.0/developers_guide_protocol_resumable_uploads#Resumable_uploads + QByteArray body = + "" + "" + "" + // "" + "Games" + "" + "" + + XmlEscape(leTitle->text()).toUtf8() + + "" + "" + + XmlEscape(teDescription->toPlainText()).toUtf8() + + "" + "" + + XmlEscape(leTags->text()).toUtf8() + + "" + + (cbPrivate->isChecked()? "" : "") + + "" + ""; + + QNetworkRequest request; + request.setUrl(QUrl("http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads")); + request.setRawHeader("User-Agent", USER_AGENT); + request.setRawHeader("Authorization", auth); + request.setRawHeader("GData-Version", "2"); + request.setRawHeader("X-GData-Key", "key=" + devKey); + request.setRawHeader("Slug", filename.toUtf8()); + request.setRawHeader("Content-Type", "application/atom+xml; charset=UTF-8"); + + reply = netManager->post(request, body); + connect(reply, SIGNAL(finished()), this, SLOT(startUpload())); +} + +void HWUploadVideoDialog::startUpload() +{ + QNetworkReply *reply = (QNetworkReply*)sender(); + reply->deleteLater(); + + location = QString::fromAscii(reply->rawHeader("Location")); + if (location.isEmpty()) + { + 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; + } + + accept(); +} diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/dialog/upload_video.h --- a/QTfrontend/ui/dialog/upload_video.h Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/dialog/upload_video.h Thu Aug 30 20:42:29 2012 +0200 @@ -1,65 +1,65 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#ifndef UPLOAD_VIDEO_H -#define UPLOAD_VIDEO_H - -#include - -class QLineEdit; -class QCheckBox; -class QPlainTextEdit; -class QLabel; -class QNetworkAccessManager; - -class HWUploadVideoDialog : public QDialog -{ - Q_OBJECT - public: - HWUploadVideoDialog(QWidget* parent, const QString& filename, QNetworkAccessManager* netManager); - - QLineEdit* leAccount; - QLineEdit* lePassword; - QCheckBox* cbSave; - - QLineEdit* leTitle; - QPlainTextEdit* teDescription; - QLineEdit* leTags; - QCheckBox* cbPrivate; - - QPushButton* btnUpload; - - QString location; - - private: - QNetworkAccessManager* netManager; - QString filename; - - void setEditable(bool editable); - - protected: - // virtual from QWidget - void showEvent(QShowEvent * event); - - private slots: - void upload(); - void authFinished(); - void startUpload(); -}; - -#endif // UPLOAD_VIDEO_H +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef UPLOAD_VIDEO_H +#define UPLOAD_VIDEO_H + +#include + +class QLineEdit; +class QCheckBox; +class QPlainTextEdit; +class QLabel; +class QNetworkAccessManager; + +class HWUploadVideoDialog : public QDialog +{ + Q_OBJECT + public: + HWUploadVideoDialog(QWidget* parent, const QString& filename, QNetworkAccessManager* netManager); + + QLineEdit* leAccount; + QLineEdit* lePassword; + QCheckBox* cbSave; + + QLineEdit* leTitle; + QPlainTextEdit* teDescription; + QLineEdit* leTags; + QCheckBox* cbPrivate; + + QPushButton* btnUpload; + + QString location; + + private: + QNetworkAccessManager* netManager; + QString filename; + + void setEditable(bool editable); + + protected: + // virtual from QWidget + void showEvent(QShowEvent * event); + + private slots: + void upload(); + void authFinished(); + void startUpload(); +}; + +#endif // UPLOAD_VIDEO_H diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/page/pagevideos.cpp --- a/QTfrontend/ui/page/pagevideos.cpp Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/page/pagevideos.cpp Thu Aug 30 20:42:29 2012 +0200 @@ -1,1136 +1,1136 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hwconsts.h" -#include "pagevideos.h" -#include "igbox.h" -#include "libav_iteraction.h" -#include "gameuiconfig.h" -#include "recorder.h" -#include "ask_quit.h" -#include "upload_video.h" - -static const QSize ThumbnailSize(350, 350*3/5); - -// columns in table with list of video files -enum VideosColumns -{ - vcName, - vcSize, - 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 - - public: - VideoItem(const QString& name); - ~VideoItem(); - - QString name; - 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; - float progress; - - bool ready() - { return !pRecorder; } - - QString path() - { return cfgdir->absoluteFilePath("Videos/" + name); } -}; - -VideoItem::VideoItem(const QString& name) - : QTableWidgetItem(name, UserType) -{ - this->name = name; - pRecorder = NULL; - pUploading = NULL; - lastSizeUpdate = 0; - progress = 0; -} - -VideoItem::~VideoItem() -{} - -QLayout * PageVideos::bodyLayoutDefinition() -{ - QGridLayout * pPageLayout = new QGridLayout(); - pPageLayout->setColumnStretch(0, 1); - pPageLayout->setColumnStretch(1, 2); - pPageLayout->setRowStretch(0, 1); - pPageLayout->setRowStretch(1, 1); - - // options - { - IconedGroupBox* pOptionsGroup = new IconedGroupBox(this); - 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); - - // label for format - QLabel *labelFormat = new QLabel(pOptionsGroup); - labelFormat->setText(QLabel::tr("Format")); - pOptLayout->addWidget(labelFormat, 0, 0); - - // list of supported formats - comboAVFormats = new QComboBox(pOptionsGroup); - pOptLayout->addWidget(comboAVFormats, 0, 1, 1, 4); - LibavIteraction::instance().fillFormats(comboAVFormats); - - // separator - QFrame * hr = new QFrame(pOptionsGroup); - hr->setFrameStyle(QFrame::HLine); - hr->setLineWidth(3); - hr->setFixedHeight(10); - pOptLayout->addWidget(hr, 1, 0, 1, 5); - - // label for audio codec - QLabel *labelACodec = new QLabel(pOptionsGroup); - labelACodec->setText(QLabel::tr("Audio codec")); - pOptLayout->addWidget(labelACodec, 2, 0); - - // list of supported audio codecs - comboAudioCodecs = new QComboBox(pOptionsGroup); - pOptLayout->addWidget(comboAudioCodecs, 2, 1, 1, 3); - - // checkbox 'record audio' - checkRecordAudio = new QCheckBox(pOptionsGroup); - checkRecordAudio->setText(QCheckBox::tr("Record audio")); - pOptLayout->addWidget(checkRecordAudio, 2, 4); - - // separator - hr = new QFrame(pOptionsGroup); - hr->setFrameStyle(QFrame::HLine); - hr->setLineWidth(3); - hr->setFixedHeight(10); - pOptLayout->addWidget(hr, 3, 0, 1, 5); - - // label for video codec - QLabel *labelVCodec = new QLabel(pOptionsGroup); - labelVCodec->setText(QLabel::tr("Video codec")); - pOptLayout->addWidget(labelVCodec, 4, 0); - - // list of supported video codecs - comboVideoCodecs = new QComboBox(pOptionsGroup); - pOptLayout->addWidget(comboVideoCodecs, 4, 1, 1, 4); - - // label for resolution - QLabel *labelRes = new QLabel(pOptionsGroup); - labelRes->setText(QLabel::tr("Resolution")); - pOptLayout->addWidget(labelRes, 5, 0); - - // width - widthEdit = new QLineEdit(pOptionsGroup); - widthEdit->setValidator(new QIntValidator(this)); - pOptLayout->addWidget(widthEdit, 5, 1); - - // x - QLabel *labelX = new QLabel(pOptionsGroup); - labelX->setText("X"); - pOptLayout->addWidget(labelX, 5, 2); - - // height - heightEdit = new QLineEdit(pOptionsGroup); - heightEdit->setValidator(new QIntValidator(pOptionsGroup)); - pOptLayout->addWidget(heightEdit, 5, 3); - - // checkbox 'use game resolution' - checkUseGameRes = new QCheckBox(pOptionsGroup); - checkUseGameRes->setText(QCheckBox::tr("Use game resolution")); - pOptLayout->addWidget(checkUseGameRes, 5, 4); - - // label for framerate - QLabel *labelFramerate = new QLabel(pOptionsGroup); - labelFramerate->setText(QLabel::tr("Framerate")); - pOptLayout->addWidget(labelFramerate, 6, 0); - - // framerate - framerateBox = new QSpinBox(pOptionsGroup); - framerateBox->setRange(1, 200); - framerateBox->setSingleStep(1); - pOptLayout->addWidget(framerateBox, 6, 1); - - // button 'set default options' - btnDefaults = new QPushButton(pOptionsGroup); - btnDefaults->setText(QPushButton::tr("Set default options")); - pOptLayout->addWidget(btnDefaults, 7, 0, 1, 5); - - pPageLayout->addWidget(pOptionsGroup, 1, 0); - } - - // list of videos - { - IconedGroupBox* pTableGroup = new IconedGroupBox(this); - pTableGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME - pTableGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - pTableGroup->setTitle(QGroupBox::tr("Videos")); - - QStringList columns; - columns << tr("Name"); - columns << tr("Size"); - columns << ""; - - filesTable = new QTableWidget(pTableGroup); - filesTable->setColumnCount(vcNumColumns); - filesTable->setHorizontalHeaderLabels(columns); - filesTable->setSelectionBehavior(QAbstractItemView::SelectRows); - filesTable->setSelectionMode(QAbstractItemView::SingleSelection); - filesTable->setEditTriggers(QAbstractItemView::SelectedClicked); - filesTable->verticalHeader()->hide(); - filesTable->setMinimumWidth(400); - - QHeaderView * header = filesTable->horizontalHeader(); - header->setResizeMode(vcName, QHeaderView::ResizeToContents); - header->setResizeMode(vcSize, QHeaderView::Fixed); - header->resizeSection(vcSize, 100); - header->setStretchLastSection(true); - - btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup); - - QVBoxLayout *box = new QVBoxLayout(pTableGroup); - box->addWidget(filesTable); - box->addWidget(btnOpenDir); - - pPageLayout->addWidget(pTableGroup, 0, 1, 2, 1); - } - - // description - { - IconedGroupBox* pDescGroup = new IconedGroupBox(this); - pDescGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME - pDescGroup->setTitle(QGroupBox::tr("Description")); - - QVBoxLayout* pDescLayout = new QVBoxLayout(pDescGroup); - QHBoxLayout* pTopDescLayout = new QHBoxLayout(0); // picture and text - QHBoxLayout* pBottomDescLayout = new QHBoxLayout(0); // buttons - - // label with thumbnail picture - labelThumbnail = new QLabel(pDescGroup); - labelThumbnail->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - labelThumbnail->setMaximumSize(ThumbnailSize); - labelThumbnail->setStyleSheet( - "QFrame {" - "border: solid;" - "border-width: 3px;" - "border-color: #ffcc00;" - "border-radius: 4px;" - "}" ); - clearThumbnail(); - pTopDescLayout->addWidget(labelThumbnail, 2); - - // 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); - labelDesc->setOpenExternalLinks(true); - pTopDescLayout->addWidget(labelDesc, 1); - - // buttons: play and delete - btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup); - btnPlay->setEnabled(false); - pBottomDescLayout->addWidget(btnPlay); - btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup); - btnDelete->setEnabled(false); - pBottomDescLayout->addWidget(btnDelete); - btnToYouTube = new QPushButton(QPushButton::tr("Upload to YouTube"), pDescGroup); - btnToYouTube->setEnabled(false); - pBottomDescLayout->addWidget(btnToYouTube); - - pDescLayout->addStretch(1); - pDescLayout->addLayout(pTopDescLayout, 0); - pDescLayout->addStretch(1); - pDescLayout->addLayout(pBottomDescLayout, 0); - - pPageLayout->addWidget(pDescGroup, 0, 0); - } - - return pPageLayout; -} - -QLayout * PageVideos::footerLayoutDefinition() -{ - return NULL; -} - -void PageVideos::connectSignals() -{ - connect(checkUseGameRes, SIGNAL(stateChanged(int)), this, SLOT(changeUseGameRes(int))); - connect(checkRecordAudio, SIGNAL(stateChanged(int)), this, SLOT(changeRecordAudio(int))); - connect(comboAVFormats, SIGNAL(currentIndexChanged(int)), this, SLOT(changeAVFormat(int))); - connect(btnDefaults, SIGNAL(clicked()), this, SLOT(setDefaultOptions())); - connect(filesTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClicked(int, int))); - connect(filesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int, int))); - connect(filesTable, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged(int,int,int,int))); - connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile())); - connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles())); - connect(btnToYouTube, SIGNAL(clicked()), this, SLOT(uploadToYouTube())); - connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory())); -} - -PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent), - config(0), netManager(0) -{ - nameChangedFromCode = false; - numRecorders = 0; - numUploads = 0; - initPage(); -} - -void PageVideos::init(GameUIConfig * config) -{ - this->config = config; - - QString path = cfgdir->absolutePath() + "/Videos"; - QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this); - pWatcher->addPath(path); - connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &))); - updateFileList(path); - - startEncoding(); // this is for videos recorded from demos which were executed directly (without frontend) -} - -// user changed file format, we need to update list of codecs -void PageVideos::changeAVFormat(int index) -{ - // remember selected codecs - QString prevVCodec = videoCodec(); - QString prevACodec = audioCodec(); - - // clear lists of codecs - comboVideoCodecs->clear(); - comboAudioCodecs->clear(); - - // get list of codecs for specified format - LibavIteraction::instance().fillCodecs(comboAVFormats->itemData(index).toString(), comboVideoCodecs, comboAudioCodecs); - - // disable audio if there is no audio codec - if (comboAudioCodecs->count() == 0) - { - checkRecordAudio->setChecked(false); - checkRecordAudio->setEnabled(false); - } - else - checkRecordAudio->setEnabled(true); - - // restore selected codecs if possible - int iVCodec = comboVideoCodecs->findData(prevVCodec); - if (iVCodec != -1) - comboVideoCodecs->setCurrentIndex(iVCodec); - int iACodec = comboAudioCodecs->findData(prevACodec); - if (iACodec != -1) - comboAudioCodecs->setCurrentIndex(iACodec); -} - -// user switched checkbox 'use game resolution' -void PageVideos::changeUseGameRes(int state) -{ - if (state && config) - { - // set resolution to game resolution - QRect resolution = config->vid_Resolution(); - widthEdit->setText(QString::number(resolution.width())); - heightEdit->setText(QString::number(resolution.height())); - } - widthEdit->setEnabled(!state); - heightEdit->setEnabled(!state); -} - -// user switched checkbox 'record audio' -void PageVideos::changeRecordAudio(int state) -{ - comboAudioCodecs->setEnabled(!!state); -} - -void PageVideos::setDefaultCodecs() -{ - if (tryCodecs("mp4", "libx264", "libmp3lame")) - return; - if (tryCodecs("mp4", "libx264", "libfaac")) - return; - if (tryCodecs("mp4", "libx264", "libvo_aacenc")) - return; - if (tryCodecs("mp4", "libx264", "aac")) - return; - if (tryCodecs("mp4", "libx264", "mp2")) - return; - if (tryCodecs("avi", "libxvid", "libmp3lame")) - return; - if (tryCodecs("avi", "libxvid", "ac3_fixed")) - return; - if (tryCodecs("avi", "libxvid", "mp2")) - return; - if (tryCodecs("avi", "mpeg4", "libmp3lame")) - return; - if (tryCodecs("avi", "mpeg4", "ac3_fixed")) - return; - if (tryCodecs("avi", "mpeg4", "mp2")) - return; - - // this shouldn't happen, just in case - if (tryCodecs("ogg", "libtheora", "libvorbis")) - return; - tryCodecs("ogg", "libtheora", "flac"); -} - -void PageVideos::setDefaultOptions() -{ - framerateBox->setValue(25); - checkRecordAudio->setChecked(true); - checkUseGameRes->setChecked(true); - setDefaultCodecs(); -} - -bool PageVideos::tryCodecs(const QString & format, const QString & vcodec, const QString & acodec) -{ - // first we should change format - int iFormat = comboAVFormats->findData(format); - 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); - if (iVCodec == -1) - return false; - comboVideoCodecs->setCurrentIndex(iVCodec); - - // try to find audio codec - int iACodec = comboAudioCodecs->findData(acodec); - if (iACodec == -1 && checkRecordAudio->isChecked()) - return false; - if (iACodec != -1) - comboAudioCodecs->setCurrentIndex(iACodec); - - return true; -} - -// get file size as string -static QString FileSizeStr(const QString & path) -{ - quint64 size = QFileInfo(path).size(); - - quint64 KiB = 1024; - quint64 MiB = 1024*KiB; - quint64 GiB = 1024*MiB; - QString sizeStr; - if (size >= GiB) - return QString("%1 GiB").arg(QString::number(float(size)/GiB, 'f', 2)); - if (size >= MiB) - return QString("%1 MiB").arg(QString::number(float(size)/MiB, 'f', 2)); - if (size >= KiB) - return QString("%1 KiB").arg(QString::number(float(size)/KiB, 'f', 2)); - return PageVideos::tr("%1 bytes").arg(QString::number(size)); -} - -// set file size in file list in specified row -void PageVideos::updateSize(int row) -{ - VideoItem * item = nameItem(row); - QString path = item->ready()? item->path() : cfgdir->absoluteFilePath("VideoTemp/" + item->pRecorder->name); - 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 - int numRows = filesTable->rowCount(); - for (int i = 0; i < numRows; i++) - nameItem(i)->seen = false; - - QStringList files = QDir(path).entryList(QDir::Files); - foreach (const QString & name, files) - { - int row = -1; - foreach (QTableWidgetItem * item, filesTable->findItems(name, Qt::MatchExactly)) - { - if (item->type() != QTableWidgetItem::UserType || !((VideoItem*)item)->ready()) - continue; - row = item->row(); - break; - } - if (row == -1) - row = appendRow(name); - VideoItem * item = nameItem(row); - item->seen = true; - item->desc = ""; - updateSize(row); - } - - // remove all non seen files - for (int i = 0; i < filesTable->rowCount();) - { - VideoItem * item = nameItem(i); - if (item->ready() && !item->seen) - filesTable->removeRow(i); - else - i++; - } -} - -void PageVideos::addRecorder(HWRecorder* pRecorder) -{ - int row = appendRow(pRecorder->name); - VideoItem * item = nameItem(row); - item->pRecorder = pRecorder; - pRecorder->item = item; - - // add progress bar - QProgressBar * progressBar = new QProgressBar(filesTable); - progressBar->setMinimum(0); - progressBar->setMaximum(10000); - progressBar->setValue(0); - connect(pRecorder, SIGNAL(onProgress(float)), this, SLOT(updateProgress(float))); - connect(pRecorder, SIGNAL(encodingFinished(bool)), this, SLOT(encodingFinished(bool))); - filesTable->setCellWidget(row, vcProgress, progressBar); - - numRecorders++; -} - -void PageVideos::setProgress(int row, VideoItem* item, float value) -{ - QProgressBar * progressBar = (QProgressBar*)filesTable->cellWidget(row, vcProgress); - progressBar->setValue(value*10000); - progressBar->setFormat(QString("%1%").arg(value*100, 0, 'f', 2)); - item->progress = value; -} - -void PageVideos::updateProgress(float value) -{ - HWRecorder * pRecorder = (HWRecorder*)sender(); - VideoItem * item = pRecorder->item; - int row = filesTable->row(item); - - // update file size every percent - if (value - item->lastSizeUpdate > 0.01) - { - updateSize(row); - item->lastSizeUpdate = value; - } - - setProgress(row, item, value); -} - -void PageVideos::encodingFinished(bool success) -{ - numRecorders--; - - HWRecorder * pRecorder = (HWRecorder*)sender(); - VideoItem * item = (VideoItem*)pRecorder->item; - int row = filesTable->row(item); - - if (success) - { - // move file to destination - success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + item->name); - if (!success) - { - // unable to rename for some reason (maybe user entered incorrect name); - // try to use temp name instead. - success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + pRecorder->name); - if (success) - setName(item, pRecorder->name); - } - } - - if (!success) - { - filesTable->removeRow(row); - return; - } - - filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar - item->pRecorder = NULL; - updateSize(row); - updateDescription(); -} - -void PageVideos::cellDoubleClicked(int row, int column) -{ - play(row); -} - -void PageVideos::cellChanged(int row, int column) -{ - // user can only edit name - if (column != vcName || nameChangedFromCode) - return; - - // user has edited filename, so we should rename the file - VideoItem * item = nameItem(row); - QString oldName = item->name; - QString newName = item->text(); - if (!newName.contains('.')) // user forgot an extension - { - // restore old extension - int pt = oldName.lastIndexOf('.'); - if (pt != -1) - { - newName += oldName.right(oldName.length() - pt); - setName(item, newName); - } - } -#ifdef Q_WS_WIN - // there is a bug in qt, QDir::rename() doesn't fail on such names but damages files - if (newName.contains(QRegExp("[\"*:<>?\/|]"))) - { - setName(item, oldName); - return; - } -#endif - if (item->ready() && !cfgdir->rename("Videos/" + oldName, "Videos/" + newName)) - { - // unable to rename for some reason (maybe user entered incorrect name), - // therefore restore old name in cell - setName(item, oldName); - return; - } - item->name = newName; - updateDescription(); -} - -void PageVideos::setName(VideoItem * item, const QString & newName) -{ - nameChangedFromCode = true; - item->setText(newName); - nameChangedFromCode = false; - item->name = newName; -} - -int PageVideos::appendRow(const QString & name) -{ - int row = filesTable->rowCount(); - filesTable->setRowCount(row+1); - - // add 'name' item - QTableWidgetItem * item = new VideoItem(name); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); - nameChangedFromCode = true; - filesTable->setItem(row, vcName, item); - nameChangedFromCode = false; - - // add 'size' item - item = new QTableWidgetItem(); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - item->setTextAlignment(Qt::AlignRight); - filesTable->setItem(row, vcSize, item); - - // add 'progress' item - item = new QTableWidgetItem(); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - filesTable->setItem(row, vcProgress, item); - - return row; -} - -VideoItem* PageVideos::nameItem(int row) -{ - return (VideoItem*)filesTable->item(row, vcName); -} - -void PageVideos::clearThumbnail() -{ - // add empty (transparent) image for proper sizing - QPixmap pic(ThumbnailSize); - pic.fill(QColor(0,0,0,0)); - labelThumbnail->setPixmap(pic); -} - -void PageVideos::updateDescription() -{ - VideoItem * item = nameItem(filesTable->currentRow()); - if (!item) - { - // nothing is selected => clear description and return - labelDesc->clear(); - clearThumbnail(); - btnPlay->setEnabled(false); - btnDelete->setEnabled(false); - btnToYouTube->setEnabled(false); - return; - } - - btnPlay->setEnabled(item->ready()); - btnToYouTube->setEnabled(item->ready()); - btnDelete->setEnabled(true); - 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"; - - 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.isEmpty()) - { - // Extract description from file; - // It will contain duration, resolution, etc and also comment added by hwengine. - item->desc = LibavIteraction::instance().getFileInfo(path); - - // 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 = item->desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7)); - item->desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin); - } - } - desc += item->desc + '\n'; - } - - 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()) - { - // 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("%1").arg(item->uploadUrl); - desc.replace("\n", "
"); - - 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")) - { - if (pic.height()*ThumbnailSize.width() > pic.width()*ThumbnailSize.height()) - pic = pic.scaledToWidth(ThumbnailSize.width()); - else - pic = pic.scaledToHeight(ThumbnailSize.height()); - labelThumbnail->setPixmap(pic); - } - else - clearThumbnail(); - } -} - -// user selected another cell, so we should change description -void PageVideos::currentCellChanged(int row, int column, int previousRow, int previousColumn) -{ - updateDescription(); -} - -// open video file in external media player -void PageVideos::play(int row) -{ - VideoItem * item = nameItem(row); - if (item && item->ready()) - QDesktopServices::openUrl(QUrl("file:///" + QDir::toNativeSeparators(item->path()))); -} - -void PageVideos::playSelectedFile() -{ - int index = filesTable->currentRow(); - if (index != -1) - play(index); -} - -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 items = filesTable->selectedItems(); - int num = items.size() / vcNumColumns; - if (num == 0) - return; - - // ask user if (s)he is serious - if (QMessageBox::question(this, - tr("Are you sure?"), - tr("Do you really want do remove %1 file(s)?").arg(num), - QMessageBox::Yes | QMessageBox::No) - != QMessageBox::Yes) - return; - - // remove - foreach (QTableWidgetItem * witem, items) - { - if (witem->type() != QTableWidgetItem::UserType) - continue; - VideoItem * item = (VideoItem*)witem; - if (!item->ready()) - item->pRecorder->deleteLater(); - else - cfgdir->remove("Videos/" + item->name); - } -#endif -} - -void PageVideos::keyPressEvent(QKeyEvent * pEvent) -{ - if (filesTable->hasFocus()) - { - if (pEvent->key() == Qt::Key_Delete) - { - deleteSelectedFiles(); - return; - } - if (pEvent->key() == Qt::Key_Enter) // doesn't work - { - playSelectedFile(); - return; - } - } - AbstractPage::keyPressEvent(pEvent); -} - -void PageVideos::openVideosDirectory() -{ - QString path = QDir::toNativeSeparators(cfgdir->absolutePath() + "/Videos"); - QDesktopServices::openUrl(QUrl("file:///" + path)); -} - -// 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") && !file.endsWith("-url.txt")) - temp.remove(file); - } -} - -bool PageVideos::tryQuit(HWForm * form) -{ - bool quit = true; - if (numRecorders != 0 || numUploads != 0) - { - // ask user what to do - abort or wait - HWAskQuitDialog * askd = new HWAskQuitDialog(this, form); - askd->deleteLater(); - quit = askd->exec(); - } - if (quit) - clearTemp(); - return quit; -} - -// returns multi-line string with list of videos in progress -/* it will look like this: -foo.avi (15.21% - encoding) -bar.avi (18.21% - uploading) -*/ -QString PageVideos::getVideosInProgress() -{ - QString list = ""; - int count = filesTable->rowCount(); - for (int i = 0; i < count; i++) - { - VideoItem * item = nameItem(i); - QString process; - if (!item->ready()) - process = tr("encoding"); - else if (item->pUploading) - process = tr("uploading"); - else - continue; - float progress = 100*item->progress; - if (progress > 99.99) - progress = 99.99; // displaying 100% may be confusing - list += item->name + " (" + QString::number(progress, 'f', 2) + "% - " + process + ")\n"; - } - return list; -} - -void PageVideos::startEncoding(const QByteArray & record) -{ - QDir videoTempDir(cfgdir->absolutePath() + "/VideoTemp/"); - QStringList files = videoTempDir.entryList(QStringList("*.txtout"), QDir::Files); - foreach (const QString & str, files) - { - QString prefix = str; - prefix.chop(7); // remove ".txtout" - videoTempDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice - - HWRecorder* pRecorder = new HWRecorder(config, prefix); - - if (!record.isEmpty()) - pRecorder->EncodeVideo(record); - else - { - // this is for videos recorded from demos which were executed directly (without frontend) - QFile demofile(videoTempDir.absoluteFilePath(prefix + ".hwd")); - if (!demofile.open(QIODevice::ReadOnly)) - continue; - QByteArray demo = demofile.readAll(); - if (demo.isEmpty()) - continue; - pRecorder->EncodeVideo(demo); - } - addRecorder(pRecorder); - } -} - -VideoItem * PageVideos::itemFromReply(QNetworkReply* reply, int & row) -{ - VideoItem * item = NULL; - int count = filesTable->rowCount(); - // find corresponding item (maybe there is a better way to implement this?) - for (int i = 0; i < count; i++) - { - item = nameItem(i); - if (item->pUploading == reply) - { - row = i; - break; - } - } - 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(); - - int row; - VideoItem * item = itemFromReply(reply, row); - if (!item) - return; - - item->pUploading = NULL; - - // extract video id from reply - QString videoid; - QXmlStreamReader xml(reply); - while (!xml.atEnd()) - { - xml.readNext(); - if (xml.qualifiedName() == "yt:videoid") - { - videoid = xml.readElementText(); - break; - } - } - - if (!videoid.isEmpty()) - { - item->uploadUrl = "http://youtu.be/" + videoid; - updateDescription(); - - // 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 (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)); - request.setRawHeader("Content-Type", "application/octet-stream"); - - QFile * file = new QFile(item->path(), this); - if (!file->open(QIODevice::ReadOnly)) - return; - - // add progress bar - QProgressBar * progressBar = new QProgressBar(filesTable); - progressBar->setMinimum(0); - progressBar->setMaximum(10000); - progressBar->setValue(0); - // 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); - - QNetworkReply* reply = netManager->put(request, file); - file->setParent(reply); // automatically close file when needed - item->pUploading = reply; - connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); - connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); - numUploads++; - - updateDescription(); -} +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hwconsts.h" +#include "pagevideos.h" +#include "igbox.h" +#include "libav_iteraction.h" +#include "gameuiconfig.h" +#include "recorder.h" +#include "ask_quit.h" +#include "upload_video.h" + +static const QSize ThumbnailSize(350, 350*3/5); + +// columns in table with list of video files +enum VideosColumns +{ + vcName, + vcSize, + 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 + + public: + VideoItem(const QString& name); + ~VideoItem(); + + QString name; + 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; + float progress; + + bool ready() + { return !pRecorder; } + + QString path() + { return cfgdir->absoluteFilePath("Videos/" + name); } +}; + +VideoItem::VideoItem(const QString& name) + : QTableWidgetItem(name, UserType) +{ + this->name = name; + pRecorder = NULL; + pUploading = NULL; + lastSizeUpdate = 0; + progress = 0; +} + +VideoItem::~VideoItem() +{} + +QLayout * PageVideos::bodyLayoutDefinition() +{ + QGridLayout * pPageLayout = new QGridLayout(); + pPageLayout->setColumnStretch(0, 1); + pPageLayout->setColumnStretch(1, 2); + pPageLayout->setRowStretch(0, 1); + pPageLayout->setRowStretch(1, 1); + + // options + { + IconedGroupBox* pOptionsGroup = new IconedGroupBox(this); + 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); + + // label for format + QLabel *labelFormat = new QLabel(pOptionsGroup); + labelFormat->setText(QLabel::tr("Format")); + pOptLayout->addWidget(labelFormat, 0, 0); + + // list of supported formats + comboAVFormats = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboAVFormats, 0, 1, 1, 4); + LibavIteraction::instance().fillFormats(comboAVFormats); + + // separator + QFrame * hr = new QFrame(pOptionsGroup); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + pOptLayout->addWidget(hr, 1, 0, 1, 5); + + // label for audio codec + QLabel *labelACodec = new QLabel(pOptionsGroup); + labelACodec->setText(QLabel::tr("Audio codec")); + pOptLayout->addWidget(labelACodec, 2, 0); + + // list of supported audio codecs + comboAudioCodecs = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboAudioCodecs, 2, 1, 1, 3); + + // checkbox 'record audio' + checkRecordAudio = new QCheckBox(pOptionsGroup); + checkRecordAudio->setText(QCheckBox::tr("Record audio")); + pOptLayout->addWidget(checkRecordAudio, 2, 4); + + // separator + hr = new QFrame(pOptionsGroup); + hr->setFrameStyle(QFrame::HLine); + hr->setLineWidth(3); + hr->setFixedHeight(10); + pOptLayout->addWidget(hr, 3, 0, 1, 5); + + // label for video codec + QLabel *labelVCodec = new QLabel(pOptionsGroup); + labelVCodec->setText(QLabel::tr("Video codec")); + pOptLayout->addWidget(labelVCodec, 4, 0); + + // list of supported video codecs + comboVideoCodecs = new QComboBox(pOptionsGroup); + pOptLayout->addWidget(comboVideoCodecs, 4, 1, 1, 4); + + // label for resolution + QLabel *labelRes = new QLabel(pOptionsGroup); + labelRes->setText(QLabel::tr("Resolution")); + pOptLayout->addWidget(labelRes, 5, 0); + + // width + widthEdit = new QLineEdit(pOptionsGroup); + widthEdit->setValidator(new QIntValidator(this)); + pOptLayout->addWidget(widthEdit, 5, 1); + + // x + QLabel *labelX = new QLabel(pOptionsGroup); + labelX->setText("X"); + pOptLayout->addWidget(labelX, 5, 2); + + // height + heightEdit = new QLineEdit(pOptionsGroup); + heightEdit->setValidator(new QIntValidator(pOptionsGroup)); + pOptLayout->addWidget(heightEdit, 5, 3); + + // checkbox 'use game resolution' + checkUseGameRes = new QCheckBox(pOptionsGroup); + checkUseGameRes->setText(QCheckBox::tr("Use game resolution")); + pOptLayout->addWidget(checkUseGameRes, 5, 4); + + // label for framerate + QLabel *labelFramerate = new QLabel(pOptionsGroup); + labelFramerate->setText(QLabel::tr("Framerate")); + pOptLayout->addWidget(labelFramerate, 6, 0); + + // framerate + framerateBox = new QSpinBox(pOptionsGroup); + framerateBox->setRange(1, 200); + framerateBox->setSingleStep(1); + pOptLayout->addWidget(framerateBox, 6, 1); + + // button 'set default options' + btnDefaults = new QPushButton(pOptionsGroup); + btnDefaults->setText(QPushButton::tr("Set default options")); + pOptLayout->addWidget(btnDefaults, 7, 0, 1, 5); + + pPageLayout->addWidget(pOptionsGroup, 1, 0); + } + + // list of videos + { + IconedGroupBox* pTableGroup = new IconedGroupBox(this); + pTableGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME + pTableGroup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + pTableGroup->setTitle(QGroupBox::tr("Videos")); + + QStringList columns; + columns << tr("Name"); + columns << tr("Size"); + columns << ""; + + filesTable = new QTableWidget(pTableGroup); + filesTable->setColumnCount(vcNumColumns); + filesTable->setHorizontalHeaderLabels(columns); + filesTable->setSelectionBehavior(QAbstractItemView::SelectRows); + filesTable->setSelectionMode(QAbstractItemView::SingleSelection); + filesTable->setEditTriggers(QAbstractItemView::SelectedClicked); + filesTable->verticalHeader()->hide(); + filesTable->setMinimumWidth(400); + + QHeaderView * header = filesTable->horizontalHeader(); + header->setResizeMode(vcName, QHeaderView::ResizeToContents); + header->setResizeMode(vcSize, QHeaderView::Fixed); + header->resizeSection(vcSize, 100); + header->setStretchLastSection(true); + + btnOpenDir = new QPushButton(QPushButton::tr("Open videos directory"), pTableGroup); + + QVBoxLayout *box = new QVBoxLayout(pTableGroup); + box->addWidget(filesTable); + box->addWidget(btnOpenDir); + + pPageLayout->addWidget(pTableGroup, 0, 1, 2, 1); + } + + // description + { + IconedGroupBox* pDescGroup = new IconedGroupBox(this); + pDescGroup->setIcon(QIcon(":/res/graphicsicon.png")); // FIXME + pDescGroup->setTitle(QGroupBox::tr("Description")); + + QVBoxLayout* pDescLayout = new QVBoxLayout(pDescGroup); + QHBoxLayout* pTopDescLayout = new QHBoxLayout(0); // picture and text + QHBoxLayout* pBottomDescLayout = new QHBoxLayout(0); // buttons + + // label with thumbnail picture + labelThumbnail = new QLabel(pDescGroup); + labelThumbnail->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + labelThumbnail->setMaximumSize(ThumbnailSize); + labelThumbnail->setStyleSheet( + "QFrame {" + "border: solid;" + "border-width: 3px;" + "border-color: #ffcc00;" + "border-radius: 4px;" + "}" ); + clearThumbnail(); + pTopDescLayout->addWidget(labelThumbnail, 2); + + // 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); + labelDesc->setOpenExternalLinks(true); + pTopDescLayout->addWidget(labelDesc, 1); + + // buttons: play and delete + btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup); + btnPlay->setEnabled(false); + pBottomDescLayout->addWidget(btnPlay); + btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup); + btnDelete->setEnabled(false); + pBottomDescLayout->addWidget(btnDelete); + btnToYouTube = new QPushButton(QPushButton::tr("Upload to YouTube"), pDescGroup); + btnToYouTube->setEnabled(false); + pBottomDescLayout->addWidget(btnToYouTube); + + pDescLayout->addStretch(1); + pDescLayout->addLayout(pTopDescLayout, 0); + pDescLayout->addStretch(1); + pDescLayout->addLayout(pBottomDescLayout, 0); + + pPageLayout->addWidget(pDescGroup, 0, 0); + } + + return pPageLayout; +} + +QLayout * PageVideos::footerLayoutDefinition() +{ + return NULL; +} + +void PageVideos::connectSignals() +{ + connect(checkUseGameRes, SIGNAL(stateChanged(int)), this, SLOT(changeUseGameRes(int))); + connect(checkRecordAudio, SIGNAL(stateChanged(int)), this, SLOT(changeRecordAudio(int))); + connect(comboAVFormats, SIGNAL(currentIndexChanged(int)), this, SLOT(changeAVFormat(int))); + connect(btnDefaults, SIGNAL(clicked()), this, SLOT(setDefaultOptions())); + connect(filesTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClicked(int, int))); + connect(filesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int, int))); + connect(filesTable, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged(int,int,int,int))); + connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile())); + connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles())); + connect(btnToYouTube, SIGNAL(clicked()), this, SLOT(uploadToYouTube())); + connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory())); +} + +PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent), + config(0), netManager(0) +{ + nameChangedFromCode = false; + numRecorders = 0; + numUploads = 0; + initPage(); +} + +void PageVideos::init(GameUIConfig * config) +{ + this->config = config; + + QString path = cfgdir->absolutePath() + "/Videos"; + QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this); + pWatcher->addPath(path); + connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &))); + updateFileList(path); + + startEncoding(); // this is for videos recorded from demos which were executed directly (without frontend) +} + +// user changed file format, we need to update list of codecs +void PageVideos::changeAVFormat(int index) +{ + // remember selected codecs + QString prevVCodec = videoCodec(); + QString prevACodec = audioCodec(); + + // clear lists of codecs + comboVideoCodecs->clear(); + comboAudioCodecs->clear(); + + // get list of codecs for specified format + LibavIteraction::instance().fillCodecs(comboAVFormats->itemData(index).toString(), comboVideoCodecs, comboAudioCodecs); + + // disable audio if there is no audio codec + if (comboAudioCodecs->count() == 0) + { + checkRecordAudio->setChecked(false); + checkRecordAudio->setEnabled(false); + } + else + checkRecordAudio->setEnabled(true); + + // restore selected codecs if possible + int iVCodec = comboVideoCodecs->findData(prevVCodec); + if (iVCodec != -1) + comboVideoCodecs->setCurrentIndex(iVCodec); + int iACodec = comboAudioCodecs->findData(prevACodec); + if (iACodec != -1) + comboAudioCodecs->setCurrentIndex(iACodec); +} + +// user switched checkbox 'use game resolution' +void PageVideos::changeUseGameRes(int state) +{ + if (state && config) + { + // set resolution to game resolution + QRect resolution = config->vid_Resolution(); + widthEdit->setText(QString::number(resolution.width())); + heightEdit->setText(QString::number(resolution.height())); + } + widthEdit->setEnabled(!state); + heightEdit->setEnabled(!state); +} + +// user switched checkbox 'record audio' +void PageVideos::changeRecordAudio(int state) +{ + comboAudioCodecs->setEnabled(!!state); +} + +void PageVideos::setDefaultCodecs() +{ + if (tryCodecs("mp4", "libx264", "libmp3lame")) + return; + if (tryCodecs("mp4", "libx264", "libfaac")) + return; + if (tryCodecs("mp4", "libx264", "libvo_aacenc")) + return; + if (tryCodecs("mp4", "libx264", "aac")) + return; + if (tryCodecs("mp4", "libx264", "mp2")) + return; + if (tryCodecs("avi", "libxvid", "libmp3lame")) + return; + if (tryCodecs("avi", "libxvid", "ac3_fixed")) + return; + if (tryCodecs("avi", "libxvid", "mp2")) + return; + if (tryCodecs("avi", "mpeg4", "libmp3lame")) + return; + if (tryCodecs("avi", "mpeg4", "ac3_fixed")) + return; + if (tryCodecs("avi", "mpeg4", "mp2")) + return; + + // this shouldn't happen, just in case + if (tryCodecs("ogg", "libtheora", "libvorbis")) + return; + tryCodecs("ogg", "libtheora", "flac"); +} + +void PageVideos::setDefaultOptions() +{ + framerateBox->setValue(25); + checkRecordAudio->setChecked(true); + checkUseGameRes->setChecked(true); + setDefaultCodecs(); +} + +bool PageVideos::tryCodecs(const QString & format, const QString & vcodec, const QString & acodec) +{ + // first we should change format + int iFormat = comboAVFormats->findData(format); + 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); + if (iVCodec == -1) + return false; + comboVideoCodecs->setCurrentIndex(iVCodec); + + // try to find audio codec + int iACodec = comboAudioCodecs->findData(acodec); + if (iACodec == -1 && checkRecordAudio->isChecked()) + return false; + if (iACodec != -1) + comboAudioCodecs->setCurrentIndex(iACodec); + + return true; +} + +// get file size as string +static QString FileSizeStr(const QString & path) +{ + quint64 size = QFileInfo(path).size(); + + quint64 KiB = 1024; + quint64 MiB = 1024*KiB; + quint64 GiB = 1024*MiB; + QString sizeStr; + if (size >= GiB) + return QString("%1 GiB").arg(QString::number(float(size)/GiB, 'f', 2)); + if (size >= MiB) + return QString("%1 MiB").arg(QString::number(float(size)/MiB, 'f', 2)); + if (size >= KiB) + return QString("%1 KiB").arg(QString::number(float(size)/KiB, 'f', 2)); + return PageVideos::tr("%1 bytes").arg(QString::number(size)); +} + +// set file size in file list in specified row +void PageVideos::updateSize(int row) +{ + VideoItem * item = nameItem(row); + QString path = item->ready()? item->path() : cfgdir->absoluteFilePath("VideoTemp/" + item->pRecorder->name); + 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 + int numRows = filesTable->rowCount(); + for (int i = 0; i < numRows; i++) + nameItem(i)->seen = false; + + QStringList files = QDir(path).entryList(QDir::Files); + foreach (const QString & name, files) + { + int row = -1; + foreach (QTableWidgetItem * item, filesTable->findItems(name, Qt::MatchExactly)) + { + if (item->type() != QTableWidgetItem::UserType || !((VideoItem*)item)->ready()) + continue; + row = item->row(); + break; + } + if (row == -1) + row = appendRow(name); + VideoItem * item = nameItem(row); + item->seen = true; + item->desc = ""; + updateSize(row); + } + + // remove all non seen files + for (int i = 0; i < filesTable->rowCount();) + { + VideoItem * item = nameItem(i); + if (item->ready() && !item->seen) + filesTable->removeRow(i); + else + i++; + } +} + +void PageVideos::addRecorder(HWRecorder* pRecorder) +{ + int row = appendRow(pRecorder->name); + VideoItem * item = nameItem(row); + item->pRecorder = pRecorder; + pRecorder->item = item; + + // add progress bar + QProgressBar * progressBar = new QProgressBar(filesTable); + progressBar->setMinimum(0); + progressBar->setMaximum(10000); + progressBar->setValue(0); + connect(pRecorder, SIGNAL(onProgress(float)), this, SLOT(updateProgress(float))); + connect(pRecorder, SIGNAL(encodingFinished(bool)), this, SLOT(encodingFinished(bool))); + filesTable->setCellWidget(row, vcProgress, progressBar); + + numRecorders++; +} + +void PageVideos::setProgress(int row, VideoItem* item, float value) +{ + QProgressBar * progressBar = (QProgressBar*)filesTable->cellWidget(row, vcProgress); + progressBar->setValue(value*10000); + progressBar->setFormat(QString("%1%").arg(value*100, 0, 'f', 2)); + item->progress = value; +} + +void PageVideos::updateProgress(float value) +{ + HWRecorder * pRecorder = (HWRecorder*)sender(); + VideoItem * item = pRecorder->item; + int row = filesTable->row(item); + + // update file size every percent + if (value - item->lastSizeUpdate > 0.01) + { + updateSize(row); + item->lastSizeUpdate = value; + } + + setProgress(row, item, value); +} + +void PageVideos::encodingFinished(bool success) +{ + numRecorders--; + + HWRecorder * pRecorder = (HWRecorder*)sender(); + VideoItem * item = (VideoItem*)pRecorder->item; + int row = filesTable->row(item); + + if (success) + { + // move file to destination + success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + item->name); + if (!success) + { + // unable to rename for some reason (maybe user entered incorrect name); + // try to use temp name instead. + success = cfgdir->rename("VideoTemp/" + pRecorder->name, "Videos/" + pRecorder->name); + if (success) + setName(item, pRecorder->name); + } + } + + if (!success) + { + filesTable->removeRow(row); + return; + } + + filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar + item->pRecorder = NULL; + updateSize(row); + updateDescription(); +} + +void PageVideos::cellDoubleClicked(int row, int column) +{ + play(row); +} + +void PageVideos::cellChanged(int row, int column) +{ + // user can only edit name + if (column != vcName || nameChangedFromCode) + return; + + // user has edited filename, so we should rename the file + VideoItem * item = nameItem(row); + QString oldName = item->name; + QString newName = item->text(); + if (!newName.contains('.')) // user forgot an extension + { + // restore old extension + int pt = oldName.lastIndexOf('.'); + if (pt != -1) + { + newName += oldName.right(oldName.length() - pt); + setName(item, newName); + } + } +#ifdef Q_WS_WIN + // there is a bug in qt, QDir::rename() doesn't fail on such names but damages files + if (newName.contains(QRegExp("[\"*:<>?\/|]"))) + { + setName(item, oldName); + return; + } +#endif + if (item->ready() && !cfgdir->rename("Videos/" + oldName, "Videos/" + newName)) + { + // unable to rename for some reason (maybe user entered incorrect name), + // therefore restore old name in cell + setName(item, oldName); + return; + } + item->name = newName; + updateDescription(); +} + +void PageVideos::setName(VideoItem * item, const QString & newName) +{ + nameChangedFromCode = true; + item->setText(newName); + nameChangedFromCode = false; + item->name = newName; +} + +int PageVideos::appendRow(const QString & name) +{ + int row = filesTable->rowCount(); + filesTable->setRowCount(row+1); + + // add 'name' item + QTableWidgetItem * item = new VideoItem(name); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + nameChangedFromCode = true; + filesTable->setItem(row, vcName, item); + nameChangedFromCode = false; + + // add 'size' item + item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + item->setTextAlignment(Qt::AlignRight); + filesTable->setItem(row, vcSize, item); + + // add 'progress' item + item = new QTableWidgetItem(); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + filesTable->setItem(row, vcProgress, item); + + return row; +} + +VideoItem* PageVideos::nameItem(int row) +{ + return (VideoItem*)filesTable->item(row, vcName); +} + +void PageVideos::clearThumbnail() +{ + // add empty (transparent) image for proper sizing + QPixmap pic(ThumbnailSize); + pic.fill(QColor(0,0,0,0)); + labelThumbnail->setPixmap(pic); +} + +void PageVideos::updateDescription() +{ + VideoItem * item = nameItem(filesTable->currentRow()); + if (!item) + { + // nothing is selected => clear description and return + labelDesc->clear(); + clearThumbnail(); + btnPlay->setEnabled(false); + btnDelete->setEnabled(false); + btnToYouTube->setEnabled(false); + return; + } + + btnPlay->setEnabled(item->ready()); + btnToYouTube->setEnabled(item->ready()); + btnDelete->setEnabled(true); + 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"; + + 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.isEmpty()) + { + // Extract description from file; + // It will contain duration, resolution, etc and also comment added by hwengine. + item->desc = LibavIteraction::instance().getFileInfo(path); + + // 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 = item->desc.mid(prefixBegin + 7, prefixEnd - (prefixBegin + 7)); + item->desc.remove(prefixBegin, prefixEnd + 7 - prefixBegin); + } + } + desc += item->desc + '\n'; + } + + 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()) + { + // 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("%1").arg(item->uploadUrl); + desc.replace("\n", "
"); + + 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")) + { + if (pic.height()*ThumbnailSize.width() > pic.width()*ThumbnailSize.height()) + pic = pic.scaledToWidth(ThumbnailSize.width()); + else + pic = pic.scaledToHeight(ThumbnailSize.height()); + labelThumbnail->setPixmap(pic); + } + else + clearThumbnail(); + } +} + +// user selected another cell, so we should change description +void PageVideos::currentCellChanged(int row, int column, int previousRow, int previousColumn) +{ + updateDescription(); +} + +// open video file in external media player +void PageVideos::play(int row) +{ + VideoItem * item = nameItem(row); + if (item && item->ready()) + QDesktopServices::openUrl(QUrl("file:///" + QDir::toNativeSeparators(item->path()))); +} + +void PageVideos::playSelectedFile() +{ + int index = filesTable->currentRow(); + if (index != -1) + play(index); +} + +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 items = filesTable->selectedItems(); + int num = items.size() / vcNumColumns; + if (num == 0) + return; + + // ask user if (s)he is serious + if (QMessageBox::question(this, + tr("Are you sure?"), + tr("Do you really want do remove %1 file(s)?").arg(num), + QMessageBox::Yes | QMessageBox::No) + != QMessageBox::Yes) + return; + + // remove + foreach (QTableWidgetItem * witem, items) + { + if (witem->type() != QTableWidgetItem::UserType) + continue; + VideoItem * item = (VideoItem*)witem; + if (!item->ready()) + item->pRecorder->deleteLater(); + else + cfgdir->remove("Videos/" + item->name); + } +#endif +} + +void PageVideos::keyPressEvent(QKeyEvent * pEvent) +{ + if (filesTable->hasFocus()) + { + if (pEvent->key() == Qt::Key_Delete) + { + deleteSelectedFiles(); + return; + } + if (pEvent->key() == Qt::Key_Enter) // doesn't work + { + playSelectedFile(); + return; + } + } + AbstractPage::keyPressEvent(pEvent); +} + +void PageVideos::openVideosDirectory() +{ + QString path = QDir::toNativeSeparators(cfgdir->absolutePath() + "/Videos"); + QDesktopServices::openUrl(QUrl("file:///" + path)); +} + +// 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") && !file.endsWith("-url.txt")) + temp.remove(file); + } +} + +bool PageVideos::tryQuit(HWForm * form) +{ + bool quit = true; + if (numRecorders != 0 || numUploads != 0) + { + // ask user what to do - abort or wait + HWAskQuitDialog * askd = new HWAskQuitDialog(this, form); + askd->deleteLater(); + quit = askd->exec(); + } + if (quit) + clearTemp(); + return quit; +} + +// returns multi-line string with list of videos in progress +/* it will look like this: +foo.avi (15.21% - encoding) +bar.avi (18.21% - uploading) +*/ +QString PageVideos::getVideosInProgress() +{ + QString list = ""; + int count = filesTable->rowCount(); + for (int i = 0; i < count; i++) + { + VideoItem * item = nameItem(i); + QString process; + if (!item->ready()) + process = tr("encoding"); + else if (item->pUploading) + process = tr("uploading"); + else + continue; + float progress = 100*item->progress; + if (progress > 99.99) + progress = 99.99; // displaying 100% may be confusing + list += item->name + " (" + QString::number(progress, 'f', 2) + "% - " + process + ")\n"; + } + return list; +} + +void PageVideos::startEncoding(const QByteArray & record) +{ + QDir videoTempDir(cfgdir->absolutePath() + "/VideoTemp/"); + QStringList files = videoTempDir.entryList(QStringList("*.txtout"), QDir::Files); + foreach (const QString & str, files) + { + QString prefix = str; + prefix.chop(7); // remove ".txtout" + videoTempDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice + + HWRecorder* pRecorder = new HWRecorder(config, prefix); + + if (!record.isEmpty()) + pRecorder->EncodeVideo(record); + else + { + // this is for videos recorded from demos which were executed directly (without frontend) + QFile demofile(videoTempDir.absoluteFilePath(prefix + ".hwd")); + if (!demofile.open(QIODevice::ReadOnly)) + continue; + QByteArray demo = demofile.readAll(); + if (demo.isEmpty()) + continue; + pRecorder->EncodeVideo(demo); + } + addRecorder(pRecorder); + } +} + +VideoItem * PageVideos::itemFromReply(QNetworkReply* reply, int & row) +{ + VideoItem * item = NULL; + int count = filesTable->rowCount(); + // find corresponding item (maybe there is a better way to implement this?) + for (int i = 0; i < count; i++) + { + item = nameItem(i); + if (item->pUploading == reply) + { + row = i; + break; + } + } + 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(); + + int row; + VideoItem * item = itemFromReply(reply, row); + if (!item) + return; + + item->pUploading = NULL; + + // extract video id from reply + QString videoid; + QXmlStreamReader xml(reply); + while (!xml.atEnd()) + { + xml.readNext(); + if (xml.qualifiedName() == "yt:videoid") + { + videoid = xml.readElementText(); + break; + } + } + + if (!videoid.isEmpty()) + { + item->uploadUrl = "http://youtu.be/" + videoid; + updateDescription(); + + // 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 (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)); + request.setRawHeader("Content-Type", "application/octet-stream"); + + QFile * file = new QFile(item->path(), this); + if (!file->open(QIODevice::ReadOnly)) + return; + + // add progress bar + QProgressBar * progressBar = new QProgressBar(filesTable); + progressBar->setMinimum(0); + progressBar->setMaximum(10000); + progressBar->setValue(0); + // 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); + + QNetworkReply* reply = netManager->put(request, file); + file->setParent(reply); // automatically close file when needed + item->pUploading = reply; + connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); + connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); + numUploads++; + + updateDescription(); +} diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/ui/page/pagevideos.h --- a/QTfrontend/ui/page/pagevideos.h Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/ui/page/pagevideos.h Thu Aug 30 20:42:29 2012 +0200 @@ -1,125 +1,125 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - - -#ifndef PAGE_VIDEOS_H -#define PAGE_VIDEOS_H - -#include "AbstractPage.h" - -class QNetworkAccessManager; -class QNetworkReply; -class GameUIConfig; -class HWRecorder; -class VideoItem; -class HWForm; - -class PageVideos : public AbstractPage -{ - Q_OBJECT - - public: - PageVideos(QWidget* parent = 0); - - QSpinBox *framerateBox; - QLineEdit *widthEdit; - QLineEdit *heightEdit; - QCheckBox *checkUseGameRes; - QCheckBox *checkRecordAudio; - - QString format() - { return comboAVFormats->itemData(comboAVFormats->currentIndex()).toString(); } - - QString videoCodec() - { return comboVideoCodecs->itemData(comboVideoCodecs->currentIndex()).toString(); } - - QString audioCodec() - { return comboAudioCodecs->itemData(comboAudioCodecs->currentIndex()).toString(); } - - void setDefaultCodecs(); - bool tryCodecs(const QString & format, const QString & vcodec, const QString & acodec); - void addRecorder(HWRecorder* pRecorder); - bool tryQuit(HWForm *form); - QString getVideosInProgress(); // get multi-line string with list of videos in progress - void startEncoding(const QByteArray & record = QByteArray()); - void init(GameUIConfig * config); - - private: - // virtuals from AbstractPage - QLayout * bodyLayoutDefinition(); - QLayout * footerLayoutDefinition(); - void connectSignals(); - - // virtual from QWidget - void keyPressEvent(QKeyEvent * pEvent); - - void setName(VideoItem * item, const QString & newName); - void updateSize(int row); - int appendRow(const QString & name); - VideoItem* nameItem(int row); - void play(int row); - void updateDescription(); - void clearTemp(); - void clearThumbnail(); - void setProgress(int row, VideoItem* item, float value); - VideoItem * itemFromReply(QNetworkReply* reply, int & row); - - GameUIConfig * config; - QNetworkAccessManager* netManager; - - // options group - QComboBox *comboAVFormats; - QComboBox *comboVideoCodecs; - QComboBox *comboAudioCodecs; - QPushButton *btnDefaults; - - // file list group - QTableWidget *filesTable; - QPushButton *btnOpenDir; - - // description group - QPushButton *btnPlay, *btnDelete, *btnToYouTube; - QLabel *labelDesc; - QLabel *labelThumbnail; - - // this flag is used to distinguish if cell was changed from code or by user - // (in signal cellChanged) - bool nameChangedFromCode; - - int numRecorders, numUploads; - - private slots: - void changeAVFormat(int index); - void changeUseGameRes(int state); - void changeRecordAudio(int state); - void setDefaultOptions(); - void encodingFinished(bool success); - void updateProgress(float value); - void cellDoubleClicked(int row, int column); - void cellChanged(int row, int column); - void currentCellChanged(int row, int column, int previousRow, int previousColumn); - void playSelectedFile(); - void deleteSelectedFiles(); - void openVideosDirectory(); - void updateFileList(const QString & path); - void uploadToYouTube(); - void uploadProgress(qint64 bytesSent, qint64 bytesTotal); - void uploadFinished(); -}; - -#endif // PAGE_VIDEOS_H +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + + +#ifndef PAGE_VIDEOS_H +#define PAGE_VIDEOS_H + +#include "AbstractPage.h" + +class QNetworkAccessManager; +class QNetworkReply; +class GameUIConfig; +class HWRecorder; +class VideoItem; +class HWForm; + +class PageVideos : public AbstractPage +{ + Q_OBJECT + + public: + PageVideos(QWidget* parent = 0); + + QSpinBox *framerateBox; + QLineEdit *widthEdit; + QLineEdit *heightEdit; + QCheckBox *checkUseGameRes; + QCheckBox *checkRecordAudio; + + QString format() + { return comboAVFormats->itemData(comboAVFormats->currentIndex()).toString(); } + + QString videoCodec() + { return comboVideoCodecs->itemData(comboVideoCodecs->currentIndex()).toString(); } + + QString audioCodec() + { return comboAudioCodecs->itemData(comboAudioCodecs->currentIndex()).toString(); } + + void setDefaultCodecs(); + bool tryCodecs(const QString & format, const QString & vcodec, const QString & acodec); + void addRecorder(HWRecorder* pRecorder); + bool tryQuit(HWForm *form); + QString getVideosInProgress(); // get multi-line string with list of videos in progress + void startEncoding(const QByteArray & record = QByteArray()); + void init(GameUIConfig * config); + + private: + // virtuals from AbstractPage + QLayout * bodyLayoutDefinition(); + QLayout * footerLayoutDefinition(); + void connectSignals(); + + // virtual from QWidget + void keyPressEvent(QKeyEvent * pEvent); + + void setName(VideoItem * item, const QString & newName); + void updateSize(int row); + int appendRow(const QString & name); + VideoItem* nameItem(int row); + void play(int row); + void updateDescription(); + void clearTemp(); + void clearThumbnail(); + void setProgress(int row, VideoItem* item, float value); + VideoItem * itemFromReply(QNetworkReply* reply, int & row); + + GameUIConfig * config; + QNetworkAccessManager* netManager; + + // options group + QComboBox *comboAVFormats; + QComboBox *comboVideoCodecs; + QComboBox *comboAudioCodecs; + QPushButton *btnDefaults; + + // file list group + QTableWidget *filesTable; + QPushButton *btnOpenDir; + + // description group + QPushButton *btnPlay, *btnDelete, *btnToYouTube; + QLabel *labelDesc; + QLabel *labelThumbnail; + + // this flag is used to distinguish if cell was changed from code or by user + // (in signal cellChanged) + bool nameChangedFromCode; + + int numRecorders, numUploads; + + private slots: + void changeAVFormat(int index); + void changeUseGameRes(int state); + void changeRecordAudio(int state); + void setDefaultOptions(); + void encodingFinished(bool success); + void updateProgress(float value); + void cellDoubleClicked(int row, int column); + void cellChanged(int row, int column); + void currentCellChanged(int row, int column, int previousRow, int previousColumn); + void playSelectedFile(); + void deleteSelectedFiles(); + void openVideosDirectory(); + void updateFileList(const QString & path); + void uploadToYouTube(); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + void uploadFinished(); +}; + +#endif // PAGE_VIDEOS_H diff -r 13fa53bb3134 -r 01b599d6f72d QTfrontend/util/libav_iteraction.h --- a/QTfrontend/util/libav_iteraction.h Thu Aug 30 22:41:22 2012 +0400 +++ b/QTfrontend/util/libav_iteraction.h Thu Aug 30 20:42:29 2012 +0200 @@ -1,49 +1,49 @@ -/* - * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev - * - * This program 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; version 2 of the License - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#ifndef LIBAV_ITERACTION -#define LIBAV_ITERACTION - -#include - -/** - * @brief Class for interacting with ffmpeg/libav libraries - * - * @see singleton pattern - */ -class LibavIteraction -{ - LibavIteraction(); - -public: - - static LibavIteraction & instance(); - - // fill combo box with known file formats - void fillFormats(QComboBox * pFormats); - - // fill combo boxes with known codecs for given formats - void fillCodecs(const QString & format, QComboBox * pVCodecs, QComboBox * pACodecs); - - QString getExtension(const QString & format); - - // get information about file (duration, resolution etc) in multiline string - QString getFileInfo(const QString & filepath); -}; - -#endif // LIBAV_ITERACTION +/* + * Hedgewars, a free turn based strategy game + * Copyright (c) 2004-2012 Andrey Korotaev + * + * This program 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; version 2 of the License + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef LIBAV_ITERACTION +#define LIBAV_ITERACTION + +#include + +/** + * @brief Class for interacting with ffmpeg/libav libraries + * + * @see singleton pattern + */ +class LibavIteraction +{ + LibavIteraction(); + +public: + + static LibavIteraction & instance(); + + // fill combo box with known file formats + void fillFormats(QComboBox * pFormats); + + // fill combo boxes with known codecs for given formats + void fillCodecs(const QString & format, QComboBox * pVCodecs, QComboBox * pACodecs); + + QString getExtension(const QString & format); + + // get information about file (duration, resolution etc) in multiline string + QString getFileInfo(const QString & filepath); +}; + +#endif // LIBAV_ITERACTION