| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 | 
							- #include "window-basic-main.hpp"
 
- #include "window-youtube-actions.hpp"
 
- #include "obs-app.hpp"
 
- #include "qt-wrappers.hpp"
 
- #include "youtube-api-wrappers.hpp"
 
- #include <QToolTip>
 
- #include <QDateTime>
 
- #include <QDesktopServices>
 
- #include <QFileInfo>
 
- #include <QStandardPaths>
 
- #include <QImageReader>
 
- const QString SchedulDateAndTimeFormat = "yyyy-MM-dd'T'hh:mm:ss'Z'";
 
- const QString RepresentSchedulDateAndTimeFormat = "dddd, MMMM d, yyyy h:m";
 
- const QString IndexOfGamingCategory = "20";
 
- OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth,
 
- 				     bool broadcastReady)
 
- 	: QDialog(parent),
 
- 	  ui(new Ui::OBSYoutubeActions),
 
- 	  apiYouTube(dynamic_cast<YoutubeApiWrappers *>(auth)),
 
- 	  workerThread(new WorkerThread(apiYouTube)),
 
- 	  broadcastReady(broadcastReady)
 
- {
 
- 	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
 
- 	ui->setupUi(this);
 
- 	ui->privacyBox->addItem(QTStr("YouTube.Actions.Privacy.Public"),
 
- 				"public");
 
- 	ui->privacyBox->addItem(QTStr("YouTube.Actions.Privacy.Unlisted"),
 
- 				"unlisted");
 
- 	ui->privacyBox->addItem(QTStr("YouTube.Actions.Privacy.Private"),
 
- 				"private");
 
- 	ui->latencyBox->addItem(QTStr("YouTube.Actions.Latency.Normal"),
 
- 				"normal");
 
- 	ui->latencyBox->addItem(QTStr("YouTube.Actions.Latency.Low"), "low");
 
- 	ui->latencyBox->addItem(QTStr("YouTube.Actions.Latency.UltraLow"),
 
- 				"ultraLow");
 
- 	UpdateOkButtonStatus();
 
- 	connect(ui->title, &QLineEdit::textChanged, this,
 
- 		[&](const QString &) { this->UpdateOkButtonStatus(); });
 
- 	connect(ui->privacyBox, &QComboBox::currentTextChanged, this,
 
- 		[&](const QString &) { this->UpdateOkButtonStatus(); });
 
- 	connect(ui->yesMakeForKids, &QRadioButton::toggled, this,
 
- 		[&](bool) { this->UpdateOkButtonStatus(); });
 
- 	connect(ui->notMakeForKids, &QRadioButton::toggled, this,
 
- 		[&](bool) { this->UpdateOkButtonStatus(); });
 
- 	connect(ui->tabWidget, &QTabWidget::currentChanged, this,
 
- 		[&](int) { this->UpdateOkButtonStatus(); });
 
- 	connect(ui->pushButton, &QPushButton::clicked, this,
 
- 		&OBSYoutubeActions::OpenYouTubeDashboard);
 
- 	connect(ui->helpAutoStartStop, &QLabel::linkActivated, this,
 
- 		[](const QString &) {
 
- 			QToolTip::showText(
 
- 				QCursor::pos(),
 
- 				QTStr("YouTube.Actions.AutoStartStop.TT"));
 
- 		});
 
- 	connect(ui->help360Video, &QLabel::linkActivated, this,
 
- 		[](const QString &link) { QDesktopServices::openUrl(link); });
 
- 	connect(ui->helpMadeForKids, &QLabel::linkActivated, this,
 
- 		[](const QString &link) { QDesktopServices::openUrl(link); });
 
- 	ui->scheduledTime->setVisible(false);
 
- 	connect(ui->checkScheduledLater, &QCheckBox::stateChanged, this,
 
- 		[&](int state) {
 
- 			ui->scheduledTime->setVisible(state);
 
- 			if (state) {
 
- 				ui->checkAutoStart->setVisible(true);
 
- 				ui->checkAutoStop->setVisible(true);
 
- 				ui->helpAutoStartStop->setVisible(true);
 
- 				ui->checkAutoStart->setChecked(false);
 
- 				ui->checkAutoStop->setChecked(false);
 
- 			} else {
 
- 				ui->checkAutoStart->setVisible(false);
 
- 				ui->checkAutoStop->setVisible(false);
 
- 				ui->helpAutoStartStop->setVisible(false);
 
- 				ui->checkAutoStart->setChecked(true);
 
- 				ui->checkAutoStop->setChecked(true);
 
- 			}
 
- 			UpdateOkButtonStatus();
 
- 		});
 
- 	ui->checkAutoStart->setVisible(false);
 
- 	ui->checkAutoStop->setVisible(false);
 
- 	ui->helpAutoStartStop->setVisible(false);
 
- 	ui->scheduledTime->setDateTime(QDateTime::currentDateTime());
 
- 	auto thumbSelectionHandler = [&]() {
 
- 		if (thumbnailFile.isEmpty()) {
 
- 			QString filePath = OpenFile(
 
- 				this,
 
- 				QTStr("YouTube.Actions.Thumbnail.SelectFile"),
 
- 				QStandardPaths::writableLocation(
 
- 					QStandardPaths::PicturesLocation),
 
- 				QString("Images (*.png *.jpg *.jpeg *.gif)"));
 
- 			if (!filePath.isEmpty()) {
 
- 				QFileInfo tFile(filePath);
 
- 				if (!tFile.exists()) {
 
- 					return ShowErrorDialog(
 
- 						this,
 
- 						QTStr("YouTube.Actions.Error.FileMissing"));
 
- 				} else if (tFile.size() > 2 * 1024 * 1024) {
 
- 					return ShowErrorDialog(
 
- 						this,
 
- 						QTStr("YouTube.Actions.Error.FileTooLarge"));
 
- 				}
 
- 				thumbnailFile = filePath;
 
- 				ui->selectedFileName->setText(thumbnailFile);
 
- 				ui->selectFileButton->setText(QTStr(
 
- 					"YouTube.Actions.Thumbnail.ClearFile"));
 
- 				QImageReader imgReader(filePath);
 
- 				imgReader.setAutoTransform(true);
 
- 				const QImage newImage = imgReader.read();
 
- 				ui->thumbnailPreview->setPixmap(
 
- 					QPixmap::fromImage(newImage).scaled(
 
- 						160, 90, Qt::KeepAspectRatio));
 
- 			}
 
- 		} else {
 
- 			thumbnailFile.clear();
 
- 			ui->selectedFileName->setText(QTStr(
 
- 				"YouTube.Actions.Thumbnail.NoFileSelected"));
 
- 			ui->selectFileButton->setText(
 
- 				QTStr("YouTube.Actions.Thumbnail.SelectFile"));
 
- 			ui->thumbnailPreview->setPixmap(
 
- 				GetPlaceholder().pixmap(QSize(16, 16)));
 
- 		}
 
- 	};
 
- 	connect(ui->selectFileButton, &QPushButton::clicked, this,
 
- 		thumbSelectionHandler);
 
- 	connect(ui->thumbnailPreview, &ClickableLabel::clicked, this,
 
- 		thumbSelectionHandler);
 
- 	if (!apiYouTube) {
 
- 		blog(LOG_DEBUG, "YouTube API auth NOT found.");
 
- 		Cancel();
 
- 		return;
 
- 	}
 
- 	const char *name = config_get_string(OBSBasic::Get()->Config(),
 
- 					     "YouTube", "ChannelName");
 
- 	this->setWindowTitle(QTStr("YouTube.Actions.WindowTitle").arg(name));
 
- 	QVector<CategoryDescription> category_list;
 
- 	if (!apiYouTube->GetVideoCategoriesList(category_list)) {
 
- 		ShowErrorDialog(
 
- 			parent,
 
- 			apiYouTube->GetLastError().isEmpty()
 
- 				? QTStr("YouTube.Actions.Error.General")
 
- 				: QTStr("YouTube.Actions.Error.Text")
 
- 					  .arg(apiYouTube->GetLastError()));
 
- 		Cancel();
 
- 		return;
 
- 	}
 
- 	for (auto &category : category_list) {
 
- 		ui->categoryBox->addItem(category.title, category.id);
 
- 		if (category.id == IndexOfGamingCategory) {
 
- 			ui->categoryBox->setCurrentText(category.title);
 
- 		}
 
- 	}
 
- 	connect(ui->okButton, &QPushButton::clicked, this,
 
- 		&OBSYoutubeActions::InitBroadcast);
 
- 	connect(ui->saveButton, &QPushButton::clicked, this,
 
- 		&OBSYoutubeActions::ReadyBroadcast);
 
- 	connect(ui->cancelButton, &QPushButton::clicked, this, [&]() {
 
- 		blog(LOG_DEBUG, "YouTube live broadcast creation cancelled.");
 
- 		// Close the dialog.
 
- 		Cancel();
 
- 	});
 
- 	qDeleteAll(ui->scrollAreaWidgetContents->findChildren<QWidget *>(
 
- 		QString(), Qt::FindDirectChildrenOnly));
 
- 	// Add label indicating loading state
 
- 	QLabel *loadingLabel = new QLabel();
 
- 	loadingLabel->setTextFormat(Qt::RichText);
 
- 	loadingLabel->setAlignment(Qt::AlignHCenter);
 
- 	loadingLabel->setText(
 
- 		QString("<big>%1</big>")
 
- 			.arg(QTStr("YouTube.Actions.EventsLoading")));
 
- 	ui->scrollAreaWidgetContents->layout()->addWidget(loadingLabel);
 
- 	// Delete "loading..." label on completion
 
- 	connect(workerThread, &WorkerThread::finished, this, [&] {
 
- 		QLayoutItem *item =
 
- 			ui->scrollAreaWidgetContents->layout()->takeAt(0);
 
- 		item->widget()->deleteLater();
 
- 	});
 
- 	connect(workerThread, &WorkerThread::failed, this, [&]() {
 
- 		auto last_error = apiYouTube->GetLastError();
 
- 		if (last_error.isEmpty())
 
- 			last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
 
- 		if (!apiYouTube->GetTranslatedError(last_error))
 
- 			last_error = QTStr("YouTube.Actions.Error.Text")
 
- 					     .arg(last_error);
 
- 		ShowErrorDialog(this, last_error);
 
- 		QDialog::reject();
 
- 	});
 
- 	connect(workerThread, &WorkerThread::new_item, this,
 
- 		[&](const QString &title, const QString &dateTimeString,
 
- 		    const QString &broadcast, const QString &status,
 
- 		    bool astart, bool astop) {
 
- 			ClickableLabel *label = new ClickableLabel();
 
- 			label->setTextFormat(Qt::RichText);
 
- 			if (status == "live" || status == "testing") {
 
- 				// Resumable stream
 
- 				label->setText(
 
- 					QString("<big>%1</big><br/>%2")
 
- 						.arg(title,
 
- 						     QTStr("YouTube.Actions.Stream.Resume")));
 
- 			} else if (dateTimeString.isEmpty()) {
 
- 				// The broadcast created by YouTube Studio has no start time.
 
- 				// Yes this does violate the restrictions set in YouTube's API
 
- 				// But why would YouTube care about consistency?
 
- 				label->setText(
 
- 					QString("<big>%1</big><br/>%2")
 
- 						.arg(title,
 
- 						     QTStr("YouTube.Actions.Stream.YTStudio")));
 
- 			} else {
 
- 				label->setText(
 
- 					QString("<big>%1</big><br/>%2")
 
- 						.arg(title,
 
- 						     QTStr("YouTube.Actions.Stream.ScheduledFor")
 
- 							     .arg(dateTimeString)));
 
- 			}
 
- 			label->setAlignment(Qt::AlignHCenter);
 
- 			label->setMargin(4);
 
- 			connect(label, &ClickableLabel::clicked, this,
 
- 				[&, label, broadcast, astart, astop]() {
 
- 					for (QWidget *i :
 
- 					     ui->scrollAreaWidgetContents->findChildren<
 
- 						     QWidget *>(
 
- 						     QString(),
 
- 						     Qt::FindDirectChildrenOnly)) {
 
- 						i->setProperty(
 
- 							"isSelectedEvent",
 
- 							"false");
 
- 						i->style()->unpolish(i);
 
- 						i->style()->polish(i);
 
- 					}
 
- 					label->setProperty("isSelectedEvent",
 
- 							   "true");
 
- 					label->style()->unpolish(label);
 
- 					label->style()->polish(label);
 
- 					this->selectedBroadcast = broadcast;
 
- 					this->autostart = astart;
 
- 					this->autostop = astop;
 
- 					UpdateOkButtonStatus();
 
- 				});
 
- 			ui->scrollAreaWidgetContents->layout()->addWidget(
 
- 				label);
 
- 			if (selectedBroadcast == broadcast)
 
- 				label->clicked();
 
- 		});
 
- 	workerThread->start();
 
- 	OBSBasic *main = OBSBasic::Get();
 
- 	bool rememberSettings = config_get_bool(main->basicConfig, "YouTube",
 
- 						"RememberSettings");
 
- 	if (rememberSettings)
 
- 		LoadSettings();
 
- 	// Switch to events page and select readied broadcast once loaded
 
- 	if (broadcastReady) {
 
- 		ui->tabWidget->setCurrentIndex(1);
 
- 		selectedBroadcast = apiYouTube->GetBroadcastId();
 
- 	}
 
- #ifdef __APPLE__
 
- 	// MacOS theming issues
 
- 	this->resize(this->width() + 200, this->height() + 120);
 
- #endif
 
- 	valid = true;
 
- }
 
