Skip to content

Commit

Permalink
feat: Add Twitch clip and marker actions, refactor stream title
Browse files Browse the repository at this point in the history
  • Loading branch information
Destroy666x committed Sep 15, 2023
1 parent fc1460e commit 65a5e63
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Thumbs.db
.settings
.idea
.metadata
.vscode
*.iml
*.ipr
*.sublime*
Expand Down
8 changes: 7 additions & 1 deletion data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -779,10 +779,15 @@ AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Set stream title"
AdvSceneSwitcher.action.twitch.type.category="Set stream category"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip"
AdvSceneSwitcher.action.twitch.type.commercial="Start commercial with duration"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Cannot select category without selecting a Twitch account first!"
AdvSceneSwitcher.action.twitch.entry="On{{account}}{{actions}}{{text}}{{category}}{{manualCategorySearch}}{{duration}}"
AdvSceneSwitcher.action.twitch.entry="On{{account}}{{actions}}{{streamTitle}}{{category}}{{manualCategorySearch}}{{markerDescription}}{{clipHasDelay}}{{duration}}"
AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker, max 140 chars"
AdvSceneSwitcher.action.twitch.title.title="Enter title, max 140 chars"

; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher"
Expand Down Expand Up @@ -935,6 +940,7 @@ AdvSceneSwitcher.twitchToken.analytics.readGames="View analytics data for the ga
AdvSceneSwitcher.twitchToken.bits.read="View Bits information for a channel."
AdvSceneSwitcher.twitchToken.channel.manageBroadcast="Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags."
AdvSceneSwitcher.twitchToken.channel.startCommercial="Run commercials on a channel."
AdvSceneSwitcher.twitchToken.channel.createClip="Create clips from channel's broadcasts."

AdvSceneSwitcher.twitchCategories.fetchStart="Fetching stream categories ..."
AdvSceneSwitcher.twitchCategories.fetchStatus="Got %1 stream categories."
Expand Down
9 changes: 8 additions & 1 deletion data/locale/fr-FR.ini
Original file line number Diff line number Diff line change
Expand Up @@ -737,9 +737,15 @@ AdvSceneSwitcher.action.sceneLock.entry="Sur {{scenes}} {{actions}} {{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.title="Définir le titre du flux"
AdvSceneSwitcher.action.twitch.type.category="Définir la catégorie du flux"
AdvSceneSwitcher.action.twitch.type.marker="Create stream marker"
AdvSceneSwitcher.action.twitch.type.clip="Create stream clip"
AdvSceneSwitcher.action.twitch.type.commercial="Démarrer une publicité d'une durée de"
AdvSceneSwitcher.action.twitch.categorySelectionDisabled="Impossible de sélectionner une catégorie sans avoir d'abord sélectionné un compte Twitch !"
AdvSceneSwitcher.action.twitch.entry="Sur {{account}} {{actions}} {{text}} {{category}} {{manualCategorySearch}} {{duration}}"
AdvSceneSwitcher.action.twitch.entry="Sur {{account}} {{actions}} {{streamTitle}} {{category}} {{manualCategorySearch}} {{markerDescription}} {{clipHasDelay}} {{duration}}"
AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!"
AdvSceneSwitcher.action.twitch.clip.hasDelay="Add a slight delay before capturing the clip"
AdvSceneSwitcher.action.twitch.marker.description="Describe marker, max 140 chars"
AdvSceneSwitcher.action.twitch.title.title="Enter title, max 140 chars"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
Expand Down Expand Up @@ -1121,6 +1127,7 @@ AdvSceneSwitcher.twitchToken.analytics.readGames="Afficher les données d'analys
AdvSceneSwitcher.twitchToken.bits.read="Afficher les informations sur les Bits pour une chaîne."
AdvSceneSwitcher.twitchToken.channel.manageBroadcast="Gérer la configuration de diffusion d'une chaîne, y compris la mise à jour de la configuration de la chaîne et la gestion des marqueurs de diffusion en continu et des balises de diffusion en continu."
AdvSceneSwitcher.twitchToken.channel.startCommercial="Lancer des publicités sur une chaîne."
AdvSceneSwitcher.twitchToken.channel.createClip="Create clips from channel's broadcasts."

