obs-studio/UI/window-youtube-actions.cpp

860 lines
26 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;
}
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();
}