- void OBSYoutubeActions::showEvent(QShowEvent *event)
 
- {
 
- 	QDialog::showEvent(event);
 
- 	if (thumbnailFile.isEmpty())
 
- 		ui->thumbnailPreview->setPixmap(
 
- 			GetPlaceholder().pixmap(QSize(16, 16)));
 
- }
 
- OBSYoutubeActions::~OBSYoutubeActions()
 
- {
 
- 	workerThread->stop();
 
- 	workerThread->wait();
 
- 	delete workerThread;
 
- }
 
- void WorkerThread::run()
 
- {
 
- 	if (!pending)
 
- 		return;
 
- 	json11::Json broadcasts;
 
- 	for (QString broadcastStatus : {"active", "upcoming"}) {
 
- 		if (!apiYouTube->GetBroadcastsList(broadcasts, "",
 
- 						   broadcastStatus)) {
 
- 			emit failed();
 
- 			return;
 
- 		}
 
- 		while (pending) {
 
- 			auto items = broadcasts["items"].array_items();
 
- 			for (auto item : items) {
 
- 				QString status = QString::fromStdString(
 
- 					item["status"]["lifeCycleStatus"]
 
- 						.string_value());
 
- 				if (status == "live" || status == "testing") {
 
- 					// Check that the attached liveStream is offline (reconnectable)
 
- 					QString stream_id = QString::fromStdString(
 
- 						item["contentDetails"]
 
- 						    ["boundStreamId"]
 
- 							    .string_value());
 
- 					json11::Json stream;
 
- 					if (!apiYouTube->FindStream(stream_id,
 
- 								    stream))
 
- 						continue;
 
- 					if (stream["status"]["streamStatus"] ==
 
- 					    "active")
 
- 						continue;
 
- 				}
 
- 				QString title = QString::fromStdString(
 
- 					item["snippet"]["title"].string_value());
 
- 				QString scheduledStartTime =
 
- 					QString::fromStdString(
 
- 						item["snippet"]
 
- 						    ["scheduledStartTime"]
 
- 							    .string_value());
 
- 				QString broadcast = QString::fromStdString(
 
- 					item["id"].string_value());
 
- 				// Treat already started streams as autostart for UI purposes
 
- 				bool astart =
 
- 					status == "live" ||
 
- 					item["contentDetails"]["enableAutoStart"]
 
- 						.bool_value();
 
- 				bool astop =
 
- 					item["contentDetails"]["enableAutoStop"]
 
- 						.bool_value();
 
- 				QDateTime utcDTime = QDateTime::fromString(
 
- 					scheduledStartTime,
 
- 					SchedulDateAndTimeFormat);
 
- 				// DateTime parser means that input datetime is a local, so we need to move it
 
- 				QDateTime dateTime = utcDTime.addSecs(
 
- 					utcDTime.offsetFromUtc());
 
- 				QString dateTimeString = QLocale().toString(
 
- 					dateTime,
 
- 					QString("%1  %2").arg(
 
- 						QLocale().dateFormat(
 
- 							QLocale::LongFormat),
 
- 						QLocale().timeFormat(
 
- 							QLocale::ShortFormat)));
 
- 				emit new_item(title, dateTimeString, broadcast,
 
- 					      status, astart, astop);
 
- 			}
 
- 			auto nextPageToken =
 
- 				broadcasts["nextPageToken"].string_value();
 
- 			if (nextPageToken.empty() || items.empty())
 
- 				break;
 
- 			else {
 
- 				if (!pending)
 
- 					return;
 
- 				if (!apiYouTube->GetBroadcastsList(
 
- 					    broadcasts,
 
- 					    QString::fromStdString(
 
- 						    nextPageToken),
 
- 					    broadcastStatus)) {
 
- 					emit failed();
 
- 					return;
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	emit ready();
 
- }
 
- void OBSYoutubeActions::UpdateOkButtonStatus()
 
- {
 
- 	bool enable = false;
 
- 	if (ui->tabWidget->currentIndex() == 0) {
 
- 		enable = !ui->title->text().isEmpty() &&
 
- 			 !ui->privacyBox->currentText().isEmpty() &&
 
- 			 (ui->yesMakeForKids->isChecked() ||
 
- 			  ui->notMakeForKids->isChecked());
 
- 		ui->okButton->setEnabled(enable);
 
- 		ui->saveButton->setEnabled(enable);
 
- 		if (ui->checkScheduledLater->checkState() == Qt::Checked) {
 
- 			ui->okButton->setText(
 
- 				QTStr("YouTube.Actions.Create_Schedule"));
 
- 			ui->saveButton->setText(
 
- 				QTStr("YouTube.Actions.Create_Schedule_Ready"));
 
- 		} else {
 
- 			ui->okButton->setText(
 
- 				QTStr("YouTube.Actions.Create_GoLive"));
 
- 			ui->saveButton->setText(
 
- 				QTStr("YouTube.Actions.Create_Ready"));
 
- 		}
 
- 		ui->pushButton->setVisible(false);
 
- 	} else {
 
- 		enable = !selectedBroadcast.isEmpty();
 
- 		ui->okButton->setEnabled(enable);
 
- 		ui->saveButton->setEnabled(enable);
 
- 		ui->okButton->setText(QTStr("YouTube.Actions.Choose_GoLive"));
 
- 		ui->saveButton->setText(QTStr("YouTube.Actions.Choose_Ready"));
 
- 		ui->pushButton->setVisible(true);
 
- 	}
 
- }
 
- bool OBSYoutubeActions::CreateEventAction(YoutubeApiWrappers *api,
 
- 					  StreamDescription &stream,
 
- 					  bool stream_later,
 
- 					  bool ready_broadcast)
 
- {
 
- 	YoutubeApiWrappers *apiYouTube = api;
 
- 	BroadcastDescription broadcast = {};
 
- 	UiToBroadcast(broadcast);
 
- 	if (stream_later) {
 
- 		// DateTime parser means that input datetime is a local, so we need to move it
 
- 		auto dateTime = ui->scheduledTime->dateTime();
 
- 		auto utcDTime = dateTime.addSecs(-dateTime.offsetFromUtc());
 
- 		broadcast.schedul_date_time =
 
- 			utcDTime.toString(SchedulDateAndTimeFormat);
 
- 	} else {
 
- 		// stream now is always autostart/autostop
 
- 		broadcast.auto_start = true;
 
- 		broadcast.auto_stop = true;
 
- 		broadcast.schedul_date_time =
 
- 			QDateTime::currentDateTimeUtc().toString(
 
- 				SchedulDateAndTimeFormat);
 
- 	}
 
- 	autostart = broadcast.auto_start;
 
- 	autostop = broadcast.auto_stop;
 
- 	blog(LOG_DEBUG, "Scheduled date and time: %s",
 
- 	     broadcast.schedul_date_time.toStdString().c_str());
 
- 	if (!apiYouTube->InsertBroadcast(broadcast)) {
 
- 		blog(LOG_DEBUG, "No broadcast created.");
 
- 		return false;
 
- 	}
 
- 	if (!apiYouTube->SetVideoCategory(broadcast.id, broadcast.title,
 
- 					  broadcast.description,
 
- 					  broadcast.category.id)) {
 
- 		blog(LOG_DEBUG, "No category set.");
 
- 		return false;
 
- 	}
 
- 	if (!thumbnailFile.isEmpty()) {
 
- 		blog(LOG_INFO, "Uploading thumbnail file \"%s\"...",
 
- 		     thumbnailFile.toStdString().c_str());
 
- 		if (!apiYouTube->SetVideoThumbnail(broadcast.id,
 
- 						   thumbnailFile)) {
 
- 			blog(LOG_DEBUG, "No thumbnail set.");
 
- 			return false;
 
- 		}
 
- 	}
 
- 	if (!stream_later || ready_broadcast) {
 
- 		stream = {"", "", "OBS Studio Video Stream"};
 
- 		if (!apiYouTube->InsertStream(stream)) {
 
- 			blog(LOG_DEBUG, "No stream created.");
 
- 			return false;
 
- 		}
 
- 		json11::Json json;
 
- 		if (!apiYouTube->BindStream(broadcast.id, stream.id, json)) {
 
- 			blog(LOG_DEBUG, "No stream binded.");
 
- 			return false;
 
- 		}
 
- 		if (broadcast.privacy != "private") {
 
- 			const std::string apiLiveChatId =
 
- 				json["snippet"]["liveChatId"].string_value();
 
- 			apiYouTube->SetChatId(broadcast.id, apiLiveChatId);
 
- 		} else {
 
- 			apiYouTube->ResetChat();
 
- 		}
 
- 	}
 
- 	return true;
 
- }
 
- bool OBSYoutubeActions::ChooseAnEventAction(YoutubeApiWrappers *api,
 
- 					    StreamDescription &stream)
 
- {
 
- 	YoutubeApiWrappers *apiYouTube = api;
 
- 	json11::Json json;
 
- 	if (!apiYouTube->FindBroadcast(selectedBroadcast, json)) {
 
- 		blog(LOG_DEBUG, "No broadcast found.");
 
- 		return false;
 
- 	}
 
- 	std::string boundStreamId =
 
- 		json["items"]
 
- 			.array_items()[0]["contentDetails"]["boundStreamId"]
 
- 			.string_value();
 
- 	std::string broadcastPrivacy =
 
- 		json["items"]
 
- 			.array_items()[0]["status"]["privacyStatus"]
 
- 			.string_value();
 
- 	std::string apiLiveChatId =
 
- 		json["items"]
 
- 			.array_items()[0]["snippet"]["liveChatId"]
 
- 			.string_value();
 
- 	stream.id = boundStreamId.c_str();
 
- 	if (!stream.id.isEmpty() && apiYouTube->FindStream(stream.id, json)) {
 
- 		auto item = json["items"].array_items()[0];
 
- 		auto streamName = item["cdn"]["ingestionInfo"]["streamName"]
 
- 					  .string_value();
 
- 		auto title = item["snippet"]["title"].string_value();
 
- 		stream.name = streamName.c_str();
 
- 		stream.title = title.c_str();
 
- 		api->SetBroadcastId(selectedBroadcast);
 
- 	} else {
 
- 		stream = {"", "", "OBS Studio Video Stream"};
 
- 		if (!apiYouTube->InsertStream(stream)) {
 
- 			blog(LOG_DEBUG, "No stream created.");
 
- 			return false;
 
- 		}
 
- 		if (!apiYouTube->BindStream(selectedBroadcast, stream.id,
 
- 					    json)) {
 
- 			blog(LOG_DEBUG, "No stream binded.");
 
- 			return false;
 
- 		}
 
- 	}
 
- 	if (broadcastPrivacy != "private")
 
- 		apiYouTube->SetChatId(selectedBroadcast, apiLiveChatId);
 
- 	else
 
- 		apiYouTube->ResetChat();
 
- 	return true;
 
- }
 
- void OBSYoutubeActions::ShowErrorDialog(QWidget *parent, QString text)
 
- {
 
- 	QMessageBox dlg(parent);
 
- 	dlg.setWindowFlags(dlg.windowFlags() & ~Qt::WindowCloseButtonHint);
 
- 	dlg.setWindowTitle(QTStr("YouTube.Actions.Error.Title"));
 
- 	dlg.setText(text);
 
- 	dlg.setTextFormat(Qt::RichText);
 
- 	dlg.setIcon(QMessageBox::Warning);
 
- 	dlg.setStandardButtons(QMessageBox::StandardButton::Ok);
 
- 	dlg.exec();
 
- }
 
- void OBSYoutubeActions::InitBroadcast()
 
- {
 
- 	StreamDescription stream;
 
- 	QMessageBox msgBox(this);
 
- 	msgBox.setWindowFlags(msgBox.windowFlags() &
 
- 			      ~Qt::WindowCloseButtonHint);
 
- 	msgBox.setWindowTitle(QTStr("YouTube.Actions.Notify.Title"));
 
- 	msgBox.setText(QTStr("YouTube.Actions.Notify.CreatingBroadcast"));
 
- 	msgBox.setStandardButtons(QMessageBox::StandardButtons());
 
- 	bool success = false;
 
- 	auto action = [&]() {
 
- 		if (ui->tabWidget->currentIndex() == 0) {
 
- 			success = this->CreateEventAction(
 
- 				apiYouTube, stream,
 
- 				ui->checkScheduledLater->isChecked());
 
- 		} else {
 
- 			success = this->ChooseAnEventAction(apiYouTube, stream);
 
- 		};
 
- 		QMetaObject::invokeMethod(&msgBox, "accept",
 
- 					  Qt::QueuedConnection);
 
- 	};
 
- 	QScopedPointer<QThread> thread(CreateQThread(action));
 
- 	thread->start();
 
- 	msgBox.exec();
 
- 	thread->wait();
 
- 	if (success) {
 
- 		if (ui->tabWidget->currentIndex() == 0) {
 
- 			// Stream later usecase.
 
- 			if (ui->checkScheduledLater->isChecked()) {
 
- 				QMessageBox msg(this);
 
- 				msg.setWindowTitle(QTStr(
 
- 					"YouTube.Actions.EventCreated.Title"));
 
- 				msg.setText(QTStr(
 
- 					"YouTube.Actions.EventCreated.Text"));
 
- 				msg.setStandardButtons(QMessageBox::Ok);
 
- 				msg.exec();
 
- 				// Close dialog without start streaming.
 
- 				Cancel();
 
- 			} else {
 
- 				// Stream now usecase.
 
- 				blog(LOG_DEBUG, "New valid stream: %s",
 
- 				     QT_TO_UTF8(stream.name));
 
- 				emit ok(QT_TO_UTF8(stream.id),
 
- 					QT_TO_UTF8(stream.name), true, true,
 
- 					true);
 
- 				Accept();
 
- 			}
 
- 		} else {
 
- 			// Stream to precreated broadcast usecase.
 
- 			emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
 
- 				autostart, autostop, true);
 
- 			Accept();
 
- 		}
 
- 	} else {
 
- 		// Fail.
 
- 		auto last_error = apiYouTube->GetLastError();
 
- 		if (last_error.isEmpty())
 
- 			last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
 
- 		if (!apiYouTube->GetTranslatedError(last_error))
 
- 			last_error =
 
- 				QTStr("YouTube.Actions.Error.NoBroadcastCreated")
 
- 					.arg(last_error);
 
- 		ShowErrorDialog(this, last_error);
 
- 	}
 
- }
 
- void OBSYoutubeActions::ReadyBroadcast()
 
- {
 
- 	StreamDescription stream;
 
- 	QMessageBox msgBox(this);
 
- 	msgBox.setWindowFlags(msgBox.windowFlags() &
 
- 			      ~Qt::WindowCloseButtonHint);
 
- 	msgBox.setWindowTitle(QTStr("YouTube.Actions.Notify.Title"));
 
- 	msgBox.setText(QTStr("YouTube.Actions.Notify.CreatingBroadcast"));
 
- 	msgBox.setStandardButtons(QMessageBox::StandardButtons());
 
- 	bool success = false;
 
- 	auto action = [&]() {
 
- 		if (ui->tabWidget->currentIndex() == 0) {
 
- 			success = this->CreateEventAction(
 
- 				apiYouTube, stream,
 
- 				ui->checkScheduledLater->isChecked(), true);
 
- 		} else {
 
- 			success = this->ChooseAnEventAction(apiYouTube, stream);
 
- 		};
 
- 		QMetaObject::invokeMethod(&msgBox, "accept",
 
- 					  Qt::QueuedConnection);
 
- 	};
 
- 	QScopedPointer<QThread> thread(CreateQThread(action));
 
- 	thread->start();
 
- 	msgBox.exec();
 
- 	thread->wait();
 
- 	if (success) {
 
- 		emit ok(QT_TO_UTF8(stream.id), QT_TO_UTF8(stream.name),
 
- 			autostart, autostop, false);
 
- 		Accept();
 
- 	} else {
 
- 		// Fail.
 
- 		auto last_error = apiYouTube->GetLastError();
 
- 		if (last_error.isEmpty())
 
- 			last_error = QTStr("YouTube.Actions.Error.YouTubeApi");
 
- 		if (!apiYouTube->GetTranslatedError(last_error))
 
- 			last_error =
 
- 				QTStr("YouTube.Actions.Error.NoBroadcastCreated")
 
- 					.arg(last_error);
 
- 		ShowErrorDialog(this, last_error);
 
- 	}
 
- }
 
- void OBSYoutubeActions::UiToBroadcast(BroadcastDescription &broadcast)
 
- {
 
- 	broadcast.title = ui->title->text();
 
- 	// ToDo: UI warning rather than silent truncation
 
- 	broadcast.description = ui->description->toPlainText().left(5000);
 
- 	broadcast.privacy = ui->privacyBox->currentData().toString();
 
- 	broadcast.category.title = ui->categoryBox->currentText();
 
- 	broadcast.category.id = ui->categoryBox->currentData().toString();
 
- 	broadcast.made_for_kids = ui->yesMakeForKids->isChecked();
 
- 	broadcast.latency = ui->latencyBox->currentData().toString();
 
- 	broadcast.auto_start = ui->checkAutoStart->isChecked();
 
- 	broadcast.auto_stop = ui->checkAutoStop->isChecked();
 
- 	broadcast.dvr = ui->checkDVR->isChecked();
 
- 	broadcast.schedul_for_later = ui->checkScheduledLater->isChecked();
 
- 	broadcast.projection = ui->check360Video->isChecked() ? "360"
 
- 							      : "rectangular";
 
- 	if (ui->checkRememberSettings->isChecked())
 
- 		SaveSettings(broadcast);
 
- }
 
- void OBSYoutubeActions::SaveSettings(BroadcastDescription &broadcast)
 
- {
 
- 	OBSBasic *main = OBSBasic::Get();
 
- 	config_set_string(main->basicConfig, "YouTube", "Title",
 
- 			  QT_TO_UTF8(broadcast.title));
 
- 	config_set_string(main->basicConfig, "YouTube", "Description",
 
- 			  QT_TO_UTF8(broadcast.description));
 
- 	config_set_string(main->basicConfig, "YouTube", "Privacy",
 
- 			  QT_TO_UTF8(broadcast.privacy));
 
- 	config_set_string(main->basicConfig, "YouTube", "CategoryID",
 
- 			  QT_TO_UTF8(broadcast.category.id));
 
- 	config_set_string(main->basicConfig, "YouTube", "Latency",
 
- 			  QT_TO_UTF8(broadcast.latency));
 
- 	config_set_bool(main->basicConfig, "YouTube", "MadeForKids",
 
- 			broadcast.made_for_kids);
 
- 	config_set_bool(main->basicConfig, "YouTube", "AutoStart",
 
- 			broadcast.auto_start);
 
- 	config_set_bool(main->basicConfig, "YouTube", "AutoStop",
 
- 			broadcast.auto_start);
 
- 	config_set_bool(main->basicConfig, "YouTube", "DVR", broadcast.dvr);
 
- 	config_set_bool(main->basicConfig, "YouTube", "ScheduleForLater",
 
- 			broadcast.schedul_for_later);
 
- 	config_set_string(main->basicConfig, "YouTube", "Projection",
 
- 			  QT_TO_UTF8(broadcast.projection));
 
- 	config_set_string(main->basicConfig, "YouTube", "ThumbnailFile",
 
- 			  QT_TO_UTF8(thumbnailFile));
 
- 	config_set_bool(main->basicConfig, "YouTube", "RememberSettings", true);
 
- }
 
- void OBSYoutubeActions::LoadSettings()
 
- {
 
- 	OBSBasic *main = OBSBasic::Get();
 
- 	const char *title =
 
- 		config_get_string(main->basicConfig, "YouTube", "Title");
 
- 	ui->title->setText(QT_UTF8(title));
 
- 	const char *desc =
 
- 		config_get_string(main->basicConfig, "YouTube", "Description");
 
- 	ui->description->setPlainText(QT_UTF8(desc));
 
- 	const char *priv =
 
- 		config_get_string(main->basicConfig, "YouTube", "Privacy");
 
- 	int index = ui->privacyBox->findData(priv);
 
- 	ui->privacyBox->setCurrentIndex(index);
 
- 	const char *catID =
 
- 		config_get_string(main->basicConfig, "YouTube", "CategoryID");
 
- 	index = ui->categoryBox->findData(catID);
 
- 	ui->categoryBox->setCurrentIndex(index);
 
- 	const char *latency =
 
- 		config_get_string(main->basicConfig, "YouTube", "Latency");
 
- 	index = ui->latencyBox->findData(latency);
 
- 	ui->latencyBox->setCurrentIndex(index);
 
- 	bool dvr = config_get_bool(main->basicConfig, "YouTube", "DVR");
 
- 	ui->checkDVR->setChecked(dvr);
 
- 	bool forKids =
 
- 		config_get_bool(main->basicConfig, "YouTube", "MadeForKids");
 
- 	if (forKids)
 
- 		ui->yesMakeForKids->setChecked(true);
 
- 	else
 
- 		ui->notMakeForKids->setChecked(true);
 
- 	bool schedLater = config_get_bool(main->basicConfig, "YouTube",
 
- 					  "ScheduleForLater");
 
- 	ui->checkScheduledLater->setChecked(schedLater);
 
- 	bool autoStart =
 
- 		config_get_bool(main->basicConfig, "YouTube", "AutoStart");
 
- 	ui->checkAutoStart->setChecked(autoStart);
 
- 	bool autoStop =
 
- 		config_get_bool(main->basicConfig, "YouTube", "AutoStop");
 
- 	ui->checkAutoStop->setChecked(autoStop);
 
- 	const char *projection =
 
- 		config_get_string(main->basicConfig, "YouTube", "Projection");
 
- 	if (projection && *projection) {
 
- 		if (strcmp(projection, "360") == 0)
 
- 			ui->check360Video->setChecked(true);
 
- 		else
 
- 			ui->check360Video->setChecked(false);
 
- 	}
 
- 	const char *thumbFile = config_get_string(main->basicConfig, "YouTube",
 
- 						  "ThumbnailFile");
 
- 	if (thumbFile && *thumbFile) {
 
- 		QFileInfo tFile(thumbFile);
 
- 		// Re-check validity before setting path again
 
- 		if (tFile.exists() && tFile.size() <= 2 * 1024 * 1024) {
 
- 			thumbnailFile = tFile.absoluteFilePath();
 
- 			ui->selectedFileName->setText(thumbnailFile);
 
- 			ui->selectFileButton->setText(
 
- 				QTStr("YouTube.Actions.Thumbnail.ClearFile"));
 
- 			QImageReader imgReader(thumbnailFile);
 
- 			imgReader.setAutoTransform(true);
 
- 			const QImage newImage = imgReader.read();
 
- 			ui->thumbnailPreview->setPixmap(
 
- 				QPixmap::fromImage(newImage).scaled(
 
- 					160, 90, Qt::KeepAspectRatio));
 
- 		}
 
- 	}
 
- }
 
- void OBSYoutubeActions::OpenYouTubeDashboard()
 
- {
 
- 	ChannelDescription channel;
 
- 	if (!apiYouTube->GetChannelDescription(channel)) {
 
- 		blog(LOG_DEBUG, "Could not get channel description.");
 
- 		ShowErrorDialog(
 
- 			this,
 
- 			apiYouTube->GetLastError().isEmpty()
 
- 				? QTStr("YouTube.Actions.Error.General")
 
- 				: QTStr("YouTube.Actions.Error.Text")
 
- 					  .arg(apiYouTube->GetLastError()));
 
- 		return;
 
- 	}
 
- 	//https://studio.youtube.com/channel/UCA9bSfH3KL186kyiUsvi3IA/videos/live?filter=%5B%5D&sort=%7B%22columnType%22%3A%22date%22%2C%22sortOrder%22%3A%22DESCENDING%22%7D
 
- 	QString uri =
 
- 		QString("https://studio.youtube.com/channel/%1/videos/live?filter=[]&sort={\"columnType\"%3A\"date\"%2C\"sortOrder\"%3A\"DESCENDING\"}")
 
- 			.arg(channel.id);
 
- 	QDesktopServices::openUrl(uri);
 
- }
 
- void OBSYoutubeActions::Cancel()
 
- {
 
- 	workerThread->stop();
 
- 	reject();
 
- }
 
- void OBSYoutubeActions::Accept()
 
- {
 
- 	workerThread->stop();
 
- 	accept();
 
- }
 
 
  |