AdvSceneSwitcher.twitchCategories.fetchStart="Récupération des catégories de streams..."
AdvSceneSwitcher.twitchCategories.fetchStatus="Obtenu %1 catégories de streams."
Expand Down
128 changes: 107 additions & 21 deletions src/macro-external/twitch/macro-action-twitch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,29 @@ const static std::map<MacroActionTwitch::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.twitch.type.title"},
{MacroActionTwitch::Action::CATEGORY,
"AdvSceneSwitcher.action.twitch.type.category"},
{MacroActionTwitch::Action::MARKER,
"AdvSceneSwitcher.action.twitch.type.marker"},
{MacroActionTwitch::Action::CLIP,
"AdvSceneSwitcher.action.twitch.type.clip"},
{MacroActionTwitch::Action::COMMERCIAL,
"AdvSceneSwitcher.action.twitch.type.commercial"},
};

void MacroActionTwitch::SetStreamTitle(
const std::shared_ptr<TwitchToken> &token) const
{
if (std::string(_text).empty()) {
if (std::string(_streamTitle).empty()) {
return;
}

OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "title", _text.c_str());
obs_data_set_string(data, "title", _streamTitle.c_str());
auto result = SendPatchRequest(
"https://api.twitch.tv",
std::string("/helix/channels?broadcaster_id=") +
token->GetUserID(),
*token, data.Get());

if (result.status != 204) {
blog(LOG_INFO, "Failed to set stream title! (%d)",
result.status);
Expand Down Expand Up @@ -63,6 +68,39 @@ void MacroActionTwitch::SetStreamCategory(
}
}

void MacroActionTwitch::CreateStreamMarker(
const std::shared_ptr<TwitchToken> &token) const
{
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "user_id", token->GetUserID().c_str());

if (!std::string(_markerDescription).empty()) {
obs_data_set_string(data, "description", _markerDescription.c_str());
}

auto result = SendPostRequest("https://api.twitch.tv",
"/helix/streams/markers", *token, data.Get());

if (result.status != 200) {
blog(LOG_INFO, "Failed to create marker! (%d)", result.status);
}
}

void MacroActionTwitch::CreateStreamClip(
const std::shared_ptr<TwitchToken> &token) const
{
OBSDataAutoRelease data = obs_data_create();
auto hasDelay = _clipHasDelay ? "true" : "false";
auto result = SendPostRequest("https://api.twitch.tv",
"/helix/clips?broadcaster_id=" + token->GetUserID() +
"&has_delay=" + hasDelay,
*token, data.Get());

if (result.status != 202) {
blog(LOG_INFO, "Failed to create clip! (%d)", result.status);
}
}

