Add option to change stream language

This commit is contained in:
WarmUpTill 2025-08-27 20:21:49 +02:00 committed by WarmUpTill
parent 4d22a539f0
commit d2b70bbc6b
6 changed files with 423 additions and 0 deletions

View File

@ -1207,6 +1207,7 @@ AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.channel.info.tags.set="Set stream tags"
AdvSceneSwitcher.action.twitch.type.channel.info.language.set="Set stream language"
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
@ -1250,6 +1251,42 @@ AdvSceneSwitcher.action.twitch.contentClassification.violentGraphic="Simulations
AdvSceneSwitcher.action.twitch.contentClassification.gambling="Participating in or promoting online or in-person gambling, poker, or fantasy sports that involve the exchange of real money."
AdvSceneSwitcher.action.twitch.contentClassification.profanityVulgarity="Prolonged and repeated use of obscenities, profanities, and vulgarities, especially as a regular part of speech."
AdvSceneSwitcher.action.twitch.contentClassification.getCurrent="Get current content classification labels"
AdvSceneSwitcher.action.twitch.language.english="English"
AdvSceneSwitcher.action.twitch.language.indonesian="Bahasa Indonesia"
AdvSceneSwitcher.action.twitch.language.catalan="Català"
AdvSceneSwitcher.action.twitch.language.danish="Dansk"
AdvSceneSwitcher.action.twitch.language.german="Deutsch"
AdvSceneSwitcher.action.twitch.language.spanish="Español"
AdvSceneSwitcher.action.twitch.language.french="Français"
AdvSceneSwitcher.action.twitch.language.italian="Italiano"
AdvSceneSwitcher.action.twitch.language.hungarian="Magyar"
AdvSceneSwitcher.action.twitch.language.dutch="Nederlands"
AdvSceneSwitcher.action.twitch.language.norwegian="Norsk"
AdvSceneSwitcher.action.twitch.language.polish="Polski"
AdvSceneSwitcher.action.twitch.language.portuguese="Português"
AdvSceneSwitcher.action.twitch.language.romanian="Română"
AdvSceneSwitcher.action.twitch.language.slovak="Slovenčina"
AdvSceneSwitcher.action.twitch.language.finnish="Suomi"
AdvSceneSwitcher.action.twitch.language.swedish="Svenska"
AdvSceneSwitcher.action.twitch.language.tagalog="Tagalog"
AdvSceneSwitcher.action.twitch.language.vietnamese="Tiếng Việt"
AdvSceneSwitcher.action.twitch.language.turkish="Türkçe"
AdvSceneSwitcher.action.twitch.language.czech="Čeština"
AdvSceneSwitcher.action.twitch.language.greek="Ελληνικά"
AdvSceneSwitcher.action.twitch.language.bulgarian="Български"
AdvSceneSwitcher.action.twitch.language.russian="Русский"
AdvSceneSwitcher.action.twitch.language.ukrainian="Українська"
AdvSceneSwitcher.action.twitch.language.arabic="العربية"
AdvSceneSwitcher.action.twitch.language.malay="بهاس ملايو"
AdvSceneSwitcher.action.twitch.language.hindi="मानक हिन्दी"
AdvSceneSwitcher.action.twitch.language.thai="ภาษาไทย"
AdvSceneSwitcher.action.twitch.language.chinese="中文"
AdvSceneSwitcher.action.twitch.language.japanese="日本語"
AdvSceneSwitcher.action.twitch.language.cantonese="粵語"
AdvSceneSwitcher.action.twitch.language.korean="한국어"
AdvSceneSwitcher.action.twitch.language.asl="American Sign Language"
AdvSceneSwitcher.action.twitch.language.other="Other"
AdvSceneSwitcher.action.twitch.language.getCurrent="Get current language"
AdvSceneSwitcher.action.clipboard="Clipboard"
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"

View File

