diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini
index 882380935c01d6..7e3aba56748459 100644
--- a/frontend/data/locale/en-US.ini
+++ b/frontend/data/locale/en-US.ini
@@ -1575,6 +1575,7 @@ YouTube.Actions.EnableAutoStart="Enable Auto-start"
YouTube.Actions.EnableAutoStop="Enable Auto-stop"
YouTube.Actions.AutoStartStop.TT="Indicates whether this scheduled broadcast should start automatically"
YouTube.Actions.EnableDVR="Enable DVR"
+YouTube.Actions.SendLanguage="Set broadcast language from OBS language setting"
YouTube.Actions.360Video="360 video"
YouTube.Actions.360Video.Help="(?)"
YouTube.Actions.ScheduleForLater="Schedule for later"
diff --git a/frontend/data/locale/ko-KR.ini b/frontend/data/locale/ko-KR.ini
index 0fc508fb6602e3..ea090b89bae2b9 100644
--- a/frontend/data/locale/ko-KR.ini
+++ b/frontend/data/locale/ko-KR.ini
@@ -1250,6 +1250,7 @@ YouTube.Actions.AutoStartStop.TT="예약된 방송이 자동으로 시작될지
YouTube.Actions.EnableDVR="DVR 사용"
YouTube.Actions.360Video="360° 동영상"
YouTube.Actions.ScheduleForLater="나중을 위해 예약하기"
+YouTube.Actions.SendLanguage="OBS 언어 설정을 방송 언어로 자동 설정"
YouTube.Actions.RememberSettings="이 설정 기억하기"
YouTube.Actions.Create_Ready="방송 생성"
YouTube.Actions.Create_GoLive="방송 생성 후 생방송 시작하기"
diff --git a/frontend/dialogs/OBSYoutubeActions.cpp b/frontend/dialogs/OBSYoutubeActions.cpp
index cf1c1843082a84..1d53ae3f0df94b 100644
--- a/frontend/dialogs/OBSYoutubeActions.cpp
+++ b/frontend/dialogs/OBSYoutubeActions.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
@@ -146,6 +147,32 @@ OBSYoutubeActions::OBSYoutubeActions(QWidget *parent, Auth *auth, bool broadcast
}
}
+ QVector language_list;
+ if (apiYouTube->GetI18nLanguagesList(language_list)) {
+ QString obs_language = QLocale::languageToCode(QLocale(App()->GetLocale()).language());
+ bool obs_language_found = false;
+
+ for (auto &language : language_list) {
+ ui->languageBox->addItem(language.name, language.id);
+ if (language.id == obs_language) {
+ obs_language_found = true;
+ }
+ }
+
+ if (obs_language_found) {
+ ui->languageBox->setCurrentIndex(ui->languageBox->findData(obs_language));
+ } else {
+ int en_index = ui->languageBox->findData("en");
+ if (en_index != -1) {
+ ui->languageBox->setCurrentIndex(en_index);
+ } else if (ui->languageBox->count() > 0) {
+ ui->languageBox->setCurrentIndex(0);
+ }
+ }
+ } else {
+ ui->languageBox->setEnabled(false);
+ }
+
connect(ui->okButton, &QPushButton::clicked, this, &OBSYoutubeActions::InitBroadcast);
connect(ui->saveButton, &QPushButton::clicked, this, &OBSYoutubeActions::ReadyBroadcast);
connect(ui->cancelButton, &QPushButton::clicked, this, [&]() {
@@ -388,8 +415,8 @@ bool OBSYoutubeActions::CreateEventAction(YoutubeApiWrappers *api, BroadcastDesc
blog(LOG_DEBUG, "No broadcast created.");
return false;
}
- if (!apiYouTube->SetVideoCategory(broadcast.id, broadcast.title, broadcast.description,
- broadcast.category.id)) {
+ if (!apiYouTube->SetVideoCategory(broadcast.id, broadcast.title, broadcast.description, broadcast.category.id,
+ broadcast.language)) {
blog(LOG_DEBUG, "No category set.");
return false;
}
@@ -608,6 +635,9 @@ void OBSYoutubeActions::UiToBroadcast(BroadcastDescription &broadcast)
broadcast.schedul_for_later = ui->checkScheduledLater->isChecked();
broadcast.projection = ui->check360Video->isChecked() ? "360" : "rectangular";
+ // Send selected language to YouTube API
+ broadcast.language = ui->languageBox->currentData().toString();
+
if (ui->checkRememberSettings->isChecked())
SaveSettings(broadcast);
}
@@ -628,6 +658,7 @@ void OBSYoutubeActions::SaveSettings(BroadcastDescription &broadcast)
config_set_bool(main->activeConfiguration, "YouTube", "ScheduleForLater", broadcast.schedul_for_later);
config_set_string(main->activeConfiguration, "YouTube", "Projection", QT_TO_UTF8(broadcast.projection));
config_set_string(main->activeConfiguration, "YouTube", "ThumbnailFile", QT_TO_UTF8(thumbnailFile));
+ config_set_string(main->activeConfiguration, "YouTube", "Language", QT_TO_UTF8(broadcast.language));
config_set_bool(main->activeConfiguration, "YouTube", "RememberSettings", true);
}
@@ -679,6 +710,15 @@ void OBSYoutubeActions::LoadSettings()
ui->check360Video->setChecked(false);
}
+ // Load language setting
+ const char *langStr = config_get_string(main->activeConfiguration, "YouTube", "Language");
+ if (langStr && *langStr) {
+ int langIndex = ui->languageBox->findData(QT_UTF8(langStr));
+ if (langIndex != -1) {
+ ui->languageBox->setCurrentIndex(langIndex);
+ }
+ }
+
const char *thumbFile = config_get_string(main->activeConfiguration, "YouTube", "ThumbnailFile");
if (thumbFile && *thumbFile) {
QFileInfo tFile(thumbFile);
diff --git a/frontend/forms/OBSYoutubeActions.ui b/frontend/forms/OBSYoutubeActions.ui
index 1f38008cda7b2a..6b7e2b442621bd 100644
--- a/frontend/forms/OBSYoutubeActions.ui
+++ b/frontend/forms/OBSYoutubeActions.ui
@@ -177,13 +177,33 @@
-
+
+
+ Basic.Settings.General.Language
+
+
+ languageBox
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
YouTube.Actions.MadeForKids
- -
+
-
-
@@ -210,21 +230,21 @@
- -
+
-
YouTube.Actions.MadeForKids.Yes
- -
+
-
YouTube.Actions.Thumbnail
- -
+
-
true
@@ -258,7 +278,7 @@
- -
+
-
0
@@ -282,7 +302,7 @@
- -
+
-
Qt::Vertical
@@ -295,7 +315,7 @@
- -
+
-
@@ -307,7 +327,7 @@
- -
+
-
true
@@ -317,7 +337,7 @@
- -
+
-
-
@@ -357,7 +377,7 @@
- -
+
-
-
@@ -406,7 +426,7 @@
- -
+
-
true
@@ -419,7 +439,7 @@
- -
+
-
true
@@ -448,7 +468,7 @@
- -
+
-
YouTube.Actions.EnableDVR
@@ -458,7 +478,7 @@
- -
+
-
YouTube.Actions.Latency
@@ -468,7 +488,7 @@
- -
+
-
diff --git a/frontend/utility/YoutubeApiWrappers.cpp b/frontend/utility/YoutubeApiWrappers.cpp
index 4d1a2a1ea598f4..3831da7b24af82 100644
--- a/frontend/utility/YoutubeApiWrappers.cpp
+++ b/frontend/utility/YoutubeApiWrappers.cpp
@@ -24,6 +24,7 @@ constexpr auto youtubeLiveBroadcastBindUrl = "https://www.googleapis.com/youtube
constexpr auto youtubeLiveChannelUrl = "https://www.googleapis.com/youtube/v3/channels"sv;
constexpr auto youtubeLiveTokenUrl = "https://oauth2.googleapis.com/token"sv;
+constexpr auto youtubeLiveI18nLanguagesUrl = "https://www.googleapis.com/youtube/v3/i18nLanguages"sv;
constexpr auto youtubeLiveVideoCategoriesUrl = "https://www.googleapis.com/youtube/v3/videoCategories"sv;
constexpr auto youtubeLiveVideosUrl = "https://www.googleapis.com/youtube/v3/videos"sv;
constexpr auto youtubeLiveThumbnailUrl = "https://www.googleapis.com/upload/youtube/v3/thumbnails/set"sv;
@@ -275,6 +276,31 @@ bool YoutubeApiWrappers::GetBroadcastsList(Json &json_out, const QString &page,
return InsertCommand(url.c_str(), "application/json", "", nullptr, json_out);
}
+bool YoutubeApiWrappers::GetI18nLanguagesList(QVector &language_list_out)
+{
+ lastErrorMessage.clear();
+ lastErrorReason.clear();
+ const QString url_template = QString(youtubeLiveI18nLanguagesUrl.data()) + "?part=snippet&hl=%1";
+
+ QString url = url_template.arg(QLocale().name());
+
+ Json json_out;
+ if (!InsertCommand(QT_TO_UTF8(url), "application/json", "", nullptr, json_out)) {
+ if (lastErrorReason != "unsupportedLanguageCode" && lastErrorReason != "invalidLanguage")
+ return false;
+ // Try again with en_US if YouTube error indicates an unsupported locale
+ url = url_template.arg("en_US");
+ if (!InsertCommand(QT_TO_UTF8(url), "application/json", "", nullptr, json_out))
+ return false;
+ }
+ language_list_out = {};
+ for (auto &j : json_out["items"].array_items()) {
+ language_list_out.push_back(
+ {j["id"].string_value().c_str(), j["snippet"]["name"].string_value().c_str()});
+ }
+ return language_list_out.isEmpty() ? false : true;
+}
+
bool YoutubeApiWrappers::GetVideoCategoriesList(QVector &category_list_out)
{
lastErrorMessage.clear();
@@ -315,19 +341,27 @@ bool YoutubeApiWrappers::GetVideoCategoriesList(QVector &ca
}
bool YoutubeApiWrappers::SetVideoCategory(const QString &video_id, const QString &video_title,
- const QString &video_description, const QString &categorie_id)
+ const QString &video_description, const QString &categorie_id,
+ const QString &language)
{
lastErrorMessage.clear();
lastErrorReason.clear();
const std::string url = std::string(youtubeLiveVideosUrl) + "?part=snippet";
+
+ Json::object snippet = {
+ {"title", QT_TO_UTF8(video_title)},
+ {"description", QT_TO_UTF8(video_description)},
+ {"categoryId", QT_TO_UTF8(categorie_id)},
+ };
+
+ if (!language.isEmpty()) {
+ snippet["defaultLanguage"] = QT_TO_UTF8(language);
+ snippet["defaultAudioLanguage"] = QT_TO_UTF8(language);
+ }
+
const Json data = Json::object{
{"id", QT_TO_UTF8(video_id)},
- {"snippet",
- Json::object{
- {"title", QT_TO_UTF8(video_title)},
- {"description", QT_TO_UTF8(video_description)},
- {"categoryId", QT_TO_UTF8(categorie_id)},
- }},
+ {"snippet", snippet},
};
Json json_out;
return InsertCommand(url.c_str(), "application/json", "PUT", data.dump().c_str(), json_out);
diff --git a/frontend/utility/YoutubeApiWrappers.hpp b/frontend/utility/YoutubeApiWrappers.hpp
index 9cabb709138756..d2bb6ec06668cd 100644
--- a/frontend/utility/YoutubeApiWrappers.hpp
+++ b/frontend/utility/YoutubeApiWrappers.hpp
@@ -22,6 +22,11 @@ struct CategoryDescription {
QString title;
};
+struct I18nLanguageDescription {
+ QString id;
+ QString name;
+};
+
struct BroadcastDescription {
QString id;
QString title;
@@ -36,6 +41,7 @@ struct BroadcastDescription {
bool schedul_for_later;
QString schedul_date_time;
QString projection;
+ QString language;
};
bool IsYouTubeService(const std::string &service);
@@ -59,8 +65,9 @@ class YoutubeApiWrappers : public YoutubeAuth {
bool BindStream(const QString broadcast_id, const QString stream_id);
bool GetBroadcastsList(json11::Json &json_out, const QString &page, const QString &status);
bool GetVideoCategoriesList(QVector &category_list_out);
+ bool GetI18nLanguagesList(QVector &language_list_out);
bool SetVideoCategory(const QString &video_id, const QString &video_title, const QString &video_description,
- const QString &categorie_id);
+ const QString &categorie_id, const QString &language = "");
bool SetVideoThumbnail(const QString &video_id, const QString &thumbnail_file);
bool StartBroadcast(const QString &broadcast_id);
bool StopBroadcast(const QString &broadcast_id);