mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc59c1082 | ||
|
|
8b9f03a349 | ||
|
|
093b0f9ec0 | ||
|
|
254eabfa8d | ||
|
|
b2da8faf37 | ||
|
|
3eba20e2d8 | ||
|
|
08841aa88e | ||
|
|
2e96c226b5 | ||
|
|
c34308f743 | ||
|
|
4af07d9d55 | ||
|
|
d743260ca8 | ||
|
|
8ff50056f0 | ||
|
|
b786831f3b | ||
|
|
32e2a5f44c | ||
|
|
e3f277c32a | ||
|
|
5a7d76061c | ||
|
|
39a8c34fa6 | ||
|
|
ead51e2f13 |
@@ -42,6 +42,7 @@ endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
|
||||
git_describe(GIT_TAG)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
|
||||
|
||||
@@ -218,6 +219,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/macro-action-scene-visibility.hpp
|
||||
src/headers/macro-action-source.hpp
|
||||
src/headers/macro-action-streaming.hpp
|
||||
src/headers/macro-action-timer.hpp
|
||||
src/headers/macro-action-transition.hpp
|
||||
src/headers/macro-action-virtual-cam.hpp
|
||||
src/headers/macro-action-wait.hpp
|
||||
@@ -308,6 +310,7 @@ set(advanced-scene-switcher_SOURCES
|
||||
src/macro-action-scene-visibility.cpp
|
||||
src/macro-action-source.cpp
|
||||
src/macro-action-streaming.cpp
|
||||
src/macro-action-timer.cpp
|
||||
src/macro-action-transition.cpp
|
||||
src/macro-action-virtual-cam.cpp
|
||||
src/macro-action-wait.cpp
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "src/headers/version.h"
|
||||
#define GIT_SHA1 "@GIT_SHA1@"
|
||||
const char g_GIT_SHA1[] = GIT_SHA1;
|
||||
#define GIT_TAG "@GIT_TAG@"
|
||||
const char g_GIT_SHA1[] = GIT_SHA1;
|
||||
const char g_GIT_TAG[] = GIT_TAG;
|
||||
@@ -97,7 +97,8 @@ AdvSceneSwitcher.condition.scene.type.current="Current scene is"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
|
||||
AdvSceneSwitcher.condition.scene.type.changed="Scene chagned"
|
||||
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
|
||||
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}}"
|
||||
AdvSceneSwitcher.condition.scene.waitForTransition="Wait for transition to complete"
|
||||
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}} {{waitForTransition}}"
|
||||
AdvSceneSwitcher.condition.window="Window"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
|
||||
@@ -120,8 +121,10 @@ AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
|
||||
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
|
||||
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
|
||||
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
|
||||
AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: "
|
||||
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
|
||||
AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches."
|
||||
AdvSceneSwitcher.condition.video.showMatch="Show match"
|
||||
AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!"
|
||||
AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
|
||||
@@ -150,9 +153,11 @@ AdvSceneSwitcher.condition.pluginState="Plugin state"
|
||||
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval"
|
||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||
AdvSceneSwitcher.condition.timer="Timer"
|
||||
AdvSceneSwitcher.condition.timer.pause="Pause"
|
||||
AdvSceneSwitcher.condition.timer.continue="Continue"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} have passed"
|
||||
AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seconds"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{reset}} {{autoReset}} Automatically reset timer after duration was reached"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Save time remaining {{autoReset}} Automatically reset timer after duration was reached"
|
||||
AdvSceneSwitcher.condition.timer.reset="Reset"
|
||||
AdvSceneSwitcher.condition.macro="Macro"
|
||||
AdvSceneSwitcher.condition.macro.type.count="Count"
|
||||
@@ -208,7 +213,7 @@ AdvSceneSwitcher.condition.date.state.after="After"
|
||||
AdvSceneSwitcher.condition.date.state.before="Before"
|
||||
AdvSceneSwitcher.condition.date.state.between="Between"
|
||||
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component"
|
||||
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Reschedule {{duration}} later when matching"
|
||||
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Repeat every {{duration}} on date match"
|
||||
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
|
||||
@@ -329,6 +334,12 @@ AdvSceneSwitcher.action.SceneSwap.entry="Swap preview and program scene in studi
|
||||
AdvSceneSwitcher.action.transition="Transition"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds"
|
||||
AdvSceneSwitcher.action.timer="Timer"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Continue"
|
||||
AdvSceneSwitcher.action.timer.type.reset="Reset"
|
||||
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Set time remaining of"
|
||||
AdvSceneSwitcher.action.timer.entry="{{timerAction}} timers on {{macros}} {{duration}}"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
|
||||
@@ -258,13 +258,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_52">
|
||||
<item>
|
||||
@@ -362,20 +355,8 @@
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_41">
|
||||
<item>
|
||||
<widget class="QListWidget" name="priorityList"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_30">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<widget class="QListWidget" name="priorityList">
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
@@ -232,12 +232,17 @@ void SwitcherData::Thread()
|
||||
}
|
||||
}
|
||||
|
||||
// After this point we will call frontend functions
|
||||
// After this point we will call frontend functions like
|
||||
// obs_frontend_set_current_scene() and
|
||||
// obs_frontend_set_current_transition()
|
||||
//
|
||||
// During this time SaveSceneSwitcher() could be called
|
||||
// leading to a deadlock, so we have to unlock()
|
||||
// leading to a deadlock with the frontend function being stuck
|
||||
// in QMetaObject::invokeMethod() holding the mutex and
|
||||
// OBS being stuck in SaveSceneSwitcher().
|
||||
//
|
||||
// So we have to unlock() risking race conditions as these are
|
||||
// less frequent than the above described deadlock
|
||||
lock.unlock();
|
||||
|
||||
if (match) {
|
||||
@@ -599,6 +604,7 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
|
||||
extern "C" void InitSceneSwitcher()
|
||||
{
|
||||
blog(LOG_INFO, "version: %s", g_GIT_TAG);
|
||||
blog(LOG_INFO, "version: %s", g_GIT_SHA1);
|
||||
|
||||
QAction *action = (QAction *)obs_frontend_add_tools_menu_qaction(
|
||||
|
||||
@@ -46,6 +46,13 @@ double Duration::TimeRemaining()
|
||||
return (seconds * 1000 - runTime.count()) / 1000.;
|
||||
}
|
||||
|
||||
void Duration::SetTimeRemaining(double remaining)
|
||||
{
|
||||
long long msPassed = (seconds - remaining) * 1000;
|
||||
_startTime = std::chrono::high_resolution_clock::now() -
|
||||
std::chrono::milliseconds(msPassed);
|
||||
}
|
||||
|
||||
void Duration::Reset()
|
||||
{
|
||||
_startTime = {};
|
||||
|
||||
@@ -887,4 +887,6 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
QTimer *statusTimer = new QTimer(this);
|
||||
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
|
||||
statusTimer->start(1000);
|
||||
|
||||
setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public:
|
||||
void SetEditMacro(Macro &m);
|
||||
void HighlightAction(int idx);
|
||||
void HighlightCondition(int idx);
|
||||
void PopulateMacroActions(Macro &m, int afterIdx = 0);
|
||||
void PopulateMacroConditions(Macro &m, int afterIdx = 0);
|
||||
void ConnectControlSignals(MacroActionEdit *);
|
||||
void ConnectControlSignals(MacroConditionEdit *);
|
||||
void SwapActions(Macro *m, int pos1, int pos2);
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
|
||||
bool DurationReached();
|
||||
double TimeRemaining();
|
||||
void SetTimeRemaining(double);
|
||||
void Reset();
|
||||
std::string ToString();
|
||||
|
||||
|
||||
72
src/headers/macro-action-timer.hpp
Normal file
72
src/headers/macro-action-timer.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
#include "macro-selection.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
enum class TimerAction {
|
||||
PAUSE,
|
||||
CONTINUE,
|
||||
RESET,
|
||||
SET_TIME_REMAINING,
|
||||
};
|
||||
|
||||
class MacroActionTimer : public MacroRefAction {
|
||||
public:
|
||||
bool PerformAction();
|
||||
void LogAction();
|
||||
bool Save(obs_data_t *obj);
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc();
|
||||
std::string GetId() { return id; };
|
||||
static std::shared_ptr<MacroAction> Create()
|
||||
{
|
||||
return std::make_shared<MacroActionTimer>();
|
||||
}
|
||||
Duration _duration;
|
||||
TimerAction _actionType = TimerAction::PAUSE;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionTimerEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionTimerEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionTimer> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionTimerEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionTimer>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void MacroChanged(const QString &text);
|
||||
void MacroRemove(const QString &name);
|
||||
void DurationChanged(double value);
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
void ActionTypeChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
MacroSelection *_macros;
|
||||
DurationSelection *_duration;
|
||||
QComboBox *_timerAction;
|
||||
std::shared_ptr<MacroActionTimer> _entryData;
|
||||
|
||||
private:
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
enum class SceneType {
|
||||
CURRENT,
|
||||
@@ -24,6 +25,7 @@ public:
|
||||
return std::make_shared<MacroConditionScene>();
|
||||
}
|
||||
|
||||
bool _waitForTransition = false;
|
||||
SceneSelection _scene;
|
||||
SceneType _type;
|
||||
|
||||
@@ -52,12 +54,14 @@ public:
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void TypeChanged(int value);
|
||||
void WaitForTransitionChanged(int state);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
QComboBox *_sceneType;
|
||||
QCheckBox *_waitForTransition;
|
||||
std::shared_ptr<MacroConditionScene> _entryData;
|
||||
|
||||
private:
|
||||
|
||||
@@ -16,8 +16,14 @@ public:
|
||||
{
|
||||
return std::make_shared<MacroConditionTimer>();
|
||||
}
|
||||
void Pause();
|
||||
void Continue();
|
||||
void Reset();
|
||||
|
||||
Duration _duration;
|
||||
bool _paused = false;
|
||||
bool _saveRemaining = false;
|
||||
double _remaining = 0.;
|
||||
bool _oneshot = false;
|
||||
|
||||
private:
|
||||
@@ -44,14 +50,20 @@ public:
|
||||
private slots:
|
||||
void DurationChanged(double seconds);
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
void SaveRemainingChanged(int state);
|
||||
void AutoResetChanged(int state);
|
||||
void PauseContinueClicked();
|
||||
void ResetClicked();
|
||||
void UpdateTimeRemaining();
|
||||
|
||||
protected:
|
||||
void SetPauseContinueButtonLabel();
|
||||
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_autoReset;
|
||||
QCheckBox *_saveRemaining;
|
||||
QPushButton *_reset;
|
||||
QPushButton *_pauseConinue;
|
||||
QLabel *_remaining;
|
||||
std::shared_ptr<MacroConditionTimer> _entryData;
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ enum class VideoCondition {
|
||||
OBJECT,
|
||||
};
|
||||
|
||||
struct PatternMatchData {
|
||||
cv::Mat4b rgbaPattern;
|
||||
cv::Mat3b rgbPattern;
|
||||
cv::Mat1b mask;
|
||||
};
|
||||
|
||||
constexpr int minMinNeighbors = 3;
|
||||
constexpr int maxMinNeighbors = 6;
|
||||
|
||||
@@ -44,6 +50,8 @@ public:
|
||||
OBSWeakSource _videoSource;
|
||||
VideoCondition _condition = VideoCondition::MATCH;
|
||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
bool _useAlphaAsMask = false;
|
||||
PatternMatchData _patternData;
|
||||
double _patternThreshold = 0.8;
|
||||
cv::CascadeClassifier _objectCascade;
|
||||
double _scaleFactor = 1.1;
|
||||
@@ -120,6 +128,7 @@ private slots:
|
||||
void ImagePathChanged(const QString &text);
|
||||
void ImageBrowseButtonClicked();
|
||||
void PatternThresholdChanged(double);
|
||||
void UseAlphaAsMaskChanged(int value);
|
||||
|
||||
void ModelPathChanged(const QString &text);
|
||||
void ObjectScaleThresholdChanged(double);
|
||||
@@ -141,12 +150,14 @@ protected:
|
||||
|
||||
FileSelection *_imagePath;
|
||||
ThresholdSlider *_patternThreshold;
|
||||
QCheckBox *_useAlphaAsMask;
|
||||
|
||||
FileSelection *_modelDataPath;
|
||||
QHBoxLayout *_modelPathLayout;
|
||||
ThresholdSlider *_objectScaleThreshold;
|
||||
QHBoxLayout *_neighborsControlLayout;
|
||||
QSpinBox *_minNeighbors;
|
||||
QLabel *_minNeighborsDescription;
|
||||
QHBoxLayout *_minSizeControlLayout;
|
||||
QSpinBox *_minSizeX;
|
||||
QSpinBox *_minSizeY;
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
// Use this function to avoid accidental edits when scrolling through
|
||||
// list of actions and conditions
|
||||
void SetFocusPolicyOfWidgets();
|
||||
void SetCollapsed(bool collapsed);
|
||||
|
||||
protected slots:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "switch-generic.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
enum class sceneTriggerType {
|
||||
NONE = 0,
|
||||
@@ -43,7 +44,7 @@ struct SceneTrigger : SceneSwitcherEntry {
|
||||
static bool pause;
|
||||
sceneTriggerType triggerType = sceneTriggerType::NONE;
|
||||
sceneTriggerAction triggerAction = sceneTriggerAction::NONE;
|
||||
double duration = 0;
|
||||
Duration duration;
|
||||
OBSWeakSource audioSource = nullptr;
|
||||
|
||||
const char *getType() { return "trigger"; }
|
||||
@@ -70,13 +71,14 @@ public:
|
||||
private slots:
|
||||
void TriggerTypeChanged(int index);
|
||||
void TriggerActionChanged(int index);
|
||||
void DurationChanged(double dur);
|
||||
void DurationChanged(double seconds);
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
void AudioSourceChanged(const QString &text);
|
||||
|
||||
private:
|
||||
QComboBox *triggers;
|
||||
QComboBox *actions;
|
||||
QDoubleSpinBox *duration;
|
||||
DurationSelection *duration;
|
||||
QComboBox *audioSources;
|
||||
|
||||
SceneTrigger *switchData;
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
|
||||
void SetContent(QWidget *w, bool collapsed = true);
|
||||
void AddHeaderWidget(QWidget *);
|
||||
void SetCollapsed(bool);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
@@ -44,7 +44,7 @@ void placeWidgets(std::string text, QBoxLayout *layout,
|
||||
std::unordered_map<std::string, QWidget *> placeholders,
|
||||
bool addStretch = true);
|
||||
void deleteLayoutItem(QLayoutItem *item);
|
||||
void clearLayout(QLayout *layout);
|
||||
void clearLayout(QLayout *layout, int afterIdx = 0);
|
||||
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
|
||||
QColor = QColor(0, 0, 0, 0),
|
||||
QString specifier = "QLabel ",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
extern const char g_GIT_SHA1[];
|
||||
extern const char g_GIT_TAG[];
|
||||
|
||||
@@ -152,12 +152,8 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
|
||||
}
|
||||
macro->UpdateActionIndices();
|
||||
|
||||
// All entry pointers in existing edit widgets after the new entry will
|
||||
// be invalidated by adding the new entry so we have to recreate them
|
||||
// and because I am lazy I am recreating every widget.
|
||||
//
|
||||
// If performance should become a concern this has to be revisited.
|
||||
SetEditMacro(*macro);
|
||||
clearLayout(ui->macroEditActionLayout, idx);
|
||||
PopulateMacroActions(*macro, idx);
|
||||
HighlightAction(idx);
|
||||
}
|
||||
|
||||
@@ -189,12 +185,8 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||
switcher->macroWaitCv.notify_one();
|
||||
macro->UpdateActionIndices();
|
||||
|
||||
// All entry pointers in existing edit widgets after the new entry will
|
||||
// be invalidated by adding the new entry so we have to recreate them
|
||||
// and because I am lazy I am recreating every widget.
|
||||
//
|
||||
// If performance should become a concern this has to be revisited.
|
||||
SetEditMacro(*macro);
|
||||
clearLayout(ui->macroEditActionLayout, idx);
|
||||
PopulateMacroActions(*macro, idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_actionRemove_clicked()
|
||||
|
||||
228
src/macro-action-timer.cpp
Normal file
228
src/macro-action-timer.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "headers/macro-action-timer.hpp"
|
||||
#include "headers/macro-condition-timer.hpp"
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
const std::string MacroActionTimer::id = "timer";
|
||||
|
||||
bool MacroActionTimer::_registered = MacroActionFactory::Register(
|
||||
MacroActionTimer::id,
|
||||
{MacroActionTimer::Create, MacroActionTimerEdit::Create,
|
||||
"AdvSceneSwitcher.action.Timer"});
|
||||
|
||||
static std::map<TimerAction, std::string> timerActions = {
|
||||
{TimerAction::PAUSE, "AdvSceneSwitcher.action.timer.type.pause"},
|
||||
{TimerAction::CONTINUE, "AdvSceneSwitcher.action.timer.type.continue"},
|
||||
{TimerAction::RESET, "AdvSceneSwitcher.action.timer.type.reset"},
|
||||
{TimerAction::SET_TIME_REMAINING,
|
||||
"AdvSceneSwitcher.action.timer.type.setTimeRemaining"},
|
||||
};
|
||||
|
||||
bool MacroActionTimer::PerformAction()
|
||||
{
|
||||
if (!_macro.get()) {
|
||||
return true;
|
||||
}
|
||||
for (auto c : _macro->Conditions()) {
|
||||
if (c->GetId() != "timer") {
|
||||
continue;
|
||||
}
|
||||
auto timerCondition =
|
||||
dynamic_cast<MacroConditionTimer *>(c.get());
|
||||
if (!timerCondition) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (_actionType) {
|
||||
case TimerAction::PAUSE:
|
||||
timerCondition->Pause();
|
||||
break;
|
||||
case TimerAction::CONTINUE:
|
||||
timerCondition->Continue();
|
||||
break;
|
||||
case TimerAction::RESET:
|
||||
timerCondition->Reset();
|
||||
break;
|
||||
case TimerAction::SET_TIME_REMAINING:
|
||||
timerCondition->_duration.SetTimeRemaining(
|
||||
_duration.seconds);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionTimer::LogAction()
|
||||
{
|
||||
if (!_macro.get()) {
|
||||
return;
|
||||
}
|
||||
switch (_actionType) {
|
||||
case TimerAction::PAUSE:
|
||||
vblog(LOG_INFO, "paused timers on \"%s\"",
|
||||
_macro->Name().c_str());
|
||||
break;
|
||||
case TimerAction::CONTINUE:
|
||||
vblog(LOG_INFO, "continued timers on \"%s\"",
|
||||
_macro->Name().c_str());
|
||||
break;
|
||||
case TimerAction::RESET:
|
||||
vblog(LOG_INFO, "reset timers on \"%s\"",
|
||||
_macro->Name().c_str());
|
||||
break;
|
||||
case TimerAction::SET_TIME_REMAINING:
|
||||
vblog(LOG_INFO,
|
||||
"set time remaining of timers on \"%s\" to \"%s\"",
|
||||
_macro->Name().c_str(), _duration.ToString().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionTimer::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_macro.Save(obj);
|
||||
_duration.Save(obj);
|
||||
obs_data_set_int(obj, "actionType", static_cast<int>(_actionType));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionTimer::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_macro.Load(obj);
|
||||
_duration.Load(obj);
|
||||
_actionType =
|
||||
static_cast<TimerAction>(obs_data_get_int(obj, "actionType"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionTimer::GetShortDesc()
|
||||
{
|
||||
if (_macro.get()) {
|
||||
return _macro->Name();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : timerActions) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionTimerEdit::MacroActionTimerEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionTimer> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_macros = new MacroSelection(parent);
|
||||
_duration = new DurationSelection();
|
||||
_timerAction = new QComboBox();
|
||||
|
||||
populateTypeSelection(_timerAction);
|
||||
|
||||
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(MacroChanged(const QString &)));
|
||||
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
|
||||
SLOT(MacroRemove(const QString &)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
QWidget::connect(_timerAction, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionTypeChanged(int)));
|
||||
|
||||
_mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{macros}}", _macros},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{timerAction}}", _timerAction},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.timer.entry"),
|
||||
_mainLayout, widgetPlaceholders);
|
||||
setLayout(_mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_macros->SetCurrentMacro(_entryData->_macro.get());
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_timerAction->setCurrentIndex(
|
||||
static_cast<int>(_entryData->_actionType));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::ActionTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_actionType = static_cast<TimerAction>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_duration->setVisible(_entryData->_actionType ==
|
||||
TimerAction::SET_TIME_REMAINING);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::DurationChanged(double seconds)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.seconds = seconds;
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::DurationUnitChanged(DurationUnit unit)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.displayUnit = unit;
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::MacroChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_macro.UpdateRef(text);
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionTimerEdit::MacroRemove(const QString &)
|
||||
{
|
||||
if (_entryData) {
|
||||
_entryData->_macro.UpdateRef();
|
||||
}
|
||||
}
|
||||
@@ -276,12 +276,8 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
|
||||
(*cond)->SetLogicType(logic);
|
||||
macro->UpdateConditionIndices();
|
||||
|
||||
// All entry pointers in existing edit widgets after the new entry will
|
||||
// be invalidated by adding the new entry so we have to recreate them
|
||||
// and because I am lazy I am recreating every widget.
|
||||
//
|
||||
// If performance should become a concern this has to be revisited.
|
||||
SetEditMacro(*macro);
|
||||
clearLayout(ui->macroEditConditionLayout, idx);
|
||||
PopulateMacroConditions(*macro, idx);
|
||||
HighlightCondition(idx);
|
||||
}
|
||||
|
||||
@@ -311,21 +307,13 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
||||
macro->UpdateConditionIndices();
|
||||
|
||||
if (macro->Conditions().size() == 0) {
|
||||
ui->macroEditConditionHelp->setVisible(true);
|
||||
}
|
||||
|
||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||
auto newRoot = macro->Conditions().at(0);
|
||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||
}
|
||||
|
||||
// All entry pointers in existing edit widgets after the new entry will
|
||||
// be invalidated by adding the new entry so we have to recreate them
|
||||
// and because I am lazy I am recreating every widget.
|
||||
//
|
||||
// If performance should become a concern this has to be revisited.
|
||||
SetEditMacro(*macro);
|
||||
clearLayout(ui->macroEditConditionLayout, idx);
|
||||
PopulateMacroConditions(*macro, idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_conditionRemove_clicked()
|
||||
|
||||
@@ -28,7 +28,18 @@ bool MacroConditionScene::CheckCondition()
|
||||
|
||||
switch (_type) {
|
||||
case SceneType::CURRENT:
|
||||
return switcher->currentScene == _scene.GetScene(false);
|
||||
if (_waitForTransition) {
|
||||
return switcher->currentScene == _scene.GetScene(false);
|
||||
} else {
|
||||
|
||||
bool match = false;
|
||||
auto current = obs_frontend_get_current_scene();
|
||||
auto weak = obs_source_get_weak_source(current);
|
||||
match = weak == _scene.GetScene(false);
|
||||
obs_weak_source_release(weak);
|
||||
obs_source_release(current);
|
||||
return match;
|
||||
}
|
||||
case SceneType::PREVIOUS:
|
||||
return switcher->previousScene == _scene.GetScene(false);
|
||||
case SceneType::CHANGED:
|
||||
@@ -47,6 +58,7 @@ bool MacroConditionScene::Save(obs_data_t *obj)
|
||||
MacroCondition::Save(obj);
|
||||
_scene.Save(obj);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_bool(obj, "waitForTransition", _waitForTransition);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,6 +67,12 @@ bool MacroConditionScene::Load(obs_data_t *obj)
|
||||
MacroCondition::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_type = static_cast<SceneType>(obs_data_get_int(obj, "type"));
|
||||
if (!obs_data_has_user_value(obj, "waitForTransition")) {
|
||||
_waitForTransition = true;
|
||||
} else {
|
||||
_waitForTransition =
|
||||
obs_data_get_bool(obj, "waitForTransition");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,11 +94,15 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, false);
|
||||
_sceneType = new QComboBox();
|
||||
_waitForTransition = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.scene.waitForTransition"));
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sceneType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TypeChanged(int)));
|
||||
QWidget::connect(_waitForTransition, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(WaitForTransitionChanged(int)));
|
||||
|
||||
populateTypeSelection(_sceneType);
|
||||
|
||||
@@ -88,6 +110,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{sceneType}}", _sceneType},
|
||||
{"{{waitForTransition}}", _waitForTransition},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.scene.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
@@ -121,10 +144,21 @@ void MacroConditionSceneEdit::TypeChanged(int value)
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionSceneEdit::WaitForTransitionChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_waitForTransition = state;
|
||||
}
|
||||
|
||||
void MacroConditionSceneEdit::SetWidgetVisibility()
|
||||
{
|
||||
_scenes->setVisible(_entryData->_type == SceneType::CURRENT ||
|
||||
_entryData->_type == SceneType::PREVIOUS);
|
||||
_waitForTransition->setVisible(_entryData->_type == SceneType::CURRENT);
|
||||
}
|
||||
|
||||
void MacroConditionSceneEdit::UpdateEntryData()
|
||||
@@ -135,5 +169,6 @@ void MacroConditionSceneEdit::UpdateEntryData()
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_waitForTransition->setChecked(_entryData->_waitForTransition);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ bool MacroConditionTimer::_registered = MacroConditionFactory::Register(
|
||||
|
||||
bool MacroConditionTimer::CheckCondition()
|
||||
{
|
||||
if (_paused) {
|
||||
return _remaining == 0.;
|
||||
}
|
||||
if (_duration.DurationReached()) {
|
||||
if (!_oneshot) {
|
||||
_duration.Reset();
|
||||
@@ -25,6 +28,16 @@ bool MacroConditionTimer::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_duration.Save(obj);
|
||||
if (!_paused) {
|
||||
_remaining = _duration.TimeRemaining();
|
||||
}
|
||||
if (_saveRemaining) {
|
||||
obs_data_set_double(obj, "remaining", _remaining);
|
||||
} else {
|
||||
obs_data_set_double(obj, "remaining", _duration.seconds);
|
||||
}
|
||||
obs_data_set_bool(obj, "saveRemaining", _saveRemaining);
|
||||
obs_data_set_bool(obj, "paused", _paused);
|
||||
obs_data_set_bool(obj, "oneshot", _oneshot);
|
||||
return true;
|
||||
}
|
||||
@@ -33,20 +46,49 @@ bool MacroConditionTimer::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_duration.Load(obj);
|
||||
_remaining = obs_data_get_double(obj, "remaining");
|
||||
_paused = obs_data_get_bool(obj, "paused");
|
||||
_saveRemaining = obs_data_get_bool(obj, "saveRemaining");
|
||||
if (!obs_data_has_user_value(obj, "oneshot")) {
|
||||
_oneshot = false;
|
||||
} else {
|
||||
_oneshot = obs_data_get_bool(obj, "oneshot");
|
||||
}
|
||||
_duration.SetTimeRemaining(_remaining);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroConditionTimer::Pause()
|
||||
{
|
||||
if (!_paused) {
|
||||
_paused = true;
|
||||
_remaining = _duration.TimeRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionTimer::Continue()
|
||||
{
|
||||
if (_paused) {
|
||||
_paused = false;
|
||||
_duration.SetTimeRemaining(_remaining);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionTimer::Reset()
|
||||
{
|
||||
_remaining = _duration.seconds;
|
||||
_duration.Reset();
|
||||
}
|
||||
|
||||
MacroConditionTimerEdit::MacroConditionTimerEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionTimer> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_duration = new DurationSelection();
|
||||
_autoReset = new QCheckBox();
|
||||
_saveRemaining = new QCheckBox();
|
||||
_pauseConinue = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.condition.timer.pause"));
|
||||
_reset = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.condition.timer.reset"));
|
||||
_remaining = new QLabel();
|
||||
@@ -55,16 +97,22 @@ MacroConditionTimerEdit::MacroConditionTimerEdit(
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
QWidget::connect(_pauseConinue, SIGNAL(clicked()), this,
|
||||
SLOT(PauseContinueClicked()));
|
||||
QWidget::connect(_reset, SIGNAL(clicked()), this, SLOT(ResetClicked()));
|
||||
QWidget::connect(_autoReset, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(AutoResetChanged(int)));
|
||||
QWidget::connect(_saveRemaining, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(SaveRemainingChanged(int)));
|
||||
|
||||
auto line1Layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{duration}}", _duration},
|
||||
{"{{autoReset}}", _autoReset},
|
||||
{"{{remaining}}", _remaining},
|
||||
{"{{pauseContinue}}", _pauseConinue},
|
||||
{"{{reset}}", _reset},
|
||||
{"{{saveRemaining}}", _saveRemaining},
|
||||
};
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.timer.entry.line1"),
|
||||
@@ -112,6 +160,16 @@ void MacroConditionTimerEdit::DurationUnitChanged(DurationUnit unit)
|
||||
_entryData->_duration.displayUnit = unit;
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::SaveRemainingChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_saveRemaining = state;
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::AutoResetChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -122,6 +180,23 @@ void MacroConditionTimerEdit::AutoResetChanged(int state)
|
||||
_entryData->_oneshot = !state;
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::PauseContinueClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
if (_entryData->_paused) {
|
||||
timer.start(1000);
|
||||
_entryData->Continue();
|
||||
} else {
|
||||
_entryData->Pause();
|
||||
timer.stop();
|
||||
}
|
||||
SetPauseContinueButtonLabel();
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::ResetClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -129,7 +204,7 @@ void MacroConditionTimerEdit::ResetClicked()
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.Reset();
|
||||
_entryData->Reset();
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::UpdateTimeRemaining()
|
||||
@@ -138,8 +213,28 @@ void MacroConditionTimerEdit::UpdateTimeRemaining()
|
||||
_remaining->setText("-");
|
||||
return;
|
||||
}
|
||||
_remaining->setText(
|
||||
QString::number(_entryData->_duration.TimeRemaining()));
|
||||
|
||||
if (_entryData->_paused) {
|
||||
_remaining->setText(QString::number(_entryData->_remaining));
|
||||
} else {
|
||||
_remaining->setText(
|
||||
QString::number(_entryData->_duration.TimeRemaining()));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::SetPauseContinueButtonLabel()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entryData->_paused) {
|
||||
_pauseConinue->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.timer.continue"));
|
||||
} else {
|
||||
_pauseConinue->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.timer.pause"));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionTimerEdit::UpdateEntryData()
|
||||
@@ -150,4 +245,6 @@ void MacroConditionTimerEdit::UpdateEntryData()
|
||||
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_autoReset->setChecked(!_entryData->_oneshot);
|
||||
_saveRemaining->setChecked(_entryData->_saveRemaining);
|
||||
SetPauseContinueButtonLabel();
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "filePath", _file.c_str());
|
||||
obs_data_set_double(obj, "threshold", _patternThreshold);
|
||||
obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask);
|
||||
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
|
||||
obs_data_set_double(obj, "scaleFactor", _scaleFactor);
|
||||
obs_data_set_int(obj, "minNeighbors", _minNeighbors);
|
||||
@@ -132,13 +133,14 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
|
||||
_file = obs_data_get_string(obj, "filePath");
|
||||
_patternThreshold = obs_data_get_double(obj, "threshold");
|
||||
_useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask");
|
||||
_modelDataPath = obs_data_get_string(obj, "modelDataPath");
|
||||
_scaleFactor = obs_data_get_double(obj, "scaleFactor");
|
||||
if (!isScaleFactorValid(_scaleFactor)) {
|
||||
_scaleFactor = 1.1;
|
||||
}
|
||||
_minNeighbors = obs_data_get_int(obj, "minNeighbors");
|
||||
if (!isMinNeighborsValid(_scaleFactor)) {
|
||||
if (!isMinNeighborsValid(_minNeighbors)) {
|
||||
_minNeighbors = minMinNeighbors;
|
||||
}
|
||||
_minSizeX = obs_data_get_int(obj, "minSizeX");
|
||||
@@ -174,26 +176,7 @@ void MacroConditionVideo::GetScreenshot()
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::LoadImageFromFile()
|
||||
{
|
||||
if (!_matchImage.load(QString::fromStdString(_file))) {
|
||||
blog(LOG_WARNING, "Cannot load image data from file '%s'",
|
||||
_file.c_str());
|
||||
return false;
|
||||
}
|
||||
_matchImage =
|
||||
_matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::LoadModelData(std::string &path)
|
||||
{
|
||||
_modelDataPath = path;
|
||||
_objectCascade = initObjectCascade(path);
|
||||
return !_objectCascade.empty();
|
||||
}
|
||||
|
||||
// Assumption is that QImage uses Format_RGBX8888.
|
||||
// Assumption is that QImage uses Format_RGBA8888.
|
||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||
cv::Mat QImageToMat(const QImage &img)
|
||||
{
|
||||
@@ -210,31 +193,94 @@ QImage MatToQImage(const cv::Mat &mat)
|
||||
return QImage();
|
||||
}
|
||||
return QImage(mat.data, mat.cols, mat.rows, mat.step,
|
||||
QImage::Format::Format_RGBX8888);
|
||||
QImage::Format::Format_RGBA8888);
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, QImage &pattern, double threshold,
|
||||
cv::Mat &result)
|
||||
PatternMatchData createPatternData(QImage &pattern)
|
||||
{
|
||||
if (img.isNull() || pattern.isNull()) {
|
||||
PatternMatchData data;
|
||||
if (pattern.isNull()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data.rgbaPattern = QImageToMat(pattern);
|
||||
std::vector<cv::Mat1b> rgbaChannelsPattern;
|
||||
cv::split(data.rgbaPattern, rgbaChannelsPattern);
|
||||
std::vector<cv::Mat1b> rgbChanlesPattern(
|
||||
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
|
||||
cv::merge(rgbChanlesPattern, data.rgbPattern);
|
||||
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
|
||||
cv::THRESH_BINARY);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::LoadImageFromFile()
|
||||
{
|
||||
if (!_matchImage.load(QString::fromStdString(_file))) {
|
||||
blog(LOG_WARNING, "Cannot load image data from file '%s'",
|
||||
_file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
_matchImage =
|
||||
_matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
|
||||
_patternData = createPatternData(_matchImage);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::LoadModelData(std::string &path)
|
||||
{
|
||||
_modelDataPath = path;
|
||||
_objectCascade = initObjectCascade(path);
|
||||
return !_objectCascade.empty();
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask = true)
|
||||
{
|
||||
if (img.isNull() || patternData.rgbaPattern.empty()) {
|
||||
return;
|
||||
}
|
||||
if (img.height() < pattern.height() || img.width() < pattern.width()) {
|
||||
if (img.height() < patternData.rgbaPattern.rows ||
|
||||
img.width() < patternData.rgbaPattern.cols) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto i = QImageToMat(img);
|
||||
auto p = QImageToMat(pattern);
|
||||
|
||||
cv::matchTemplate(i, p, result, cv::TM_CCOEFF_NORMED);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
if (useAlphaAsMask) {
|
||||
std::vector<cv::Mat1b> rgbaChannelsImage;
|
||||
cv::split(i, rgbaChannelsImage);
|
||||
std::vector<cv::Mat1b> rgbChanlesImage(
|
||||
rgbaChannelsImage.begin(),
|
||||
rgbaChannelsImage.begin() + 3);
|
||||
|
||||
cv::Mat3b rgbImage;
|
||||
cv::merge(rgbChanlesImage, rgbImage);
|
||||
|
||||
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
|
||||
cv::TM_CCORR_NORMED, patternData.mask);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
|
||||
} else {
|
||||
cv::matchTemplate(i, patternData.rgbaPattern, result,
|
||||
cv::TM_CCOEFF_NORMED);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
}
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, QImage &pattern, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask)
|
||||
{
|
||||
auto data = createPatternData(pattern);
|
||||
matchPattern(img, data, threshold, result, useAlphaAsMask);
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::ScreenshotContainsPattern()
|
||||
{
|
||||
cv::Mat result;
|
||||
matchPattern(_screenshotData->image, _matchImage, _patternThreshold,
|
||||
result);
|
||||
matchPattern(_screenshotData->image, _patternData, _patternThreshold,
|
||||
result, _useAlphaAsMask);
|
||||
return countNonZero(result) > 0;
|
||||
}
|
||||
|
||||
@@ -358,6 +404,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
"AdvSceneSwitcher.condition.video.patternThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdDescription"));
|
||||
_useAlphaAsMask = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"));
|
||||
|
||||
_modelDataPath = new FileSelection();
|
||||
_objectScaleThreshold = new ThresholdSlider(
|
||||
@@ -369,6 +417,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
_minNeighbors = new QSpinBox();
|
||||
_minNeighbors->setMinimum(minMinNeighbors);
|
||||
_minNeighbors->setMaximum(maxMinNeighbors);
|
||||
_minNeighborsDescription = new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.minNeighborDescription"));
|
||||
_minSizeX = new QSpinBox();
|
||||
_minSizeY = new QSpinBox();
|
||||
_minSizeX->setMaximum(1024);
|
||||
@@ -397,6 +447,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
SLOT(ImageBrowseButtonClicked()));
|
||||
QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)),
|
||||
this, SLOT(PatternThresholdChanged(double)));
|
||||
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseAlphaAsMaskChanged(int)));
|
||||
QWidget::connect(_objectScaleThreshold,
|
||||
SIGNAL(DoubleValueChanged(double)), this,
|
||||
SLOT(ObjectScaleThresholdChanged(double)));
|
||||
@@ -472,9 +524,11 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLine1Layout);
|
||||
mainLayout->addWidget(_patternThreshold);
|
||||
mainLayout->addWidget(_useAlphaAsMask);
|
||||
mainLayout->addLayout(_modelPathLayout);
|
||||
mainLayout->addWidget(_objectScaleThreshold);
|
||||
mainLayout->addLayout(_neighborsControlLayout);
|
||||
mainLayout->addWidget(_minNeighborsDescription);
|
||||
mainLayout->addLayout(_minSizeControlLayout);
|
||||
mainLayout->addLayout(_maxSizeControlLayout);
|
||||
mainLayout->addLayout(showMatchLayout);
|
||||
@@ -525,11 +579,6 @@ void MacroConditionVideoEdit::SourceChanged(const QString &text)
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
bool needsPatternThreshold(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::PATTERN;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -644,6 +693,17 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
|
||||
_entryData->_patternThreshold = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_useAlphaAsMask = value;
|
||||
_entryData->LoadImageFromFile();
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -734,7 +794,7 @@ QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
|
||||
rectangle(matchImg, {col, row},
|
||||
cv::Point(col + pattern.width(),
|
||||
row + pattern.height()),
|
||||
cv::Scalar(255, 0, 0, 0), 2, 8, 0);
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,7 +808,7 @@ QImage markObjects(QImage &image, std::vector<cv::Rect> &objects)
|
||||
rectangle(frame, cv::Point(objects[i].x, objects[i].y),
|
||||
cv::Point(objects[i].x + objects[i].width,
|
||||
objects[i].y + objects[i].height),
|
||||
cv::Scalar(255, 0, 0, 0), 2, 8, 0);
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
return MatToQImage(frame);
|
||||
}
|
||||
@@ -772,7 +832,8 @@ void MacroConditionVideoEdit::ShowMatchClicked()
|
||||
if (_entryData->_condition == VideoCondition::PATTERN) {
|
||||
cv::Mat result;
|
||||
matchPattern(screenshot->image, pattern,
|
||||
_entryData->_patternThreshold, result);
|
||||
_entryData->_patternThreshold, result,
|
||||
_entryData->_useAlphaAsMask);
|
||||
if (countNonZero(result) == 0) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternMatchFail"));
|
||||
@@ -851,13 +912,17 @@ void setLayoutVisible(QLayout *layout, bool visible)
|
||||
void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
{
|
||||
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
|
||||
_patternThreshold->setVisible(
|
||||
needsPatternThreshold(_entryData->_condition));
|
||||
_patternThreshold->setVisible(_entryData->_condition ==
|
||||
VideoCondition::PATTERN);
|
||||
_useAlphaAsMask->setVisible(_entryData->_condition ==
|
||||
VideoCondition::PATTERN);
|
||||
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
|
||||
_objectScaleThreshold->setVisible(
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_neighborsControlLayout,
|
||||
needsObjectControls(_entryData->_condition));
|
||||
_minNeighborsDescription->setVisible(
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_minSizeControlLayout,
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_maxSizeControlLayout,
|
||||
@@ -880,6 +945,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
|
||||
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
|
||||
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
|
||||
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
|
||||
_minNeighbors->setValue(_entryData->_minNeighbors);
|
||||
|
||||
@@ -145,3 +145,8 @@ void MacroSegmentEdit::SetFocusPolicyOfWidgets()
|
||||
w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::SetCollapsed(bool collapsed)
|
||||
{
|
||||
_section->SetCollapsed(collapsed);
|
||||
}
|
||||
|
||||
@@ -170,42 +170,46 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
|
||||
emit MacroRenamed(oldName, newName);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::PopulateMacroActions(Macro &m, int afterIdx)
|
||||
{
|
||||
bool root = afterIdx == 0;
|
||||
auto &actions = m.Actions();
|
||||
for (; afterIdx < actions.size(); afterIdx++) {
|
||||
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
||||
actions[afterIdx]->GetId());
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditActionLayout->addWidget(newEntry);
|
||||
ui->macroEditActionHelp->setVisible(false);
|
||||
root = false;
|
||||
}
|
||||
ui->macroEditActionHelp->setVisible(actions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, int afterIdx)
|
||||
{
|
||||
bool root = afterIdx == 0;
|
||||
auto &conditions = m.Conditions();
|
||||
for (; afterIdx < conditions.size(); afterIdx++) {
|
||||
auto newEntry = new MacroConditionEdit(
|
||||
this, &conditions[afterIdx],
|
||||
conditions[afterIdx]->GetId(), root);
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditConditionLayout->addWidget(newEntry);
|
||||
ui->macroEditConditionHelp->setVisible(false);
|
||||
root = false;
|
||||
}
|
||||
ui->macroEditConditionHelp->setVisible(conditions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||
{
|
||||
ui->macroName->setText(m.Name().c_str());
|
||||
clearLayout(ui->macroEditConditionLayout);
|
||||
clearLayout(ui->macroEditActionLayout);
|
||||
|
||||
bool root = true;
|
||||
for (auto &c : m.Conditions()) {
|
||||
auto newEntry =
|
||||
new MacroConditionEdit(this, &c, c->GetId(), root);
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditConditionLayout->addWidget(newEntry);
|
||||
ui->macroEditConditionHelp->setVisible(false);
|
||||
root = false;
|
||||
}
|
||||
|
||||
for (auto &a : m.Actions()) {
|
||||
auto newEntry = new MacroActionEdit(this, &a, a->GetId());
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditActionLayout->addWidget(newEntry);
|
||||
ui->macroEditActionHelp->setVisible(false);
|
||||
}
|
||||
|
||||
PopulateMacroConditions(m);
|
||||
PopulateMacroActions(m);
|
||||
ui->macroEdit->setDisabled(false);
|
||||
|
||||
if (m.Conditions().size() == 0) {
|
||||
ui->macroEditConditionHelp->setVisible(true);
|
||||
} else {
|
||||
ui->macroEditConditionHelp->setVisible(false);
|
||||
}
|
||||
|
||||
if (m.Actions().size() == 0) {
|
||||
ui->macroEditActionHelp->setVisible(true);
|
||||
} else {
|
||||
ui->macroEditActionHelp->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightAction(int idx)
|
||||
@@ -400,17 +404,25 @@ void AdvSceneSwitcher::CopyMacro()
|
||||
ui->macros->setCurrentItem(item);
|
||||
}
|
||||
|
||||
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
|
||||
{
|
||||
QLayoutItem *item = nullptr;
|
||||
for (int i = 0; i < layout->count(); i++) {
|
||||
item = layout->itemAt(i);
|
||||
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
|
||||
if (segment) {
|
||||
segment->SetCollapsed(collapse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllActions()
|
||||
{
|
||||
auto m = getSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &a : m->Actions()) {
|
||||
a->SetCollapsed(false);
|
||||
}
|
||||
SetEditMacro(*m);
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllConditions()
|
||||
@@ -419,11 +431,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &c : m->Conditions()) {
|
||||
c->SetCollapsed(false);
|
||||
}
|
||||
SetEditMacro(*m);
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllActions()
|
||||
@@ -432,11 +440,7 @@ void AdvSceneSwitcher::CollapseAllActions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &a : m->Actions()) {
|
||||
a->SetCollapsed(true);
|
||||
}
|
||||
SetEditMacro(*m);
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllConditions()
|
||||
@@ -445,9 +449,5 @@ void AdvSceneSwitcher::CollapseAllConditions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &c : m->Conditions()) {
|
||||
c->SetCollapsed(true);
|
||||
}
|
||||
SetEditMacro(*m);
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, true);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ bool Macro::PerformAction()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ret && _count != std::numeric_limits<int>::max()) {
|
||||
if (_count != std::numeric_limits<int>::max()) {
|
||||
_count++;
|
||||
}
|
||||
return ret;
|
||||
@@ -492,7 +492,7 @@ bool SwitcherData::checkMacros()
|
||||
|
||||
bool SwitcherData::runMacros()
|
||||
{
|
||||
for (auto &m : macros) {
|
||||
for (auto m : macros) {
|
||||
if (m.Matched()) {
|
||||
vblog(LOG_INFO, "running macro: %s", m.Name().c_str());
|
||||
if (!m.PerformAction()) {
|
||||
|
||||
@@ -156,7 +156,7 @@ void SceneTrigger::logMatch()
|
||||
blog(LOG_INFO,
|
||||
"scene '%s' in status '%s' triggering action '%s' after %f seconds",
|
||||
GetWeakSourceName(scene).c_str(), statusName.c_str(),
|
||||
actionName.c_str(), duration);
|
||||
actionName.c_str(), duration.seconds);
|
||||
}
|
||||
|
||||
void frontEndActionThread(sceneTriggerAction action, double delay)
|
||||
@@ -265,13 +265,15 @@ void SceneTrigger::performAction()
|
||||
std::thread t;
|
||||
|
||||
if (isFrontendAction(triggerAction)) {
|
||||
t = std::thread(frontEndActionThread, triggerAction, duration);
|
||||
t = std::thread(frontEndActionThread, triggerAction,
|
||||
duration.seconds);
|
||||
} else if (isAudioAction(triggerAction)) {
|
||||
bool mute = triggerAction == sceneTriggerAction::MUTE_SOURCE;
|
||||
t = std::thread(muteThread, audioSource, duration, mute);
|
||||
t = std::thread(muteThread, audioSource, duration.seconds,
|
||||
mute);
|
||||
} else if (isSwitcherStatusAction(triggerAction)) {
|
||||
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
|
||||
t = std::thread(statusThread, duration, stop);
|
||||
t = std::thread(statusThread, duration.seconds, stop);
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignoring unknown action '%d'",
|
||||
static_cast<int>(triggerAction));
|
||||
@@ -374,7 +376,7 @@ void SceneTrigger::save(obs_data_t *obj)
|
||||
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
|
||||
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
|
||||
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
duration.Save(obj, "duration");
|
||||
obs_data_set_string(obj, "audioSource",
|
||||
GetWeakSourceName(audioSource).c_str());
|
||||
}
|
||||
@@ -388,7 +390,7 @@ void SceneTrigger::load(obs_data_t *obj)
|
||||
obs_data_get_int(obj, "triggerType"));
|
||||
triggerAction = static_cast<sceneTriggerAction>(
|
||||
obs_data_get_int(obj, "triggerAction"));
|
||||
duration = obs_data_get_double(obj, "duration");
|
||||
duration.Load(obj, "duration");
|
||||
|
||||
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
|
||||
audioSource = GetWeakSourceByName(audioSourceName);
|
||||
@@ -451,19 +453,17 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
|
||||
{
|
||||
triggers = new QComboBox();
|
||||
actions = new QComboBox();
|
||||
duration = new QDoubleSpinBox();
|
||||
duration = new DurationSelection();
|
||||
audioSources = new QComboBox();
|
||||
|
||||
duration->setMinimum(0.0);
|
||||
duration->setMaximum(99.000000);
|
||||
duration->setSuffix("s");
|
||||
|
||||
QWidget::connect(triggers, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TriggerTypeChanged(int)));
|
||||
QWidget::connect(actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TriggerActionChanged(int)));
|
||||
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
|
||||
QWidget::connect(duration, SIGNAL(DurationChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(duration, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
QWidget::connect(audioSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(AudioSourceChanged(const QString &)));
|
||||
@@ -475,7 +475,7 @@ SceneTriggerWidget::SceneTriggerWidget(QWidget *parent, SceneTrigger *s)
|
||||
if (s) {
|
||||
triggers->setCurrentIndex(static_cast<int>(s->triggerType));
|
||||
actions->setCurrentIndex(static_cast<int>(s->triggerAction));
|
||||
duration->setValue(s->duration);
|
||||
duration->SetDuration(s->duration);
|
||||
|
||||
audioSources->setCurrentText(
|
||||
GetWeakSourceName(s->audioSource).c_str());
|
||||
@@ -552,14 +552,24 @@ void SceneTriggerWidget::TriggerActionChanged(int index)
|
||||
}
|
||||
}
|
||||
|
||||
void SceneTriggerWidget::DurationChanged(double dur)
|
||||
void SceneTriggerWidget::DurationChanged(double seconds)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->duration = dur;
|
||||
switchData->duration.seconds = seconds;
|
||||
}
|
||||
|
||||
void SceneTriggerWidget::DurationUnitChanged(DurationUnit unit)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->duration.displayUnit = unit;
|
||||
}
|
||||
|
||||
void SceneTriggerWidget::AudioSourceChanged(const QString &text)
|
||||
|
||||
@@ -79,7 +79,7 @@ void AdvSSScreenshotObj::Copy()
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
|
||||
image = QImage(cx, cy, QImage::Format::Format_RGBX8888);
|
||||
image = QImage(cx, cy, QImage::Format::Format_RGBA8888);
|
||||
|
||||
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
|
||||
int linesize = image.bytesPerLine();
|
||||
|
||||
@@ -93,6 +93,11 @@ void Section::AddHeaderWidget(QWidget *w)
|
||||
_headerWidgetLayout->addWidget(w);
|
||||
}
|
||||
|
||||
void Section::SetCollapsed(bool collapsed)
|
||||
{
|
||||
Collapse(collapsed);
|
||||
}
|
||||
|
||||
bool Section::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) {
|
||||
|
||||
@@ -392,6 +392,11 @@ SceneSequenceSwitch *SceneSequenceSwitch::extend()
|
||||
}
|
||||
extendedSequence = std::make_unique<SceneSequenceSwitch>();
|
||||
extendedSequence->startScene = scene;
|
||||
if (targetType == SwitchTargetType::SceneGroup) {
|
||||
extendedSequence->startScene = nullptr;
|
||||
extendedSequence->startTargetType =
|
||||
SwitchTargetType::SceneGroup;
|
||||
}
|
||||
return extendedSequence.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ bool VideoSwitch::loadImageFromFile()
|
||||
return false;
|
||||
}
|
||||
matchImage =
|
||||
matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
|
||||
matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -213,10 +213,10 @@ void deleteLayoutItem(QLayoutItem *item)
|
||||
}
|
||||
}
|
||||
|
||||
void clearLayout(QLayout *layout)
|
||||
void clearLayout(QLayout *layout, int afterIdx)
|
||||
{
|
||||
QLayoutItem *item;
|
||||
while ((item = layout->takeAt(0))) {
|
||||
while (item = layout->takeAt(afterIdx)) {
|
||||
if (item->layout()) {
|
||||
clearLayout(item->layout());
|
||||
delete item->layout();
|
||||
|
||||
Reference in New Issue
Block a user