@ -90,6 +90,8 @@ target_sources(
content-classification.hpp
event-sub.cpp
event-sub.hpp
language-selection.cpp
language-selection.hpp
macro-action-twitch.cpp
macro-action-twitch.hpp
macro-condition-twitch.cpp

View File

@ -0,0 +1,314 @@
#include "language-selection.hpp"
#include "channel-selection.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "twitch-helpers.hpp"
#include "ui-helpers.hpp"
#include <QPushButton>
#include <QVBoxLayout>
namespace advss {
namespace {
struct LocaleMapping {
const QString languageCode;
const QString name;
};
const std::vector<LocaleMapping> &getLocales()
{
static std::vector<LocaleMapping> locales;
static const char *lastLocaleCode = "";
const char *curLocale = obs_get_locale();
if (strcmp(lastLocaleCode, curLocale) != 0) {
locales.clear();
locales.emplace_back(LocaleMapping{
"en",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.english")});
locales.emplace_back(LocaleMapping{
"id",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.indonesian")});
locales.emplace_back(LocaleMapping{
"ca",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.catalan")});
locales.emplace_back(LocaleMapping{
"da",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.danish")});
locales.emplace_back(LocaleMapping{
"de",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.german")});
locales.emplace_back(LocaleMapping{
"es",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.spanish")});
locales.emplace_back(LocaleMapping{
"fr",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.french")});
locales.emplace_back(LocaleMapping{
"it",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.italian")});
locales.emplace_back(LocaleMapping{
"hu",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.hungarian")});
locales.emplace_back(LocaleMapping{
"nl",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.dutch")});
locales.emplace_back(LocaleMapping{
"no",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.norwegian")});
locales.emplace_back(LocaleMapping{
"pl",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.polish")});
locales.emplace_back(LocaleMapping{
"pt",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.portuguese")});
locales.emplace_back(LocaleMapping{
"ro",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.romanian")});
locales.emplace_back(LocaleMapping{
"sk",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.slovak")});
locales.emplace_back(LocaleMapping{
"fi",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.finnish")});
locales.emplace_back(LocaleMapping{
"sv",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.swedish")});
locales.emplace_back(LocaleMapping{
"tl",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.tagalog")});
locales.emplace_back(LocaleMapping{
"vi",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.vietnamese")});
locales.emplace_back(LocaleMapping{
"tr",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.turkish")});
locales.emplace_back(LocaleMapping{
"cs",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.czech")});
locales.emplace_back(LocaleMapping{
"el",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.greek")});
locales.emplace_back(LocaleMapping{
"bg",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.bulgarian")});
locales.emplace_back(LocaleMapping{
"ru",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.russian")});
locales.emplace_back(LocaleMapping{
"uk",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.ukrainian")});
locales.emplace_back(LocaleMapping{
"ar",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.arabic")});
locales.emplace_back(LocaleMapping{
"ms",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.malay")});
locales.emplace_back(LocaleMapping{
"hi",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.hindi")});
locales.emplace_back(LocaleMapping{
"th",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.thai")});
locales.emplace_back(LocaleMapping{
"zh",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.chinese")});
locales.emplace_back(LocaleMapping{
"ja",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.japanese")});
locales.emplace_back(LocaleMapping{
"zh-hk",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.cantonese")});
locales.emplace_back(LocaleMapping{
"ko",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.korean")});
locales.emplace_back(LocaleMapping{
"asl",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.asl")});
locales.emplace_back(LocaleMapping{
"other",
obs_module_text(
"AdvSceneSwitcher.action.twitch.language.other")});
lastLocaleCode = curLocale;
}
return locales;
}
} // namespace
void LanguageSelection::Load(obs_data_t *obj)
{
OBSDataAutoRelease data = obs_data_get_obj(obj, "languageSelection");
_language.Load(data, "language");
_useManualInput = obs_data_get_bool(data, "useManualInput");
}
void LanguageSelection::Save(obs_data_t *obj) const
{
OBSDataAutoRelease data = obs_data_create();
_language.Save(data, "language");
obs_data_set_bool(data, "useManualInput", _useManualInput);
obs_data_set_obj(obj, "languageSelection", data);
}
void LanguageSelection::SetStreamLanguage(const TwitchToken &token) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "broadcaster_language", _language.c_str());
auto result = SendPatchRequest(token, "https://api.twitch.tv",
"/helix/channels",
{{"broadcaster_id", token.GetUserID()}},
data.Get());
if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream language! (%d)",
result.status);
}
}
static void populateLocaleSelection(QComboBox *list)
{
for (const auto &[languageCode, name] : getLocales()) {
list->addItem(name, languageCode);
}
}
LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent)
: QWidget(parent),
_languageText(new VariableLineEdit(this)),
_languageCombo(new QComboBox(this)),
_toggleInput(new QPushButton(this))
{
populateLocaleSelection(_languageCombo);
auto getCurrent = new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.twitch.language.getCurrent"));
connect(getCurrent, &QPushButton::clicked, this,
&LanguageSelectionWidget::GetCurrentClicked);
_toggleInput->setCheckable(true);
_toggleInput->setMaximumWidth(11);
SetButtonIcon(_toggleInput, GetThemeTypeName() == "Light"
? ":/res/images/dots-vert.svg"
: "theme:Dark/dots-vert.svg");
connect(_toggleInput, &QPushButton::toggled, this, [this]() {
_languageText->setVisible(!_languageText->isVisible());
_languageCombo->setVisible(!_languageCombo->isVisible());
});
connect(_languageText, &VariableLineEdit::editingFinished, this,
&LanguageSelectionWidget::TextChanged);
connect(_languageCombo, &QComboBox::currentIndexChanged, this,
&LanguageSelectionWidget::ComboSelectionChanged);
auto inputLayout = new QHBoxLayout;
inputLayout->addWidget(_languageCombo);
inputLayout->addWidget(_languageText);
inputLayout->addWidget(_toggleInput);
auto layout = new QVBoxLayout;
layout->addLayout(inputLayout);
layout->addWidget(getCurrent);
setLayout(layout);
}
void LanguageSelectionWidget::SetLanguageSelection(
const LanguageSelection &language)
{
const QSignalBlocker b1(_languageText);
const QSignalBlocker b2(_languageCombo);
_languageText->setText(language._language);
_languageCombo->setCurrentIndex(
_languageCombo->findData(QString(language._language.c_str())));
_toggleInput->setChecked(language._useManualInput);
_languageText->setVisible(language._useManualInput);
_languageCombo->setVisible(!language._useManualInput);
}
void LanguageSelectionWidget::TextChanged()
{
LanguageSelection language;
language._useManualInput = _toggleInput->isChecked();
language._language = _languageText->text().toStdString();
emit LanguageChanged(language);
const QSignalBlocker b(this);
SetLanguageSelection(language);
}
void LanguageSelectionWidget::ComboSelectionChanged()
{
LanguageSelection language;
language._useManualInput = _toggleInput->isChecked();
language._language =
_languageCombo->currentData().toString().toStdString();
emit LanguageChanged(language);
const QSignalBlocker b(this);
SetLanguageSelection(language);
}
void LanguageSelectionWidget::SetToken(const std::weak_ptr<TwitchToken> &t)
{
_token = t;
}
void LanguageSelectionWidget::GetCurrentClicked()
{
auto token = _token.lock();
if (!token) {
return;
}
TwitchChannel channel;
channel.SetName(token->Name());
const auto channelInfo = channel.GetInfo(*token);
if (!channelInfo) {
return;
}
LanguageSelection language;
language._language = channelInfo->broadcaster_language;
SetLanguageSelection(language);
}
} // namespace advss

View File

@ -0,0 +1,41 @@
#pragma once
#include "variable-line-edit.hpp"
#include "token.hpp"
namespace advss {
struct LanguageSelection {
void Load(obs_data_t *obj);
void Save(obs_data_t *obj) const;
void SetStreamLanguage(const TwitchToken &) const;
private:
StringVariable _language = "en";
bool _useManualInput = false;
friend class LanguageSelectionWidget;
};
class LanguageSelectionWidget final : public QWidget {
Q_OBJECT
public:
explicit LanguageSelectionWidget(QWidget *parent = nullptr);
void SetLanguageSelection(const LanguageSelection &);
void SetToken(const std::weak_ptr<TwitchToken> &);
private slots:
void GetCurrentClicked();
void TextChanged();
void ComboSelectionChanged();
signals:
void LanguageChanged(const LanguageSelection &);
private:
VariableLineEdit *_languageText;
QComboBox *_languageCombo;
QPushButton *_toggleInput;
std::weak_ptr<TwitchToken> _token;
};
} // namespace advss

View File

@ -59,6 +59,8 @@ const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.twitch.type.channel.info.category.set"},
{MacroActionTwitch::Action::CHANNEL_INFO_TAGS_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.tags.set"},
{MacroActionTwitch::Action::CHANNEL_INFO_LANGUAGE_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.language.set"},
{MacroActionTwitch::Action::CHANNEL_INFO_CONTENT_LABELS_SET,
"AdvSceneSwitcher.action.twitch.type.channel.info.contentClassification.set"},
{MacroActionTwitch::Action::RAID_START,
@ -525,6 +527,9 @@ bool MacroActionTwitch::PerformAction()
case Action::CHANNEL_INFO_TAGS_SET:
_tags.SetStreamTags(*token);
break;
case Action::CHANNEL_INFO_LANGUAGE_SET:
_language.SetStreamLanguage(*token);
break;
case Action::CHANNEL_INFO_CONTENT_LABELS_SET:
_contentClassification.SetContentClassification(*token);
break;
@ -588,6 +593,7 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const
_streamTitle.Save(obj, "streamTitle");
_category.Save(obj);
_tags.Save(obj);
_language.Save(obj);
_contentClassification.Save(obj);
_markerDescription.Save(obj, "markerDescription");
obs_data_set_bool(obj, "clipHasDelay", _clipHasDelay);
@ -618,6 +624,7 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
_streamTitle.Load(obj, "streamTitle");
_category.Load(obj);
_tags.Load(obj);
_language.Load(obj);
_contentClassification.Load(obj);
_markerDescription.Load(obj, "markerDescription");
_clipHasDelay = obs_data_get_bool(obj, "clipHasDelay");
@ -835,6 +842,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_streamTitle(new VariableLineEdit(this)),
_category(new TwitchCategoryWidget(this)),
_tags(new TagListWidget(this)),
_language(new LanguageSelectionWidget(this)),
_contentClassification(new ContentClassificationEdit(this)),
_markerDescription(new VariableLineEdit(this)),
_clipHasDelay(new QCheckBox(obs_module_text(
@ -862,6 +870,7 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
mainLayout->addWidget(_announcementMessage);
mainLayout->addWidget(_chatMessage);
mainLayout->addWidget(_tags);
mainLayout->addWidget(_language);
mainLayout->addWidget(_contentClassification);
mainLayout->addWidget(_tokenWarning);
setLayout(mainLayout);
@ -900,6 +909,7 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
_channel->SetToken(_entryData->_token);
_pointsReward->SetToken(_entryData->_token);
_tags->SetToken(_entryData->_token);
_language->SetToken(_entryData->_token);
_contentClassification->SetToken(_entryData->_token);
_entryData->ResetChatConnection();
@ -961,6 +971,12 @@ void MacroActionTwitchEdit::TagsChanged(const TwitchTagList &tags)
_entryData->_tags = tags;
}
void MacroActionTwitchEdit::LanguageChanged(const LanguageSelection &language)
{
GUARD_LOADING_AND_LOCK();
_entryData->_language = language;
}
void MacroActionTwitchEdit::ContentClassificationChanged(
const ContentClassification &ccl)
{
@ -1050,6 +1066,10 @@ void MacroActionTwitchEdit::SetWidgetSignalConnections()
SLOT(CategoryChanged(const TwitchCategory &)));
QWidget::connect(_tags, SIGNAL(TagListChanged(const TwitchTagList &)),
this, SLOT(TagsChanged(const TwitchTagList &)));
QWidget::connect(_language,
SIGNAL(LanguageChanged(const LanguageSelection &)),
this,
SLOT(LanguageChanged(const LanguageSelection &)));
QWidget::connect(_contentClassification,
SIGNAL(ContentClassificationChanged(
const ContentClassification &)),
@ -1100,6 +1120,9 @@ void MacroActionTwitchEdit::SetWidgetVisibility()
MacroActionTwitch::Action::CHANNEL_INFO_CATEGORY_SET);
_tags->setVisible(_entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_INFO_TAGS_SET);
_language->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_INFO_LANGUAGE_SET);
_contentClassification->setVisible(
_entryData->GetAction() ==
MacroActionTwitch::Action::CHANNEL_INFO_CONTENT_LABELS_SET);
@ -1300,6 +1323,8 @@ void MacroActionTwitchEdit::UpdateEntryData()
_category->SetCategory(_entryData->_category);
_tags->SetTags(_entryData->_tags);
_tags->SetToken(_entryData->_token);
_language->SetLanguageSelection(_entryData->_language);
_language->SetToken(_entryData->_token);
_contentClassification->SetContentClassification(
_entryData->_contentClassification);
_contentClassification->SetToken(_entryData->_token);

View File

@ -5,6 +5,7 @@
#include "channel-selection.hpp"
#include "chat-connection.hpp"
#include "content-classification.hpp"
#include "language-selection.hpp"
#include "points-reward-selection.hpp"
#include "tag-selection.hpp"
@ -171,6 +172,7 @@ public:
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
TwitchCategory _category;
TwitchTagList _tags;
LanguageSelection _language;
ContentClassification _contentClassification;
StringVariable _markerDescription = obs_module_text(
"AdvSceneSwitcher.action.twitch.marker.description");
@ -240,6 +242,7 @@ private slots:
void StreamTitleChanged();
void CategoryChanged(const TwitchCategory &);
void TagsChanged(const TwitchTagList &tags);
void LanguageChanged(const LanguageSelection &);
void ContentClassificationChanged(const ContentClassification &ccl);
void MarkerDescriptionChanged();
void ClipHasDelayChanged(int state);
@ -276,6 +279,7 @@ private:
VariableLineEdit *_streamTitle;
TwitchCategoryWidget *_category;
TagListWidget *_tags;
LanguageSelectionWidget *_language;
ContentClassificationEdit *_contentClassification;
VariableLineEdit *_markerDescription;
QCheckBox *_clipHasDelay;