37 #include <QVBoxLayout> |
37 #include <QVBoxLayout> |
38 #include <QHBoxLayout> |
38 #include <QHBoxLayout> |
39 #include <QFileSystemWatcher> |
39 #include <QFileSystemWatcher> |
40 #include <QDateTime> |
40 #include <QDateTime> |
41 #include <QRegExp> |
41 #include <QRegExp> |
42 #include <QNetworkAccessManager> |
|
43 #include <QNetworkRequest> |
|
44 #include <QNetworkReply> |
|
45 #include <QXmlStreamReader> |
42 #include <QXmlStreamReader> |
46 |
43 |
47 #include "hwconsts.h" |
44 #include "hwconsts.h" |
48 #include "pagevideos.h" |
45 #include "pagevideos.h" |
49 #include "igbox.h" |
46 #include "igbox.h" |
50 #include "LibavInteraction.h" |
47 #include "LibavInteraction.h" |
51 #include "gameuiconfig.h" |
48 #include "gameuiconfig.h" |
52 #include "recorder.h" |
49 #include "recorder.h" |
53 #include "ask_quit.h" |
50 #include "ask_quit.h" |
54 #include "upload_video.h" |
|
55 |
51 |
56 static const QSize ThumbnailSize(350, 350*3/5); |
52 static const QSize ThumbnailSize(350, 350*3/5); |
57 |
53 |
58 // columns in table with list of video files |
54 // columns in table with list of video files |
59 enum VideosColumns |
55 enum VideosColumns |
60 { |
56 { |
61 vcName, |
57 vcName, |
62 vcSize, |
58 vcSize, |
63 vcProgress, // either encoding or uploading |
59 vcProgress, // for encoding |
64 |
60 |
65 vcNumColumns, |
61 vcNumColumns, |
66 }; |
62 }; |
67 |
63 |
68 // this class is used for items in first column in file-table |
64 // this class is used for items in first column in file-table |
178 Qt::LinksAccessibleByMouse | |
171 Qt::LinksAccessibleByMouse | |
179 Qt::LinksAccessibleByKeyboard); |
172 Qt::LinksAccessibleByKeyboard); |
180 labelDesc->setTextFormat(Qt::RichText); |
173 labelDesc->setTextFormat(Qt::RichText); |
181 labelDesc->setWordWrap(true); |
174 labelDesc->setWordWrap(true); |
182 labelDesc->setOpenExternalLinks(true); |
175 labelDesc->setOpenExternalLinks(true); |
183 //labelDesc->setMinimumSize(ThumbnailSize); |
|
184 //pTopDescLayout->addWidget(labelDesc, 1); |
|
185 |
176 |
186 // buttons: play and delete |
177 // buttons: play and delete |
187 btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup); |
178 btnPlay = new QPushButton(QPushButton::tr("Play"), pDescGroup); |
188 btnPlay->setEnabled(false); |
179 btnPlay->setEnabled(false); |
189 btnPlay->setWhatsThis(QPushButton::tr("Play this video")); |
180 btnPlay->setWhatsThis(QPushButton::tr("Play this video")); |
190 pBottomDescLayout->addWidget(btnPlay); |
181 pBottomDescLayout->addWidget(btnPlay); |
191 btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup); |
182 btnDelete = new QPushButton(QPushButton::tr("Delete"), pDescGroup); |
192 btnDelete->setEnabled(false); |
183 btnDelete->setEnabled(false); |
193 btnDelete->setWhatsThis(QPushButton::tr("Delete this video")); |
184 btnDelete->setWhatsThis(QPushButton::tr("Delete this video")); |
194 pBottomDescLayout->addWidget(btnDelete); |
185 pBottomDescLayout->addWidget(btnDelete); |
195 btnToYouTube = new QPushButton(QPushButton::tr("Upload to YouTube"), pDescGroup); |
|
196 btnToYouTube->setEnabled(false); |
|
197 btnToYouTube->setWhatsThis(QPushButton::tr("Upload this video to your YouTube account")); |
|
198 pBottomDescLayout->addWidget(btnToYouTube); |
|
199 |
186 |
200 pDescLayout->addWidget(labelThumbnail, 0); |
187 pDescLayout->addWidget(labelThumbnail, 0); |
201 pDescLayout->addWidget(labelDesc, 0); |
188 pDescLayout->addWidget(labelDesc, 0); |
202 pDescLayout->addLayout(pBottomDescLayout, 0); |
189 pDescLayout->addLayout(pBottomDescLayout, 0); |
203 pDescLayout->addStretch(1); |
190 pDescLayout->addStretch(1); |
218 connect(filesTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClicked(int, int))); |
205 connect(filesTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClicked(int, int))); |
219 connect(filesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int, int))); |
206 connect(filesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int, int))); |
220 connect(filesTable, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged())); |
207 connect(filesTable, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(currentCellChanged())); |
221 connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile())); |
208 connect(btnPlay, SIGNAL(clicked()), this, SLOT(playSelectedFile())); |
222 connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles())); |
209 connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles())); |
223 connect(btnToYouTube, SIGNAL(clicked()), this, SLOT(uploadToYouTube())); |
|
224 connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory())); |
210 connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory())); |
225 } |
211 } |
226 |
212 |
227 PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent), |
213 PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent), |
228 config(0), netManager(0) |
214 config(0) |
229 { |
215 { |
230 nameChangedFromCode = false; |
216 nameChangedFromCode = false; |
231 numRecorders = 0; |
217 numRecorders = 0; |
232 numUploads = 0; |
|
233 initPage(); |
218 initPage(); |
234 } |
219 } |
235 |
220 |
236 void PageVideos::init(GameUIConfig * config) |
221 void PageVideos::init(GameUIConfig * config) |
237 { |
222 { |
489 // nothing is selected => clear description and return |
474 // nothing is selected => clear description and return |
490 labelDesc->clear(); |
475 labelDesc->clear(); |
491 clearThumbnail(); |
476 clearThumbnail(); |
492 btnPlay->setEnabled(false); |
477 btnPlay->setEnabled(false); |
493 btnDelete->setEnabled(false); |
478 btnDelete->setEnabled(false); |
494 btnToYouTube->setEnabled(false); |
|
495 return; |
479 return; |
496 } |
480 } |
497 |
481 |
498 btnPlay->setEnabled(item->ready()); |
482 btnPlay->setEnabled(item->ready()); |
499 btnToYouTube->setEnabled(item->ready()); |
|
500 btnDelete->setEnabled(true); |
483 btnDelete->setEnabled(true); |
501 btnDelete->setText(item->ready()? QPushButton::tr("Delete") : QPushButton::tr("Cancel")); |
484 btnDelete->setText(item->ready()? QPushButton::tr("Delete") : QPushButton::tr("Cancel")); |
502 btnToYouTube->setText(item->pUploading? QPushButton::tr("Cancel uploading") : QPushButton::tr("Upload to YouTube")); |
|
503 |
485 |
504 // construct string with desctiption of this file to display it |
486 // construct string with desctiption of this file to display it |
505 QString desc = item->name + "\n\n"; |
487 QString desc = item->name + "\n\n"; |
506 |
488 |
507 if (!item->ready()) |
489 if (!item->ready()) |
686 { |
653 { |
687 QString path = QDir::toNativeSeparators(cfgdir->absolutePath() + "/Videos"); |
654 QString path = QDir::toNativeSeparators(cfgdir->absolutePath() + "/Videos"); |
688 QDesktopServices::openUrl(QUrl("file:///" + path)); |
655 QDesktopServices::openUrl(QUrl("file:///" + path)); |
689 } |
656 } |
690 |
657 |
691 // clear VideoTemp directory (except for thumbnails and upload links) |
658 // clear VideoTemp directory (except for thumbnails) |
692 void PageVideos::clearTemp() |
659 void PageVideos::clearTemp() |
693 { |
660 { |
694 QDir temp(cfgdir->absolutePath() + "/VideoTemp"); |
661 QDir temp(cfgdir->absolutePath() + "/VideoTemp"); |
695 QStringList files = temp.entryList(QDir::Files); |
662 QStringList files = temp.entryList(QDir::Files); |
696 foreach (const QString& file, files) |
663 foreach (const QString& file, files) |
697 { |
664 { |
698 if (!file.endsWith(".bmp") && !file.endsWith(".png") && !file.endsWith("-url.txt")) |
665 if (!file.endsWith(".bmp") && !file.endsWith(".png")) |
699 temp.remove(file); |
666 temp.remove(file); |
700 } |
667 } |
701 } |
668 } |
702 |
669 |
703 bool PageVideos::tryQuit(HWForm * form) |
670 bool PageVideos::tryQuit(HWForm * form) |
704 { |
671 { |
705 bool quit = true; |
672 bool quit = true; |
706 if (numRecorders != 0 || numUploads != 0) |
673 if (numRecorders != 0) |
707 { |
674 { |
708 // ask user what to do - abort or wait |
675 // ask user what to do - abort or wait |
709 HWAskQuitDialog * askd = new HWAskQuitDialog(this, form); |
676 HWAskQuitDialog * askd = new HWAskQuitDialog(this, form); |
710 askd->deleteLater(); |
677 askd->deleteLater(); |
711 quit = askd->exec(); |
678 quit = askd->exec(); |
769 } |
733 } |
770 addRecorder(pRecorder); |
734 addRecorder(pRecorder); |
771 } |
735 } |
772 } |
736 } |
773 |
737 |
774 VideoItem * PageVideos::itemFromReply(QNetworkReply* reply, int & row) |
|
775 { |
|
776 VideoItem * item = NULL; |
|
777 int count = filesTable->rowCount(); |
|
778 // find corresponding item (maybe there is a better way to implement this?) |
|
779 for (int i = 0; i < count; i++) |
|
780 { |
|
781 item = nameItem(i); |
|
782 if (item->pUploading == reply) |
|
783 { |
|
784 row = i; |
|
785 break; |
|
786 } |
|
787 } |
|
788 return item; |
|
789 } |
|
790 |
|
791 void PageVideos::uploadProgress(qint64 bytesSent, qint64 bytesTotal) |
|
792 { |
|
793 QNetworkReply* reply = (QNetworkReply*)sender(); |
|
794 int row; |
|
795 VideoItem * item = itemFromReply(reply, row); |
|
796 setProgress(row, item, bytesSent*1.0/bytesTotal); |
|
797 } |
|
798 |
|
799 void PageVideos::uploadFinished() |
|
800 { |
|
801 QNetworkReply* reply = (QNetworkReply*)sender(); |
|
802 reply->deleteLater(); |
|
803 |
|
804 int row; |
|
805 VideoItem * item = itemFromReply(reply, row); |
|
806 if (!item) |
|
807 return; |
|
808 |
|
809 item->pUploading = NULL; |
|
810 |
|
811 // extract video id from reply |
|
812 QString videoid; |
|
813 QXmlStreamReader xml(reply); |
|
814 while (!xml.atEnd()) |
|
815 { |
|
816 xml.readNext(); |
|
817 if (xml.qualifiedName() == "yt:videoid") |
|
818 { |
|
819 videoid = xml.readElementText(); |
|
820 break; |
|
821 } |
|
822 } |
|
823 |
|
824 if (!videoid.isEmpty()) |
|
825 { |
|
826 item->uploadUrl = "https://youtu.be/" + videoid; |
|
827 updateDescription(); |
|
828 |
|
829 // save url in file |
|
830 QFile * file = new QFile(cfgdir->absoluteFilePath("VideoTemp/" + item->prefix + "-url.txt"), this); |
|
831 if (file->open(QIODevice::WriteOnly)) |
|
832 { |
|
833 file->write(item->uploadUrl.toUtf8()); |
|
834 file->close(); |
|
835 } |
|
836 } |
|
837 |
|
838 filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar |
|
839 numUploads--; |
|
840 } |
|
841 |
|
842 // this will protect saved youtube password from those who cannot read source code |
|
843 static QString protectPass(QString str) |
|
844 { |
|
845 QByteArray array = str.toUtf8(); |
|
846 for (int i = 0; i < array.size(); i++) |
|
847 array[i] = array[i] ^ 0xC4 ^ i; |
|
848 array = array.toBase64(); |
|
849 return QString::fromLatin1(array.data()); |
|
850 } |
|
851 |
|
852 static QString unprotectPass(QString str) |
|
853 { |
|
854 QByteArray array = QByteArray::fromBase64(str.toLatin1()); |
|
855 for (int i = 0; i < array.size(); i++) |
|
856 array[i] = array[i] ^ 0xC4 ^ i; |
|
857 return QString::fromUtf8(array); |
|
858 } |
|
859 |
|
860 void PageVideos::uploadToYouTube() |
|
861 { |
|
862 int row = filesTable->currentRow(); |
|
863 VideoItem * item = nameItem(row); |
|
864 |
|
865 if (item->pUploading) //Act as 'cancel uploading' button |
|
866 { |
|
867 // ask user if (s)he is serious |
|
868 QMessageBox reallyStopMsg(this); |
|
869 reallyStopMsg.setIcon(QMessageBox::Question); |
|
870 reallyStopMsg.setWindowTitle(QMessageBox::tr("Videos - Are you sure?")); |
|
871 reallyStopMsg.setText(QMessageBox::tr("Do you really want to cancel uploading %1?").arg(item->name)); |
|
872 reallyStopMsg.setWindowModality(Qt::WindowModal); |
|
873 reallyStopMsg.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); |
|
874 |
|
875 if (reallyStopMsg.exec() != QMessageBox::Ok) |
|
876 return; |
|
877 item->pUploading->abort(); |
|
878 btnToYouTube->setText(QPushButton::tr("Upload to YouTube")); |
|
879 filesTable->setCellWidget(row, vcProgress, NULL); // remove progress bar |
|
880 //numUploads--; |
|
881 return; |
|
882 } |
|
883 |
|
884 if (!netManager) |
|
885 netManager = new QNetworkAccessManager(this); |
|
886 |
|
887 HWUploadVideoDialog* dlg = new HWUploadVideoDialog(this, item->name, netManager); |
|
888 dlg->deleteLater(); |
|
889 if (config->value("youtube/save").toBool()) |
|
890 { |
|
891 dlg->cbSave->setChecked(true); |
|
892 dlg->leAccount->setText(config->value("youtube/name").toString()); |
|
893 dlg->lePassword->setText(unprotectPass(config->value("youtube/pswd").toString())); |
|
894 } |
|
895 |
|
896 bool result = dlg->exec(); |
|
897 |
|
898 if (dlg->cbSave->isChecked()) |
|
899 { |
|
900 config->setValue("youtube/save", true); |
|
901 config->setValue("youtube/name", dlg->leAccount->text()); |
|
902 config->setValue("youtube/pswd", protectPass(dlg->lePassword->text())); |
|
903 } |
|
904 else |
|
905 { |
|
906 config->setValue("youtube/save", false); |
|
907 config->setValue("youtube/name", ""); |
|
908 config->setValue("youtube/pswd", ""); |
|
909 } |
|
910 |
|
911 if (!result) |
|
912 return; |
|
913 |
|
914 QNetworkRequest request(QUrl(dlg->location)); |
|
915 request.setRawHeader("Content-Type", "application/octet-stream"); |
|
916 |
|
917 QFile * file = new QFile(item->path(), this); |
|
918 if (!file->open(QIODevice::ReadOnly)) |
|
919 return; |
|
920 |
|
921 // add progress bar |
|
922 QProgressBar * progressBar = new QProgressBar(filesTable); |
|
923 progressBar->setMinimum(0); |
|
924 progressBar->setMaximum(10000); |
|
925 progressBar->setValue(0); |
|
926 // make it different from progress-bar used during encoding (use blue color) |
|
927 progressBar->setStyleSheet("* {color: #00ccff; selection-background-color: #00ccff;}" ); |
|
928 filesTable->setCellWidget(row, vcProgress, progressBar); |
|
929 |
|
930 QNetworkReply* reply = netManager->put(request, file); |
|
931 file->setParent(reply); // automatically close file when needed |
|
932 item->pUploading = reply; |
|
933 connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64))); |
|
934 connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); |
|
935 numUploads++; |
|
936 |
|
937 updateDescription(); |
|
938 } |
|