void MacroActionTwitch::StartCommercial(
const std::shared_ptr<TwitchToken> &token) const
{
Expand Down Expand Up @@ -102,6 +140,12 @@ bool MacroActionTwitch::PerformAction()
case MacroActionTwitch::Action::CATEGORY:
SetStreamCategory(token);
break;
case MacroActionTwitch::Action::MARKER:
CreateStreamMarker(token);
break;
case MacroActionTwitch::Action::CLIP:
CreateStreamClip(token);
break;
case MacroActionTwitch::Action::COMMERCIAL:
StartCommercial(token);
break;
Expand Down Expand Up @@ -131,8 +175,10 @@ bool MacroActionTwitch::Save(obs_data_t *obj) const
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_string(obj, "token",
GetWeakTwitchTokenName(_token).c_str());
_text.Save(obj, "text");
_streamTitle.Save(obj, "streamTitle");
_category.Save(obj);
_markerDescription.Save(obj, "markerDescription");
obs_data_set_bool(obj, "clipHasDelay", _clipHasDelay);
_duration.Save(obj);
return true;
}
Expand All @@ -142,8 +188,10 @@ bool MacroActionTwitch::Load(obs_data_t *obj)
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_token = GetWeakTwitchTokenByName(obs_data_get_string(obj, "token"));
_text.Load(obj, "text");
_streamTitle.Load(obj, "streamTitle");
_category.Load(obj);
_markerDescription.Load(obj, "markerDescription");
_clipHasDelay = obs_data_get_bool(obj, "clipHasDelay");
_duration.Load(obj);
return true;
}
Expand All @@ -158,6 +206,8 @@ bool MacroActionTwitch::ActionIsSupportedByToken()
static const std::unordered_map<Action, TokenOption> requiredOption = {
{Action::TITLE, {"channel:manage:broadcast"}},
{Action::CATEGORY, {"channel:manage:broadcast"}},
{Action::MARKER, {"channel:manage:broadcast"}},
{Action::CLIP, {"clips:edit"}},
{Action::COMMERCIAL, {"channel:edit:commercial"}},
};
auto token = _token.lock();
Expand All @@ -181,15 +231,20 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
: QWidget(parent),
_actions(new QComboBox()),
_tokens(new TwitchConnectionSelection()),
_text(new VariableLineEdit(this)),
_streamTitle(new VariableLineEdit(this)),
_category(new TwitchCategorySelection(this)),
_manualCategorySearch(new TwitchCategorySearchButton()),
_markerDescription(new VariableLineEdit(this)),
_clipHasDelay(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.twitch.clip.hasDelay"))),
_duration(new DurationSelection(this, false, 0)),
_layout(new QHBoxLayout()),
_tokenPermissionWarning(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.twitch.tokenPermissionsInsufficient")))
{
_text->setSizePolicy(QSizePolicy::MinimumExpanding,
_streamTitle->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_markerDescription->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_duration->SpinBox()->setSuffix("s");
populateActionSelection(_actions);
Expand All @@ -198,14 +253,15 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
SLOT(ActionChanged(int)));
QWidget::connect(_tokens, SIGNAL(SelectionChanged(const QString &)),
this, SLOT(TwitchTokenChanged(const QString &)));
QWidget::connect(_text, SIGNAL(editingFinished()), this,
SLOT(TextChanged()));
QWidget::connect(_streamTitle, SIGNAL(editingFinished()), this,
SLOT(StreamTitleChanged()));
QWidget::connect(_category,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_duration,
SIGNAL(CategoreyChanged(const TwitchCategory &)), this,
SLOT(CategoreyChanged(const TwitchCategory &)));
QWidget::connect(_markerDescription, SIGNAL(editingFinished()), this,
SLOT(MarkerDescriptionChanged()));
QObject::connect(_clipHasDelay, SIGNAL(stateChanged(int)), this,
SLOT(HasClipDelayChanged(const Duration &)));
QObject::connect(_duration, SIGNAL(DurationChanged(const Duration &)),
this, SLOT(DurationChanged(const Duration &)));
QWidget::connect(&_tokenPermissionCheckTimer, SIGNAL(timeout()), this,
Expand All @@ -215,9 +271,11 @@ MacroActionTwitchEdit::MacroActionTwitchEdit(
_layout,
{{"{{account}}", _tokens},
{"{{actions}}", _actions},
{"{{text}}", _text},
{"{{streamTitle}}", _streamTitle},
{"{{category}}", _category},
{"{{manualCategorySearch}}", _manualCategorySearch},
{"{{markerDescription}}", _markerDescription},
{"{{clipHasDelay}}", _clipHasDelay},
{"{{duration}}", _duration}});
_layout->setContentsMargins(0, 0, 0, 0);

Expand Down Expand Up @@ -247,14 +305,14 @@ void MacroActionTwitchEdit::TwitchTokenChanged(const QString &token)
emit(HeaderInfoChanged(token));
}

void MacroActionTwitchEdit::TextChanged()
void MacroActionTwitchEdit::StreamTitleChanged()
{
if (_loading || !_entryData) {
return;
}

auto lock = LockContext();
_entryData->_text = _text->text().toStdString();
_entryData->_streamTitle = _streamTitle->text().toStdString();
}

void MacroActionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
Expand All @@ -267,6 +325,26 @@ void MacroActionTwitchEdit::CategoreyChanged(const TwitchCategory &category)
_entryData->_category = category;
}

void MacroActionTwitchEdit::MarkerDescriptionChanged()
{
if (_loading || !_entryData) {
return;
}

auto lock = LockContext();
_entryData->_markerDescription = _markerDescription->text().toStdString();
}

void MacroActionTwitchEdit::ClipHasDelayChanged(int state)
{
if (_loading || !_entryData) {
return;
}

auto lock = LockContext();
_entryData->_clipHasDelay = state;
}

void MacroActionTwitchEdit::DurationChanged(const Duration &duration)
{
if (_loading || !_entryData) {
Expand All @@ -287,15 +365,21 @@ void MacroActionTwitchEdit::CheckTokenPermissions()

void MacroActionTwitchEdit::SetupWidgetVisibility()
{
_text->setVisible(_entryData->_action ==
MacroActionTwitch::Action::TITLE);
_streamTitle->setVisible(_entryData->_action ==
MacroActionTwitch::Action::TITLE);
_category->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CATEGORY);
MacroActionTwitch::Action::CATEGORY);
_manualCategorySearch->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CATEGORY);
MacroActionTwitch::Action::CATEGORY);
_markerDescription->setVisible(_entryData->_action ==
MacroActionTwitch::Action::MARKER);
_clipHasDelay->setVisible(_entryData->_action ==
MacroActionTwitch::Action::CLIP);
_duration->setVisible(_entryData->_action ==
MacroActionTwitch::Action::COMMERCIAL);
if (_entryData->_action == MacroActionTwitch::Action::TITLE) {
MacroActionTwitch::Action::COMMERCIAL);

if (_entryData->_action == MacroActionTwitch::Action::TITLE
|| _entryData->_action == MacroActionTwitch::Action::MARKER) {
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
Expand All @@ -316,10 +400,12 @@ void MacroActionTwitchEdit::UpdateEntryData()

_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_tokens->SetToken(_entryData->_token);
_text->setText(_entryData->_text);
_streamTitle->setText(_entryData->_streamTitle);
_category->SetToken(_entryData->_token);
_manualCategorySearch->SetToken(_entryData->_token);
_category->SetCategory(_entryData->_category);
_markerDescription->setText(_entryData->_markerDescription);
_clipHasDelay->setChecked(_entryData->_clipHasDelay);
_duration->SetDuration(_entryData->_duration);
SetupWidgetVisibility();
}
Expand Down
18 changes: 15 additions & 3 deletions src/macro-external/twitch/macro-action-twitch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,26 @@ class MacroActionTwitch : public MacroAction {
enum class Action {
TITLE,
CATEGORY,
MARKER,
CLIP,
COMMERCIAL,
};

Action _action = Action::TITLE;
std::weak_ptr<TwitchToken> _token;
StringVariable _text = obs_module_text("AdvSceneSwitcher.enterText");
StringVariable _streamTitle =
obs_module_text("AdvSceneSwitcher.action.twitch.title.title");
TwitchCategory _category;
StringVariable _markerDescription =
obs_module_text("AdvSceneSwitcher.action.twitch.marker.description");
bool _clipHasDelay = false;
Duration _duration = 60;

private:
void SetStreamTitle(const std::shared_ptr<TwitchToken> &) const;
void SetStreamCategory(const std::shared_ptr<TwitchToken> &) const;
void CreateStreamMarker(const std::shared_ptr<TwitchToken> &) const;
void CreateStreamClip(const std::shared_ptr<TwitchToken> &) const;
void StartCommercial(const std::shared_ptr<TwitchToken> &) const;

static bool _registered;
Expand All @@ -63,8 +71,10 @@ class MacroActionTwitchEdit : public QWidget {
private slots:
void ActionChanged(int);
void TwitchTokenChanged(const QString &);
void TextChanged();
void StreamTitleChanged();
void CategoreyChanged(const TwitchCategory &);
void MarkerDescriptionChanged();
void ClipHasDelayChanged(int state);
void DurationChanged(const Duration &);
void CheckTokenPermissions();

Expand All @@ -79,9 +89,11 @@ private slots:

QComboBox *_actions;
TwitchConnectionSelection *_tokens;
VariableLineEdit *_text;
VariableLineEdit *_streamTitle;
TwitchCategorySelection *_category;
TwitchCategorySearchButton *_manualCategorySearch;
VariableLineEdit *_markerDescription;
QCheckBox *_clipHasDelay;
DurationSelection *_duration;
QHBoxLayout *_layout;
QLabel *_tokenPermissionWarning;
Expand Down
1 change: 1 addition & 0 deletions src/macro-external/twitch/token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const std::unordered_map<std::string, std::string> TokenOption::apiIdToLocale{

{"channel:manage:broadcast",
"AdvSceneSwitcher.twitchToken.channel.manageBroadcast"},
{"clips:edit", "AdvSceneSwitcher.twitchToken.channel.createClip"},
{"channel:edit:commercial",
"AdvSceneSwitcher.twitchToken.channel.startCommercial"},
};
Expand Down

0 comments on commit 65a5e63

Please sign in to comment.