851 lines
25 KiB
C++
851 lines
25 KiB
C++
#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;
|
|
}
|
|
if (!apiYouTube->BindStream(broadcast.id, stream.id)) {
|
|
blog(LOG_DEBUG, "No stream binded.");
|
|
return false;
|
|
}
|
|
|
|
if (broadcast.privacy != "private")
|
|
apiYouTube->SetChatId(broadcast.id);
|
|
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();
|
|
|
|
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)) {
|
|
blog(LOG_DEBUG, "No stream binded.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (broadcastPrivacy != "private")
|
|
apiYouTube->SetChatId(selectedBroadcast);
|
|
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();
|
|
}
|