/* * 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, either version 2 of the License, or * (at your option) any later version. * * 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, see <http://www.gnu.org/licenses/>. */ #include "UBYouTubePublisher.h" #include "frameworks/UBFileSystemUtils.h" #include "core/UBApplication.h" #include "core/UBSettings.h" #include "core/UBSetting.h" #include "gui/UBMainWindow.h" //#include "gui/UBVideoPlayer.h" #include "network/UBNetworkAccessManager.h" #include "network/UBServerXMLHttpRequest.h" #include "core/memcheck.h" // API key linked to account dev.mnemis@gmail.com const QString UBYouTubePublisher::sYouTubeDeveloperKey("AI39si62ga82stA4YBr5JjkfuRsFT-QyC4UYsFn7yYQFMe_dzg8xOc0r91BOhxSEhEr0gdWJGNnDsYbv9wvpyROd2Yre-6Zh7g"); UBYouTubePublisher::UBYouTubePublisher(QObject* pParent) : QObject(pParent) , mAuthRequest(0) , mUploadRequest(0) { // NOOP } UBYouTubePublisher::~UBYouTubePublisher() { // NOOP } void UBYouTubePublisher::uploadVideo(const QString& videoFilePath) { mVideoFilePath = videoFilePath; UBYouTubePublishingDialog pub(videoFilePath, UBApplication::mainWindow); pub.title->setText(QFileInfo(mVideoFilePath).completeBaseName()); pub.keywords->setText(tr("Open-Sankore")); QString defaultEMail = UBSettings::settings()->youTubeUserEMail->get().toString(); pub.email->setText(defaultEMail); QString defaultPassword = UBSettings::settings()->password(defaultEMail); pub.password->setText(defaultPassword); if (pub.exec() == QDialog::Accepted) { mTitle = pub.title->text(); mDescription = pub.description->toPlainText(); mCategories << pub.category->itemData(pub.category->currentIndex()).toString(); mKeywords = pub.keywords->text(); QString email = pub.email->text(); UBSettings::settings()->youTubeUserEMail->set(email); QString password = pub.password->text(); UBSettings::settings()->setPassword(email, password); postClientLoginRequest(email, password); } else { deleteLater(); } } void UBYouTubePublisher::postClientLoginRequest(const QString& userName, const QString& password) { QString mUserName = userName; QUrl url("https://www.google.com/youtube/accounts/ClientLogin"); mAuthRequest = new UBServerXMLHttpRequest(UBNetworkAccessManager::defaultAccessManager() , "application/x-www-form-urlencoded"); // destroyed in postClientLoginResponse connect(mAuthRequest, SIGNAL(finished(bool, const QByteArray&)), this, SLOT(postClientLoginResponse(bool, const QByteArray&))); mAuthRequest->addHeader("X-GData-Key", sYouTubeDeveloperKey); QString payload = QString("Email=%1&Passwd=%2&service=youtube&source=%3") .arg(userName) .arg(password) .arg(tr("OpenSankore")); mAuthRequest->post(url, payload.toUtf8()); } void UBYouTubePublisher::postClientLoginResponse(bool success, const QByteArray& pPayload) { Q_UNUSED(success); mAuthToken = ""; QString auth = QString::fromUtf8(pPayload); if (auth.contains("Auth=")) { QStringList lines = auth.split("\n"); foreach(QString line, lines) { if(line.startsWith("Auth=")) { mAuthToken = line.replace("Auth=", ""); break; } } } mAuthRequest->deleteLater(); mAuthRequest = 0; if(mAuthToken.length() == 0) { UBApplication::showMessage(tr("YouTube authentication failed.")); // success = false; deleteLater(); } else { qDebug() << "Retreived youtube auth token" << mAuthToken; postVideoUploadRequest(); } } void UBYouTubePublisher::postVideoUploadRequest() { /* * POST /feeds/api/users/default/uploads HTTP/1.1 Host: gdata.youtube.com Authorization: GoogleLogin auth=<authentication_token> GData-Version: 2 X-GData-Client: <client_id> X-GData-Key: key=<developer_key> Slug: <video_filename> Content-Type: multipart/related; boundary="<boundary_string>" Content-Length: <content_length> Connection: close --<boundary_string> Content-Type: application/atom+xml; charset=UTF-8 API_XML_request --<boundary_string> Content-Type: <video_content_type> Content-Transfer-Encoding: binary <Binary File Data> --<boundary_string>-- */ QFile videoFile(mVideoFilePath); if(!videoFile.open(QIODevice::ReadOnly)) { qWarning() << "Cannot open file" << mVideoFilePath << "for upload to youtube"; return; } QUrl url("http://uploads.gdata.youtube.com/feeds/api/users/default/uploads"); QString boundary = "---------------------------f93dcbA3"; QString contentType = QString("multipart/related; boundary=\"%1\"").arg(boundary); mUploadRequest = new UBServerXMLHttpRequest(UBNetworkAccessManager::defaultAccessManager() , contentType); // destroyed in postVideoUploadResponse mUploadRequest->setVerbose(true); connect(mUploadRequest, SIGNAL(progress(qint64, qint64)), this, SLOT(progress(qint64, qint64))); connect(mUploadRequest, SIGNAL(finished(bool, const QByteArray&)), this, SLOT(postVideoUploadResponse(bool, const QByteArray&))); mUploadRequest->addHeader("X-GData-Key", "key=" + sYouTubeDeveloperKey); mUploadRequest->addHeader("Authorization", QString("GoogleLogin auth=%1").arg(mAuthToken)); mUploadRequest->addHeader("GData-Version", "2"); QFileInfo fi(mVideoFilePath); mUploadRequest->addHeader("Slug", fi.fileName()); mUploadRequest->addHeader("Connection", "close"); // do we really ned that ? QByteArray payload; payload.append(QString("\n--" + boundary + "\n").toUtf8()) .append(QString("Content-Type: application/atom+xml; charset=UTF-8\n\n").toUtf8()) .append(youtubeMetadata().toUtf8()); payload.append(QString("\n--" + boundary + "\n").toUtf8()); QString videoMimeType = UBFileSystemUtils::mimeTypeFromFileName(mVideoFilePath); payload.append((QString("Content-Type: %1\n").arg(videoMimeType)).toUtf8()) .append(QString("Content-Transfer-Encoding: binary\n\n").toUtf8()); payload.append(videoFile.readAll()); payload.append(QString("\n--" + boundary + "--\n").toUtf8()); mUploadRequest->post(url, payload); } void UBYouTubePublisher::postVideoUploadResponse(bool success, const QByteArray& pPayload) { mUploadRequest->deleteLater(); if(success) { UBApplication::showMessage("The video has been uploaded to youtube", false); } else { qWarning() << "error uploading video to youtube" << QString::fromUtf8(pPayload); UBApplication::showMessage(tr("Error while uploading video to YouTube (%1)").arg(QString::fromUtf8(pPayload)), false); } deleteLater(); } QString UBYouTubePublisher::youtubeMetadata() { QString workingTitle; if (mTitle.length() > 0) { workingTitle = mTitle; } else { workingTitle = QFileInfo(mVideoFilePath).completeBaseName(); } QString metadata; metadata += "<?xml version=\"1.0\"?>\n"; metadata += "<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:media=\"http://search.yahoo.com/mrss/\" xmlns:yt=\"http://gdata.youtube.com/schemas/2007\">\n"; metadata += " <media:group>\n"; metadata += QString(" <media:title type=\"plain\">%1</media:title>\n").arg(workingTitle); QString workingDescription = mDescription; if (workingDescription.length() > 4900) { workingDescription = workingDescription.left(4900) + "..."; } workingDescription += "\n\nhttp://www.open-sankore.org"; if(workingDescription.length() == 0) { workingDescription = workingTitle; } metadata += QString(" <media:description type=\"plain\">%1</media:description>\n").arg(workingDescription); foreach(QString cat, mCategories) { metadata += QString(" <media:category scheme=\"http://gdata.youtube.com/schemas/2007/categories.cat\">%1</media:category>\n").arg(cat); } if (mKeywords.length() > 0) { metadata += QString(" <media:keywords>%1</media:keywords>\n").arg(mKeywords); } metadata += " </media:group>\n"; metadata += "</entry>"; return metadata; } void UBYouTubePublisher::progress (qint64 bytesSent, qint64 bytesTotal ) { int percentage = (((qreal)bytesSent / (qreal)bytesTotal ) * 100); UBApplication::showMessage(tr("Upload to YouTube in progress %1 %").arg(percentage), percentage < 100); } UBYouTubePublishingDialog::UBYouTubePublishingDialog(const QString& videoFilePath, QWidget *parent) : QDialog(parent) { Q_UNUSED(videoFilePath); Ui::YouTubePublishingDialog::setupUi(this); QMap<QString, QString> cats = categories(); category->clear(); int index = 0; foreach(QString cat, cats.keys()) { category->addItem(cats.value(cat), cat); if(cat == "Education") category->setCurrentIndex(index); index++; } connect(dialogButtons, SIGNAL(accepted()), this, SLOT(accept())); connect(dialogButtons, SIGNAL(rejected()), this, SLOT(reject())); connect(title, SIGNAL(textChanged(const QString&)), this, SLOT(updateUIState(const QString&))); connect(description, SIGNAL(textChanged()), this, SLOT(updateUIState())); connect(keywords, SIGNAL(textChanged(const QString&)), this, SLOT(updateUIState(const QString&))); connect(email, SIGNAL(textChanged(const QString&)), this, SLOT(updateUIState(const QString&))); connect(password, SIGNAL(textChanged(const QString&)), this, SLOT(updateUIState(const QString&))); dialogButtons->button(QDialogButtonBox::Ok)->setEnabled(false); dialogButtons->button(QDialogButtonBox::Ok)->setText(tr("Upload")); } void UBYouTubePublishingDialog::updateUIState(const QString& string) { Q_UNUSED(string); bool ok = title->text().length() > 0 && description->toPlainText().length() > 0 && keywords->text().length() > 0 && email->text().length() > 0 && password->text().length() > 0; dialogButtons->button(QDialogButtonBox::Ok)->setEnabled(ok); } QMap<QString, QString> UBYouTubePublishingDialog::categories() { // TODO UB 4.x download localized list from // http://code.google.com/apis/youtube/2.0/reference.html#Localized_Category_Lists QMap<QString, QString> cats; cats.insert("Autos", tr("Autos & Vehicles")); cats.insert("Music", tr("Music")); cats.insert("Animals", tr("Pets & Animals")); cats.insert("Sports", tr("Sports")); cats.insert("Travel", tr("Travel & Events")); cats.insert("Games", tr("Gaming")); cats.insert("Comedy", tr("Comedy")); cats.insert("People", tr("People & Blogs")); cats.insert("News", tr("News & Politics")); cats.insert("Entertainment", tr("Entertainment")); cats.insert("Education", tr("Education")); cats.insert("Howto", tr("Howto & Style")); cats.insert("Nonprofit", tr("Nonprofits & Activism")); cats.insert("Tech", tr("Science & Technology")); return cats; }