Compare commits

...

47 Commits
1.16 ... 1.16.4

Author SHA1 Message Date
WarmUpTill
5dfed8ac2a CI: Switch to OBS 27.1.3 2021-11-20 05:37:57 -08:00
WarmUpTill
d7102d395e Ensure contents of comboboxes are readable when changing scene/filter 2021-11-20 03:52:54 -08:00
WarmUpTill
82a41694a1 Allow interacting with OBS main window while plugin settings are opened 2021-11-20 03:52:54 -08:00
WarmUpTill
a1cae263c1 Add macro action "random" (#353)
This should be expanded upon by adding the ability to add weights to each macro.
2021-11-20 12:52:12 +01:00
WarmUpTill
cce6e240c2 Add option to fade in/out volume change 2021-11-17 12:30:10 -08:00
WarmUpTill
9d04b4d1a0 Move setLayoutVisible() to utility 2021-11-17 12:30:10 -08:00
WarmUpTill
fd7e48eff9 Add scene-visibility macro condition 2021-11-17 12:29:57 -08:00
WarmUpTill
0350a46004 Allow use of "previous scene" 2021-11-17 12:29:57 -08:00
WarmUpTill
00fec4b573 Fix macro hotkeys not working 2021-11-17 12:29:41 -08:00
WarmUpTill
c40a3af8ca Fix add, delete, reorder not working when changing macro segment type 2021-11-17 12:06:53 -08:00
WarmUpTill
e452d8cc8c Allow selecting source type for scene visibility action
This should allow to simplify some setups which required showing /
hiding all sources of a particular type.
2021-11-12 10:05:54 -08:00
WarmUpTill
fbc9818764 Fix macro count not updating correctly
The underlying issue are the potential deadlocks when triggering
frontent function calls while saving / loading is triggered in parallel
like it can happen when switching scene collections.

Hopefully the new frontend events allow for a cleaner solution.
2021-11-12 10:05:11 -08:00
WarmUpTill
5ef09f661f Show warning if macro is paused
And clean up timer handling for this condition type
2021-11-12 10:05:11 -08:00
WarmUpTill
2155a942f3 Do not check conditions of paused macros 2021-11-12 10:05:11 -08:00
WarmUpTill
5af206da60 Fix typo 2021-11-09 10:24:14 -08:00
WarmUpTill
924f68df64 Fix typo 2021-11-07 11:14:25 +01:00
WarmUpTill
2f3c4a6dae Cleanup 2021-11-06 22:30:26 +01:00
WarmUpTill
f955c07bda Fix warnings 2021-11-01 11:23:43 -07:00
WarmUpTill
6f1cd3cbcc Enable building without opencv 2021-11-01 11:23:43 -07:00
WarmUpTill
95537f39e4 Switch to using process names for process condition and executable tab (#337)
Previously the process condition and executable tab were just reusing
the window title functionality.
2021-10-31 14:05:54 +01:00
WarmUpTill
0490c97ad9 Add option to use pattern matching for "output changed" condition
This will allow you to ignore minor changes in the video output, for
example in noisy video streams.
2021-10-31 06:05:16 -07:00
WarmUpTill
4b17314c66 Add conditions "stream starting" and "stream stopping" 2021-10-31 05:40:28 -07:00
WarmUpTill
7459793fe3 Improve missing data dir error 2021-10-30 11:40:34 -07:00
WarmUpTill
8a39a859be Remove whitespace 2021-10-30 11:40:26 -07:00
WarmUpTill
f8a592f17a CI: Update readme 2021-10-30 11:40:26 -07:00
WarmUpTill
f824ac8f4e Add option to display plugin running state in system tray 2021-10-30 11:40:10 -07:00
WarmUpTill
8e9323173a CI: Run builds on tag creation also 2021-10-28 14:38:25 -07:00
WarmUpTill
9bc59c1082 Add option to not save remaining time 2021-10-25 11:19:53 -07:00
WarmUpTill
8b9f03a349 Add timer action
This action can be used to control the state of the timers on macro
conditions
2021-10-23 13:54:26 -07:00
WarmUpTill
093b0f9ec0 Add pause / continue and save / load remaining time for time condition 2021-10-23 13:54:26 -07:00
WarmUpTill
254eabfa8d Fix exact image match not wokring when using images with alpha channel
The screenshots taken from video sources were using the image format
Format_RGBX8888 instead of Format_RGBA8888.
The alpha channel of screenshots taken from OBS would be blended with a
black background while this step would be not performed for files loaded
to macht match the video sources.
2021-10-23 13:54:15 -07:00
WarmUpTill
b2da8faf37 Improve performance by not creating pattern data each interval 2021-10-23 13:54:15 -07:00
WarmUpTill
3eba20e2d8 Add option to use alpha channel as mask for pattern match 2021-10-23 13:54:15 -07:00
WarmUpTill
08841aa88e Use duration control class / widget for scene triggers 2021-10-23 13:53:56 -07:00
WarmUpTill
2e96c226b5 Improve performance of expand / collapse all 2021-10-23 13:53:56 -07:00
WarmUpTill
c34308f743 Fix scroll position being lost when adding or removing macro segments 2021-10-23 13:53:56 -07:00
WarmUpTill
4af07d9d55 Fix incorrectly set start scene type when extending scquence with groups
This would result in extended sequences being aborted early whenever one
would try to extend after a scene group entry
2021-10-23 13:53:56 -07:00
WarmUpTill
d743260ca8 Add wait for transition to complete option for currrent scene check
Previous behaviour was to always wait for the transition to complete, as
switcher->currentScene is only updated on
OBS_FRONTEND_EVENT_SCENE_CHANGED.
2021-10-23 13:53:56 -07:00
WarmUpTill
8ff50056f0 Fix crash when switching scene collection during macro wait
Also update comment stating why the main mutex needs to be unlocked
during the "action" phase of running macros
2021-10-23 13:53:56 -07:00
WarmUpTill
b786831f3b Remove unnecessary return value check 2021-10-23 13:53:56 -07:00
WarmUpTill
32e2a5f44c Add git tag to version info 2021-10-19 13:02:15 -07:00
WarmUpTill
e3f277c32a Reword date repeat option 2021-10-19 13:01:24 -07:00
WarmUpTill
5a7d76061c Clean up general tab
- Remove unnecessary spacer next to priority list
- Remove horizontal line in General behviour section
2021-10-19 13:01:01 -07:00
WarmUpTill
39a8c34fa6 Add description for the "min neighbors" parameter 2021-10-09 08:08:15 -07:00
WarmUpTill
ead51e2f13 Fix minNeighbors parameter not loading correctly 2021-10-09 08:08:15 -07:00
WarmUpTill
3b465e1fdc Fix crash while loading haar cascade 2021-10-03 12:30:45 -07:00
WarmUpTill
26fd26051b CI: Run on ubuntu-latest 2021-10-03 11:11:53 -07:00
57 changed files with 2122 additions and 351 deletions

View File

@@ -43,7 +43,7 @@ jobs:
# devscripts and libobs-dev are needed but they were already installed
# from check_libobs_revision and install_frontend_header sections.
sudo apt update
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
- name: build
run: |
debuild --no-lintian --no-sign

View File

@@ -1,15 +1,11 @@
name: build obs plugin
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
on: [push, pull_request, workflow_dispatch]
env:
PLUGIN_NAME: SceneSwitcher
LIB_NAME: advanced-scene-switcher
OBS_TAG: 27.1.1
OBS_TAG: 27.1.3
jobs:
macos64:
@@ -115,7 +111,7 @@ jobs:
path: ./nightly/*.pkg
ubuntu64:
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-18.04]
runs-on: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
@@ -181,7 +177,10 @@ jobs:
libqt5svg5-dev \
swig \
libxss-dev \
libopencv-dev
libx11-xcb-dev \
libxcb-xfixes0-dev \
libopencv-dev \
libprocps-dev
- name: 'Configure'
shell: bash
run: |

View File

@@ -1,4 +1,4 @@
Note that you will have to use OBS version 26.1 or newer!
Note that you will have to use OBS version 27 or newer!
--- Windows ---
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
@@ -8,17 +8,17 @@ It is usually located at 'C:\Program Files (x86)\obs-studio\'.
Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
--- macOS ---
Recommended: Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
Alternatively extract the *so file and data folder and either ...
... right-click the OBS app inside your Applications folder and choose 'Show Package Contents'.
Copy the advanced-scene-switcher.so file to 'Contents/Plugins' and the 'data' folder to 'Contents/Resources'.
... or copy the advanced-scene-switcher.so file to Library/Application Support/obs-studio/plugins/advanced-scene-switcher/bin/ .
And the 'data' folder to 'Library/Application Support/obs-studio/plugins/advanced-scene-switcher/'.
Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
--- Linux ---
Note that the plugin has dependencies to:
* libXss
* libopencv-imgproc
* libopencv-objdetect
Optional:
* libXtst
* libcurl
Copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder.
The location of this folder can vary, so you might have to look around a bit.

View File

@@ -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)
@@ -136,9 +137,19 @@ endif()
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED COMPONENTS Xtst Xss)
link_libraries(${X11_LIBRARIES})
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
if(NOT PROCPS_INCLUDE_DIR)
message(
FATAL_ERROR "procps include dir not found - please set PROCPS_INCLUDE_DIR"
)
endif()
find_library(PROCPS_LIBRARY NAMES procps)
if(NOT PROCPS_LIBRARY)
message(FATAL_ERROR "procps lib not found - please set PROCPS_LIBRARY")
endif()
link_libraries(${X11_LIBRARIES} ${procps_LIBRARIES})
include_directories("${X11_INCLUDE_DIR}" "${X11_Xtst_INCLUDE_PATH}"
"${X11_Xss_INCLUDE_PATH}")
"${X11_Xss_INCLUDE_PATH}" "${PROCPS_INCLUDE_DIR}")
endif()
if(WIN32)
@@ -153,7 +164,7 @@ elseif(APPLE)
else()
set(advanced-scene-switcher_PLATFORM_SOURCES
src/linux/advanced-scene-switcher-nix.cpp)
set(advanced-scene-switcher_PLATFORM_LIBS Xss)
set(advanced-scene-switcher_PLATFORM_LIBS Xss ${PROCPS_LIBRARY})
endif()
# asio and websocketpp
@@ -164,10 +175,6 @@ if(WIN32)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
endif()
# opencv
find_package(OpenCV REQUIRED)
include_directories("${OpenCV_INCLUDE_DIRS}")
# Setup QT tools
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
@@ -208,6 +215,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-media.hpp
src/headers/macro-action-plugin-state.hpp
src/headers/macro-action-preview-scene.hpp
src/headers/macro-action-random.hpp
src/headers/macro-action-recording.hpp
src/headers/macro-action-replay-buffer.hpp
src/headers/macro-action-run.hpp
@@ -218,6 +226,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
@@ -237,6 +246,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-condition-replay-buffer.hpp
src/headers/macro-condition-scene-order.hpp
src/headers/macro-condition-scene-transform.hpp
src/headers/macro-condition-scene-visibility.hpp
src/headers/macro-condition-scene.hpp
src/headers/macro-condition-source.hpp
src/headers/macro-condition-streaming.hpp
@@ -298,6 +308,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-media.cpp
src/macro-action-plugin-state.cpp
src/macro-action-preview-scene.cpp
src/macro-action-random.cpp
src/macro-action-recording.cpp
src/macro-action-replay-buffer.cpp
src/macro-action-run.cpp
@@ -308,6 +319,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
@@ -327,6 +339,7 @@ set(advanced-scene-switcher_SOURCES
src/macro-condition-replay-buffer.cpp
src/macro-condition-scene-order.cpp
src/macro-condition-scene-transform.cpp
src/macro-condition-scene-visibility.cpp
src/macro-condition-scene.cpp
src/macro-condition-source.cpp
src/macro-condition-streaming.cpp
@@ -351,6 +364,22 @@ set(advanced-scene-switcher_SOURCES
src/volume-control.cpp
src/version.cpp)
# opencv
find_package(OpenCV)
if(OpenCV_FOUND)
include_directories("${OpenCV_INCLUDE_DIRS}")
else()
set(OpenCV_LIBRARIES "")
message(
WARNING
"OpenCV not found! Functionality relying on OpenCV will be disabled!\nOpenCV sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/deps/opencv"
)
list(REMOVE_ITEM advanced-scene-switcher_SOURCES
src/macro-condition-video.cpp)
list(REMOVE_ITEM advanced-scene-switcher_HEADERS
src/headers/macro-condition-video.hpp)
endif()
add_library(
advanced-scene-switcher MODULE
${advanced-scene-switcher_HEADERS}
@@ -398,11 +427,7 @@ if(BUILD_OUT_OF_TREE)
else()
# In tree build
target_link_libraries(
advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
${OpenCV_LIBRARIES}
obs-frontend-api
Qt5::Widgets
libobs)
advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS}
${OpenCV_LIBRARIES} obs-frontend-api Qt5::Widgets libobs)
install_obs_plugin_with_data(advanced-scene-switcher data)
endif()

View File

@@ -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;

View File

@@ -45,7 +45,7 @@ AdvSceneSwitcher.generalTab.priority.fileContent="Datei Inhalt"
AdvSceneSwitcher.generalTab.priority.sceneSequence="Szenenfolge"
AdvSceneSwitcher.generalTab.priority.idleDetection="Inaktivitätserkennung"
AdvSceneSwitcher.generalTab.priority.executable="Prozess"
AdvSceneSwitcher.generalTab.priority.screenRegion="Bildschrimbereich"
AdvSceneSwitcher.generalTab.priority.screenRegion="Bildschirmbereich"
AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername"
AdvSceneSwitcher.generalTab.priority.media="Medien"
AdvSceneSwitcher.generalTab.priority.time="Zeit"
@@ -98,7 +98,7 @@ AdvSceneSwitcher.executableTab.entry="Wenn {{processes}} läuft wechsle zu {{sce
AdvSceneSwitcher.executableTab.help="Dieser Tab erlaubt es Szenen zu wechseln, wenn bestimmte Prozesse laufen.\nDas kann nützlich sein, wenn sich der Name eines Fensters ändern kann oder dieser nicht bekannt ist.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Bildschrimbereich"
AdvSceneSwitcher.screenRegionTab.title="Bildschirmbereich"
AdvSceneSwitcher.screenRegionTab.currentPosition="Mauszeiger ist bei:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Hilfsrahmen einblenden"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Hilfsrahmen ausblenden"

View File

@@ -28,6 +28,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switc
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -95,9 +96,10 @@ AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at {{xPos}} x
AdvSceneSwitcher.condition.scene="Scene"
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.changed="Scene changed"
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"
@@ -118,10 +120,14 @@ AdvSceneSwitcher.condition.video.condition.object="contains object"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
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!"
@@ -136,6 +142,8 @@ AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU lo
AdvSceneSwitcher.condition.stream="Streaming"
AdvSceneSwitcher.condition.stream.state.start="Stream running"
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
AdvSceneSwitcher.condition.stream.state.starting="Stream starting"
AdvSceneSwitcher.condition.stream.state.stopping="Stream stopping"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}"
AdvSceneSwitcher.condition.record="Recording"
AdvSceneSwitcher.condition.record.state.start="Recording running"
@@ -150,9 +158,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"
@@ -162,6 +172,7 @@ AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than"
AdvSceneSwitcher.condition.macro.count.type.equal="Exactly"
AdvSceneSwitcher.condition.macro.count.reset="Reset"
AdvSceneSwitcher.condition.macro.pausedWarning="Selected macro is currently paused!"
AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} was executed {{conditions}} {{count}} times"
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}"
@@ -208,7 +219,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"
@@ -222,6 +233,10 @@ AdvSceneSwitcher.condition.transition.type.started="Transition active"
AdvSceneSwitcher.condition.transition.type.ended="Transition ended"
AdvSceneSwitcher.condition.transition.durationSuffix="seconds"
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}"
AdvSceneSwitcher.condition.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.condition.sceneVisibility.type.shown="Shown"
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="Hidden"
AdvSceneSwitcher.condition.sceneVisibility.entry="On {{scenes}} {{sources}} is {{conditions}} "
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
@@ -236,6 +251,7 @@ AdvSceneSwitcher.action.audio.type.mute="Mute"
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
AdvSceneSwitcher.action.audio.fade="{{fade}}Fade over {{duration}} seconds"
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
@@ -261,7 +277,9 @@ AdvSceneSwitcher.action.run.entry="Run {{filePath}}"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
AdvSceneSwitcher.action.sceneVisibility.entry="On {{scenes}} {{actions}} {{sources}}"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
AdvSceneSwitcher.action.sceneVisibility.entry="On {{scenes}} {{actions}} {{sourceTypes}} {{sources}}"
AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Enable"
AdvSceneSwitcher.action.filter.type.disable="Disable"
@@ -329,6 +347,16 @@ 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}}"
AdvSceneSwitcher.action.random="Random"
AdvSceneSwitcher.action.random.arguments="Macros:"
AdvSceneSwitcher.action.random.addArgument="Add Macro"
AdvSceneSwitcher.action.random.entry="Run {{macroSelection}}"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
@@ -603,6 +631,8 @@ AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task
AdvSceneSwitcher.status.active="Active"
AdvSceneSwitcher.status.inactive="Inactive"
AdvSceneSwitcher.running="Plugin running"
AdvSceneSwitcher.stopped="Plugin stopped"
AdvSceneSwitcher.unit.milliseconds="milliseconds"
AdvSceneSwitcher.unit.secends="seconds"

View File

@@ -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>
@@ -313,6 +306,30 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="showTrayNotifications">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_48">
<item>
@@ -364,19 +381,6 @@
<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>
</item>
</layout>
</item>
</layout>

View File

@@ -2,6 +2,7 @@
#include <QAction>
#include <QFileDialog>
#include <regex>
#include <filesystem>
#include <obs-module.h>
#include <obs-frontend-api.h>
@@ -19,6 +20,7 @@ SwitcherData *switcher = nullptr;
AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
: QDialog(parent), ui(new Ui_AdvSceneSwitcher)
{
switcher->settingsWindowOpened = true;
ui->setupUi(this);
std::lock_guard<std::mutex> lock(switcher->m);
@@ -27,6 +29,13 @@ AdvSceneSwitcher::AdvSceneSwitcher(QWidget *parent)
loadUI();
}
AdvSceneSwitcher::~AdvSceneSwitcher()
{
if (switcher) {
switcher->settingsWindowOpened = false;
}
}
bool translationAvailable()
{
return !!strcmp(obs_module_text("AdvSceneSwitcher.pluginName"),
@@ -36,9 +45,16 @@ bool translationAvailable()
void AdvSceneSwitcher::loadUI()
{
if (!translationAvailable()) {
(void)DisplayMessage(
"Failed to find plug-in's 'data' directory.\n"
"Please check installation instructions!");
QString msg = "Failed to find plug-in's 'data' directory.\n"
"Please check installation instructions!\n\n"
"Data most likely expected at:\n\n";
#ifdef _WIN32
msg += QString::fromStdString(
(std::filesystem::current_path().string()));
msg += "/";
#endif
msg += obs_get_module_data_path(obs_current_module());
(void)DisplayMessage(msg);
}
#if __APPLE__
@@ -104,33 +120,23 @@ static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{
if (saving) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune();
obs_data_t *obj = obs_data_create();
switcher->saveSettings(obj);
obs_data_set_obj(save_data, "advanced-scene-switcher", obj);
obs_data_release(obj);
} else {
switcher->m.lock();
obs_data_t *obj =
obs_data_get_obj(save_data, "advanced-scene-switcher");
if (!obj) {
obj = obs_data_create();
}
if (switcher->versionChanged(obj, g_GIT_SHA1)) {
AskBackup(obj);
}
switcher->loadSettings(obj);
obs_data_release(obj);
switcher->m.unlock();
// Stop the scene switcher at least once to
@@ -166,9 +172,7 @@ void SwitcherData::Thread()
bool setPrevSceneAfterLinger = false;
bool macroMatch = false;
macroSceneSwitched = false;
endTime = std::chrono::high_resolution_clock::now();
auto runTime =
std::chrono::duration_cast<std::chrono::milliseconds>(
endTime - startTime);
@@ -232,12 +236,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) {
@@ -410,12 +419,18 @@ void SwitcherData::Start()
if (networkConfig.ClientEnabled) {
client.connect(networkConfig.GetClientUri());
}
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
obs_module_text("AdvSceneSwitcher.running"));
}
}
void ResetMacroCounters()
{
for (auto &m : switcher->macros) {
m.ResetCount();
m->ResetCount();
}
}
@@ -436,6 +451,19 @@ void SwitcherData::Stop()
server.stop();
client.disconnect();
for (auto &t : audioHelperThreads) {
if (t.joinable()) {
t.join();
}
}
audioHelperThreads.clear();
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
obs_module_text("AdvSceneSwitcher.stopped"));
}
}
void SwitcherData::setWaitScene()
@@ -553,6 +581,18 @@ void setTranstionEnd()
std::chrono::high_resolution_clock::now();
}
void setStreamStarting()
{
switcher->lastStreamStartingTime =
std::chrono::high_resolution_clock::now();
}
void setStreamStopping()
{
switcher->lastStreamStoppingTime =
std::chrono::high_resolution_clock::now();
}
// Note to future self:
// be careful using switcher->m here as there is potential for deadlocks when using
// frontend functions such as obs_frontend_set_current_scene()
@@ -592,13 +632,22 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd();
break;
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
setStreamStarting();
break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
setStreamStopping();
break;
default:
break;
}
}
AdvSceneSwitcher *ssWindow;
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(
@@ -613,11 +662,16 @@ extern "C" void InitSceneSwitcher()
PlatformInit();
auto cb = []() {
QMainWindow *window =
(QMainWindow *)obs_frontend_get_main_window();
AdvSceneSwitcher ss(window);
ss.exec();
if (switcher->settingsWindowOpened) {
ssWindow->show();
ssWindow->raise();
ssWindow->activateWindow();
} else {
ssWindow = new AdvSceneSwitcher(
(QMainWindow *)obs_frontend_get_main_window());
ssWindow->setAttribute(Qt::WA_DeleteOnClose);
ssWindow->show();
}
};
obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr);

View File

@@ -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 = {};

View File

@@ -5,7 +5,7 @@
#include <QFileDialog>
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
: _type(type), QWidget(parent)
: QWidget(parent), _type(type)
{
_filePath = new QLineEdit();
_browseButton =

View File

@@ -186,6 +186,9 @@ void AdvSceneSwitcher::on_toggleStartButton_clicked()
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{
if (!switcher) {
return;
}
switcher->windowPos = this->pos();
switcher->windowSize = this->size();
@@ -210,6 +213,15 @@ void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
switcher->saveWindowGeo = state;
}
void AdvSceneSwitcher::on_showTrayNotifications_stateChanged(int state)
{
if (loading) {
return;
}
switcher->showSystemTrayNotifications = state;
}
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{
if (loading) {
@@ -503,6 +515,8 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
static_cast<int>(autoStartEvent));
obs_data_set_bool(obj, "verbose", verbose);
obs_data_set_bool(obj, "showSystemTrayNotifications",
showSystemTrayNotifications);
obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
@@ -574,6 +588,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_get_int(obj, "autoStartEvent"));
verbose = obs_data_get_bool(obj, "verbose");
showSystemTrayNotifications =
obs_data_get_bool(obj, "showSystemTrayNotifications");
disableHints = obs_data_get_bool(obj, "disableHints");
obs_data_set_default_int(obj, "priority0", default_priority_0);
@@ -800,6 +816,8 @@ void AdvSceneSwitcher::setupGeneralTab()
ui->verboseLogging->setChecked(switcher->verbose);
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
ui->showTrayNotifications->setChecked(
switcher->showSystemTrayNotifications);
ui->uiHintsDisable->setChecked(switcher->disableHints);
for (int p : switcher->functionNamesByPriority) {
@@ -887,4 +905,6 @@ void AdvSceneSwitcher::setupGeneralTab()
QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
statusTimer->start(1000);
setWindowTitle(windowTitle() + " - " + g_GIT_TAG);
}

View File

@@ -29,6 +29,7 @@ public:
bool currentStatusActive = false;
AdvSceneSwitcher(QWidget *parent);
~AdvSceneSwitcher();
void reject() override;
void closeEvent(QCloseEvent *event) override;
@@ -52,6 +53,8 @@ public:
void SetEditMacro(Macro &m);
void HighlightAction(int idx);
void HighlightCondition(int idx);
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void ConnectControlSignals(MacroActionEdit *);
void ConnectControlSignals(MacroConditionEdit *);
void SwapActions(Macro *m, int pos1, int pos2);
@@ -162,6 +165,7 @@ public slots:
void on_verboseLogging_stateChanged(int state);
void on_saveWindowGeo_stateChanged(int state);
void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked();

View File

@@ -22,6 +22,7 @@ public:
bool DurationReached();
double TimeRemaining();
void SetTimeRemaining(double);
void Reset();
std::string ToString();

View File

@@ -1,7 +1,10 @@
#pragma once
#include <QSpinBox>
#include <QHBoxLayout>
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include <QSpinBox>
#include <QCheckBox>
#include <QHBoxLayout>
enum class AudioAction {
MUTE,
@@ -26,6 +29,8 @@ public:
OBSWeakSource _audioSource;
AudioAction _action = AudioAction::MUTE;
int _volume = 0;
bool _fade = false;
Duration _duration;
private:
static bool _registered;
@@ -53,6 +58,8 @@ private slots:
void SourceChanged(const QString &text);
void ActionChanged(int value);
void VolumeChanged(int value);
void FadeChanged(int value);
void DurationChanged(double seconds);
signals:
void HeaderInfoChanged(const QString &);
@@ -60,9 +67,11 @@ protected:
QComboBox *_audioSources;
QComboBox *_actions;
QSpinBox *_volumePercent;
QCheckBox *_fade;
DurationSelection *_duration;
QHBoxLayout *_fadeLayout;
std::shared_ptr<MacroActionAudio> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-selection.hpp"
#include <QPushButton>
#include <QListWidget>
#include <unordered_map>
class MacroActionRandom : public MultiMacroRefAction {
public:
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetId() { return id; };
static std::shared_ptr<MacroAction> Create()
{
return std::make_shared<MacroActionRandom>();
}
// TODO: add weights to each macro ...
// std::unordered_map<MacroRef, int> _weights;
private:
MacroRef lastRandomMacro;
static bool _registered;
static const std::string id;
};
class MacroActionRandomEdit : public QWidget {
Q_OBJECT
public:
MacroActionRandomEdit(
QWidget *parent,
std::shared_ptr<MacroActionRandom> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionRandomEdit(
parent,
std::dynamic_pointer_cast<MacroActionRandom>(action));
}
private slots:
void MacroRemove(const QString &name);
void MacroRename(const QString &oldName, const QString &newName);
void AddMacro();
void RemoveMacro();
int FindEntry(const std::string &macro);
void MacroSelectionChanged(int idx);
protected:
std::shared_ptr<MacroActionRandom> _entryData;
private:
MacroSelection *_macroSelection;
QListWidget *_macroList;
QPushButton *_add;
QPushButton *_remove;
bool _loading = true;
};

View File

@@ -8,6 +8,11 @@ enum class SceneVisibilityAction {
HIDE,
};
enum class SceneItemSourceType {
SOURCE,
SOURCE_GROUP,
};
class MacroActionSceneVisibility : public MacroAction {
public:
bool PerformAction();
@@ -22,7 +27,9 @@ public:
}
SceneSelection _scene;
SceneItemSourceType _sourceType = SceneItemSourceType::SOURCE;
OBSWeakSource _source;
std::string _sourceGroup = "";
SceneVisibilityAction _action = SceneVisibilityAction::SHOW;
private:
@@ -49,6 +56,7 @@ public:
private slots:
void SceneChanged(const SceneSelection &);
void SourceTypeChanged(int value);
void SourceChanged(const QString &text);
void ActionChanged(int value);
signals:
@@ -56,6 +64,7 @@ signals:
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sourceTypes;
QComboBox *_sources;
QComboBox *_actions;
std::shared_ptr<MacroActionSceneVisibility> _entryData;

View 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;
};

View File

@@ -67,6 +67,7 @@ private slots:
void ConditionChanged(int cond);
void ResetClicked();
void UpdateCount();
void UpdatePaused();
signals:
void HeaderInfoChanged(const QString &);
@@ -76,16 +77,17 @@ protected:
QComboBox *_counterConditions;
QSpinBox *_count;
QLabel *_currentCount;
QLabel *_pausedWarning;
QPushButton *_resetCount;
QHBoxLayout *_settingsLine1;
QHBoxLayout *_settingsLine2;
std::unique_ptr<QTimer> _timer;
QTimer _countTimer;
QTimer _pausedTimer;
std::shared_ptr<MacroConditionMacro> _entryData;
private:
void ClearLayouts();
void SetupStateWidgets();
void SetupCountWidgets();
void ResetTimer();
bool _loading = true;
};

View File

@@ -0,0 +1,66 @@
#pragma once
#include "macro.hpp"
#include "scene-selection.hpp"
#include <QComboBox>
enum class SceneVisibilityCondition {
SHOWN,
HIDDEN,
};
class MacroConditionSceneVisibility : public MacroCondition {
public:
bool CheckCondition();
bool Save(obs_data_t *obj);
bool Load(obs_data_t *obj);
std::string GetShortDesc();
std::string GetId() { return id; };
static std::shared_ptr<MacroCondition> Create()
{
return std::make_shared<MacroConditionSceneVisibility>();
}
SceneSelection _scene;
OBSWeakSource _source;
SceneVisibilityCondition _condition = SceneVisibilityCondition::SHOWN;
private:
static bool _registered;
static const std::string id;
};
class MacroConditionSceneVisibilityEdit : public QWidget {
Q_OBJECT
public:
MacroConditionSceneVisibilityEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneVisibility> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionSceneVisibilityEdit(
parent,
std::dynamic_pointer_cast<MacroConditionSceneVisibility>(
cond));
}
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const QString &text);
void ConditionChanged(int cond);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sources;
QComboBox *_conditions;
std::shared_ptr<MacroConditionSceneVisibility> _entryData;
private:
bool _loading = true;
};

View File

@@ -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:

View File

@@ -6,6 +6,8 @@
enum class StreamState {
STOP,
START,
STARTING,
STOPPING,
};
class MacroConditionStream : public MacroCondition {
@@ -22,6 +24,9 @@ public:
StreamState _streamState;
private:
std::chrono::high_resolution_clock::time_point _lastStreamStartingTime{};
std::chrono::high_resolution_clock::time_point _lastStreamStoppingTime{};
static bool _registered;
static const std::string id;
};

View File

@@ -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;

View File

@@ -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,9 @@ public:
OBSWeakSource _videoSource;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
bool _useAlphaAsMask = false;
bool _usePatternForChangedCheck = false;
PatternMatchData _patternData;
double _patternThreshold = 0.8;
cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1;
@@ -57,6 +66,7 @@ public:
int _throttleCount = 3;
private:
bool OutputChanged();
bool ScreenshotContainsPattern();
bool ScreenshotContainsObject();
bool Compare();
@@ -119,7 +129,9 @@ private slots:
void ConditionChanged(int cond);
void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked();
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(double);
@@ -139,14 +151,17 @@ protected:
QComboBox *_videoSelection;
QComboBox *_condition;
QCheckBox *_usePatternForChangedCheck;
FileSelection *_imagePath;
ThresholdSlider *_patternThreshold;
QCheckBox *_useAlphaAsMask;
FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout;
ThresholdSlider *_objectScaleThreshold;
QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
QHBoxLayout *_minSizeControlLayout;
QSpinBox *_minSizeX;
QSpinBox *_minSizeY;

View File

@@ -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 &);

View File

@@ -77,7 +77,7 @@ public:
bool Matched() { return _matched; }
std::string Name() { return _name; }
void SetName(const std::string &name);
void SetPaused(bool pause = true) { _paused = pause; }
void SetPaused(bool pause = true);
bool Paused() { return _paused; }
int GetCount() { return _count; };
void ResetCount() { _count = 0; };
@@ -102,6 +102,7 @@ private:
void SetupHotkeys();
void ClearHotkeys();
void SetHotkeysDesc();
void ResetTimers();
std::string _name = "";
std::deque<std::shared_ptr<MacroCondition>> _conditions;
@@ -144,3 +145,9 @@ public:
void ResolveMacroRef();
MacroRef _macro;
};
class MultiMacroRefAction : public MacroAction {
public:
void ResolveMacroRef();
std::vector<MacroRef> _macros;
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -6,6 +6,7 @@
#include <QDateTime>
#include <QThread>
#include <curl/curl.h>
#include <unordered_map>
#include "scene-group.hpp"
#include "scene-trigger.hpp"
@@ -63,6 +64,8 @@ class SwitcherThread;
struct SwitcherData {
SwitcherThread *th = nullptr;
bool settingsWindowOpened = false;
std::condition_variable cv;
std::mutex m;
bool transitionActive = false;
@@ -70,6 +73,7 @@ struct SwitcherData {
bool stop = false;
bool verbose = false;
bool disableHints = false;
bool showSystemTrayNotifications = false;
bool showFrame = false;
bool transitionOverrideOverride = false;
bool adjustActiveTransitionType = true;
@@ -81,6 +85,8 @@ struct SwitcherData {
OBSWeakSource previousScene = nullptr;
std::chrono::high_resolution_clock::time_point lastSceneChangeTime{};
std::chrono::high_resolution_clock::time_point lastTransitionEndTime{};
std::chrono::high_resolution_clock::time_point lastStreamStartingTime{};
std::chrono::high_resolution_clock::time_point lastStreamStoppingTime{};
OBSWeakSource lastRandomScene;
SceneGroup *lastRandomSceneGroup;
OBSWeakSource nonMatchingScene;
@@ -89,10 +95,14 @@ struct SwitcherData {
StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
std::vector<std::thread> audioHelperThreads;
std::atomic_bool masterAudioFadeActive = {false};
std::unordered_map<std::string, std::atomic_bool> activeAudioFades;
Duration cooldown;
std::chrono::high_resolution_clock::time_point lastMatchTime;
std::deque<Macro> macros;
std::deque<std::shared_ptr<Macro>> macros;
std::condition_variable macroWaitCv;
std::atomic_bool abortMacroWait = {false};
bool macroSceneSwitched = false;

View File

@@ -44,7 +44,8 @@ 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);
void setLayoutVisible(QLayout *layout, bool visible);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor = QColor(0, 0, 0, 0),
QString specifier = "QLabel ",
@@ -55,6 +56,7 @@ void listAddClicked(QListWidget *list, QWidget *newWidget,
bool listMoveUp(QListWidget *list);
bool listMoveDown(QListWidget *list);
bool DisplayMessage(const QString &msg, bool question = false);
void DisplayTrayMessage(const QString &title, const QString &msg);
void addSelectionEntry(QComboBox *sel, const char *description,
bool selectable = false, const char *tooltip = "");
void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
@@ -77,3 +79,4 @@ void populateFilterSelection(QComboBox *list,
void populateSceneItemSelection(QComboBox *list,
OBSWeakSource sceneWeakSource = nullptr);
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
void populateSourceTypeSelection(QComboBox *list);

View File

@@ -1 +1,2 @@
extern const char g_GIT_SHA1[];
extern const char g_GIT_TAG[];

View File

@@ -24,6 +24,9 @@
#include <QStringList>
#include <QRegularExpression>
#include <QLibrary>
#include <proc/readproc.h>
#include <fstream>
#include <sstream>
static Display *xdisplay = 0;
@@ -363,17 +366,78 @@ bool isFullscreen(const std::string &title)
void GetProcessList(QStringList &processes)
{
processes.clear();
for (size_t i = 0; i < getTopLevelWindows().size(); ++i) {
std::string s = GetWindowTitle(i);
if (s != "")
processes << QString::fromStdString(s);
PROCTAB *proc = openproc(PROC_FILLSTAT);
proc_t proc_info;
memset(&proc_info, 0, sizeof(proc_info));
while (readproc(proc, &proc_info) != NULL) {
QString procName(proc_info.cmd);
if (!procName.isEmpty() && !processes.contains(proc_info.cmd)) {
processes << QString(proc_info.cmd);
}
}
closeproc(proc);
}
int getForegroundProcessPid()
{
if (!ewmhIsSupported()) {
return -1;
}
auto dpy = disp();
Atom active = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", true);
Atom actualType;
int format;
unsigned long num, bytes;
Window *window = 0;
int pid = -1;
Window rootWin = RootWindow(dpy, 0);
int xstatus = XGetWindowProperty(dpy, rootWin, active, 0L, ~0L, false,
AnyPropertyType, &actualType, &format,
&num, &bytes, (uint8_t **)&window);
if (xstatus == 0 && window == nullptr) {
return -1;
}
Atom atom, actual_type;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char *prop;
atom = XInternAtom(dpy, "_NET_WM_PID", True);
auto status = XGetWindowProperty(dpy, *window, atom, 0, 1024, False,
AnyPropertyType, &actual_type,
&actual_format, &nitems, &bytes_after,
&prop);
if (status != 0) {
return -2;
}
if (!prop) {
return -3;
}
pid = prop[1] * 256;
pid += prop[0];
return pid;
}
std::string getProcNameFromPid(int pid)
{
std::string path = "/proc/" + std::to_string(pid) + "/comm";
std::ifstream t(path);
std::stringstream buffer;
buffer << t.rdbuf();
return buffer.str();
}
bool isInFocus(const QString &executable)
{
std::string current;
GetCurrentWindowTitle(current);
auto pid = getForegroundProcessPid();
std::string current = getProcNameFromPid(pid);
// True if executable switch equals current window
bool equals = (executable.toStdString() == current);

View File

@@ -18,6 +18,94 @@ const static std::map<AudioAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.audio.type.masterVolume"},
};
constexpr auto fadeInterval = std::chrono::milliseconds(100);
constexpr float minFade = 0.000001f;
void fadeSourceVolume(Duration duration, float vol, OBSWeakSource audioSource)
{
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
}
float curVol = obs_source_get_volume(s);
obs_source_release(s);
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
if (volStep < minFade) {
switcher->activeAudioFades[GetWeakSourceName(audioSource)] =
false;
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
}
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_source_set_volume(s, curVol);
std::this_thread::sleep_for(fadeInterval);
obs_source_release(s);
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = false;
}
void fadeMasterVolume(Duration duration, float vol)
{
float curVol = obs_get_master_volume();
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
if (volStep < minFade) {
switcher->masterAudioFadeActive = false;
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_set_master_volume(curVol);
std::this_thread::sleep_for(fadeInterval);
}
switcher->masterAudioFadeActive = false;
}
void startSourceFade(Duration &duration, float vol, OBSWeakSource audioSource)
{
if (!audioSource) {
return;
}
auto it =
switcher->activeAudioFades.find(GetWeakSourceName(audioSource));
if (it != switcher->activeAudioFades.end() && it->second == true) {
blog(LOG_WARNING,
"Audio fade for volume of %s already active! New fade request will be ignored!",
GetWeakSourceName(audioSource).c_str());
return;
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = true;
switcher->audioHelperThreads.emplace_back(fadeSourceVolume, duration,
vol, audioSource);
}
void startMasterFade(Duration &duration, float vol)
{
if (switcher->masterAudioFadeActive) {
blog(LOG_WARNING,
"Audio fade for master volume already active! New fade request will be ignored!");
return;
}
switcher->masterAudioFadeActive = true;
switcher->audioHelperThreads.emplace_back(fadeMasterVolume, duration,
vol);
}
bool MacroActionAudio::PerformAction()
{
auto s = obs_weak_source_get_source(_audioSource);
@@ -29,10 +117,19 @@ bool MacroActionAudio::PerformAction()
obs_source_set_muted(s, false);
break;
case AudioAction::SOURCE_VOLUME:
obs_source_set_volume(s, (float)_volume / 100.0f);
if (_fade) {
startSourceFade(_duration, (float)_volume / 100.0f,
_audioSource);
} else {
obs_source_set_volume(s, (float)_volume / 100.0f);
}
break;
case AudioAction::MASTER_VOLUME:
obs_set_master_volume((float)_volume / 100.0f);
if (_fade) {
startMasterFade(_duration, (float)_volume / 100.0f);
} else {
obs_set_master_volume((float)_volume / 100.0f);
}
break;
default:
break;
@@ -46,9 +143,10 @@ void MacroActionAudio::LogAction()
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO,
"performed action \"%s\" for source \"%s\" with volume %d",
"performed action \"%s\" for source \"%s\" with volume %d with fade %d %f",
it->second.c_str(),
GetWeakSourceName(_audioSource).c_str(), _volume);
GetWeakSourceName(_audioSource).c_str(), _volume, _fade,
_duration.seconds);
} else {
blog(LOG_WARNING, "ignored unknown audio action %d",
static_cast<int>(_action));
@@ -58,20 +156,24 @@ void MacroActionAudio::LogAction()
bool MacroActionAudio::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_duration.Save(obj);
obs_data_set_string(obj, "audioSource",
GetWeakSourceName(_audioSource).c_str());
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "volume", _volume);
obs_data_set_bool(obj, "fade", _fade);
return true;
}
bool MacroActionAudio::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_duration.Load(obj);
const char *audioSourceName = obs_data_get_string(obj, "audioSource");
_audioSource = GetWeakSourceByName(audioSourceName);
_action = static_cast<AudioAction>(obs_data_get_int(obj, "action"));
_volume = obs_data_get_int(obj, "volume");
_fade = obs_data_get_bool(obj, "fade");
return true;
}
@@ -100,6 +202,8 @@ MacroActionAudioEdit::MacroActionAudioEdit(
_volumePercent->setMinimum(0);
_volumePercent->setMaximum(2000);
_volumePercent->setSuffix("%");
_fade = new QCheckBox();
_duration = new DurationSelection(parent, false);
populateActionSelection(_actions);
populateAudioSelection(_audioSources);
@@ -111,15 +215,25 @@ MacroActionAudioEdit::MacroActionAudioEdit(
SLOT(SourceChanged(const QString &)));
QWidget::connect(_volumePercent, SIGNAL(valueChanged(int)), this,
SLOT(VolumeChanged(int)));
QWidget::connect(_fade, SIGNAL(stateChanged(int)), this,
SLOT(FadeChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _audioSources},
{"{{actions}}", _actions},
{"{{volume}}", _volumePercent},
{"{{audioSources}}", _audioSources}, {"{{actions}}", _actions},
{"{{volume}}", _volumePercent}, {"{{fade}}", _fade},
{"{{duration}}", _duration},
};
QHBoxLayout *entryLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
mainLayout, widgetPlaceholders);
entryLayout, widgetPlaceholders);
_fadeLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.fade"),
_fadeLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_fadeLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -140,19 +254,11 @@ bool hasSourceControl(AudioAction action)
void MacroActionAudioEdit::SetWidgetVisibility()
{
if (hasVolumeControl(_entryData->_action)) {
_volumePercent->show();
} else {
_volumePercent->hide();
}
if (hasSourceControl(_entryData->_action)) {
_audioSources->show();
} else {
_audioSources->hide();
}
_volumePercent->setVisible(hasVolumeControl(_entryData->_action));
_audioSources->setVisible(hasSourceControl(_entryData->_action));
setLayoutVisible(_fadeLayout, hasVolumeControl(_entryData->_action));
adjustSize();
}
void MacroActionAudioEdit::UpdateEntryData()
{
if (!_entryData) {
@@ -163,7 +269,8 @@ void MacroActionAudioEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_audioSource).c_str());
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_volumePercent->setValue(_entryData->_volume);
_fade->setChecked(_entryData->_fade);
_duration->SetDuration(_entryData->_duration);
SetWidgetVisibility();
}
@@ -199,3 +306,23 @@ void MacroActionAudioEdit::VolumeChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_volume = value;
}
void MacroActionAudioEdit::FadeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fade = value;
}
void MacroActionAudioEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}

View File

@@ -92,12 +92,14 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
return;
}
auto idx = _entryData->get()->GetIndex();
std::string id = MacroActionFactory::GetIdByName(text);
HeaderInfoChanged("");
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset();
*_entryData = MacroActionFactory::Create(id);
(*_entryData)->SetIndex(idx);
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
@@ -152,12 +154,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 +187,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()

View File

@@ -94,6 +94,7 @@ MacroActionFilterEdit::MacroActionFilterEdit(
{
_sources = new QComboBox();
_filters = new QComboBox();
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_actions = new QComboBox();
_getSettings = new QPushButton(
obs_module_text("AdvSceneSwitcher.action.filter.getSettings"));
@@ -165,6 +166,7 @@ void MacroActionFilterEdit::SourceChanged(const QString &text)
}
_filters->clear();
populateFilterSelection(_filters, _entryData->_source);
_filters->adjustSize();
}
void MacroActionFilterEdit::FilterChanged(const QString &text)

View File

@@ -141,7 +141,7 @@ obs_key_combination keysToOBSKeycombo(const std::vector<HotkeyType> &keys)
return combo;
}
for (int i = 0; i < keys.size() - 1; i++) {
for (uint32_t i = 0; i < keys.size() - 1; i++) {
switch (keys[i]) {
case HotkeyType::Key_Shift_L:
combo.modifiers |= INTERACT_SHIFT_KEY;

263
src/macro-action-random.cpp Normal file
View File

@@ -0,0 +1,263 @@
#include "headers/macro-action-random.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/name-dialog.hpp"
#include "headers/utility.hpp"
#include <cstdlib>
const std::string MacroActionRandom::id = "random";
bool MacroActionRandom::_registered = MacroActionFactory::Register(
MacroActionRandom::id,
{MacroActionRandom::Create, MacroActionRandomEdit::Create,
"AdvSceneSwitcher.action.random"});
std::vector<MacroRef> getPossibleMacros(std::vector<MacroRef> &macros,
MacroRef &lastRandomMacro)
{
std::vector<MacroRef> res;
if (macros.size() == 1) {
if (macros[0]->Paused()) {
return res;
} else {
return macros;
}
}
for (auto &m : macros) {
if (m.get() && !m->Paused() &&
!(lastRandomMacro.get() == m.get())) {
res.push_back(m);
}
}
return res;
}
bool MacroActionRandom::PerformAction()
{
if (_macros.size() == 0) {
return true;
}
auto macros = getPossibleMacros(_macros, lastRandomMacro);
if (macros.size() == 0) {
return true;
}
if (macros.size() == 1) {
lastRandomMacro = macros[0];
return macros[0]->PerformAction();
}
size_t idx = std::rand() % (macros.size());
lastRandomMacro = macros[idx];
return macros[idx]->PerformAction();
}
void MacroActionRandom::LogAction()
{
vblog(LOG_INFO, "running random macro");
}
bool MacroActionRandom::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_array_t *args = obs_data_array_create();
for (auto &m : _macros) {
obs_data_t *array_obj = obs_data_create();
m.Save(array_obj);
obs_data_array_push_back(args, array_obj);
obs_data_release(array_obj);
}
obs_data_set_array(obj, "macros", args);
obs_data_array_release(args);
return true;
}
bool MacroActionRandom::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
obs_data_array_t *args = obs_data_get_array(obj, "macros");
size_t count = obs_data_array_count(args);
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(args, i);
MacroRef ref;
ref.Load(array_obj);
_macros.push_back(ref);
obs_data_release(array_obj);
}
obs_data_array_release(args);
return true;
}
MacroActionRandomEdit::MacroActionRandomEdit(
QWidget *parent, std::shared_ptr<MacroActionRandom> entryData)
: QWidget(parent)
{
_macroSelection = new MacroSelection(window());
_macroList = new QListWidget();
_macroList->setSortingEnabled(true);
_add = new QPushButton();
_add->setMaximumSize(QSize(22, 22));
_add->setProperty("themeID",
QVariant(QString::fromUtf8("addIconSmall")));
_add->setFlat(true);
_remove = new QPushButton();
_remove->setMaximumSize(QSize(22, 22));
_remove->setProperty("themeID",
QVariant(QString::fromUtf8("removeIconSmall")));
_remove->setFlat(true);
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(AddMacro()));
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(RemoveMacro()));
QWidget::connect(_macroList, SIGNAL(currentRowChanged(int)), this,
SLOT(MacroSelectionChanged(int)));
QWidget::connect(window(),
SIGNAL(MacroRenamed(const QString &, const QString &)),
this,
SLOT(MacroRename(const QString &, const QString &)));
auto *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{macroSelection}}", _macroSelection},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.random.entry"),
entryLayout, widgetPlaceholders);
auto *argButtonLayout = new QHBoxLayout;
argButtonLayout->addWidget(_add);
argButtonLayout->addWidget(_remove);
argButtonLayout->addStretch();
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(new QLabel(
obs_module_text("AdvSceneSwitcher.action.random.arguments")));
mainLayout->addWidget(_macroList);
mainLayout->addLayout(argButtonLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionRandomEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
for (auto &m : _entryData->_macros) {
if (!m.get()) {
continue;
}
auto name = QString::fromStdString(m->Name());
QListWidgetItem *item = new QListWidgetItem(name, _macroList);
item->setData(Qt::UserRole, name);
}
}
void MacroActionRandomEdit::MacroRemove(const QString &name)
{
if (_entryData) {
auto it = _entryData->_macros.begin();
while (it != _entryData->_macros.end()) {
if (it->get()->Name() == name.toStdString()) {
it = _entryData->_macros.erase(it);
} else {
++it;
}
}
}
}
void MacroActionRandomEdit::MacroRename(const QString &oldName,
const QString &newName)
{
auto count = _macroList->count();
for (int idx = 0; idx < count; ++idx) {
QListWidgetItem *item = _macroList->item(idx);
QString itemString = item->data(Qt::UserRole).toString();
if (oldName == itemString) {
item->setData(Qt::UserRole, newName);
item->setText(newName);
break;
}
}
}
void MacroActionRandomEdit::AddMacro()
{
if (_loading || !_entryData) {
return;
}
auto macroName = _macroSelection->currentText();
MacroRef macro(macroName.toStdString());
if (!macro.get()) {
return;
}
if (FindEntry(macro->Name()) != -1) {
return;
}
QVariant v = QVariant::fromValue(macroName);
QListWidgetItem *item = new QListWidgetItem(macroName, _macroList);
item->setData(Qt::UserRole, macroName);
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macros.push_back(macro);
}
void MacroActionRandomEdit::RemoveMacro()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
auto item = _macroList->currentItem();
if (!item) {
return;
}
std::string name = item->data(Qt::UserRole).toString().toStdString();
for (auto it = _entryData->_macros.begin();
it != _entryData->_macros.end(); ++it) {
auto m = *it;
if (m.get() && m->Name() == name) {
_entryData->_macros.erase(it);
break;
}
}
delete item;
}
int MacroActionRandomEdit::FindEntry(const std::string &macro)
{
int count = _macroList->count();
int idx = -1;
for (int i = 0; i < count; i++) {
QListWidgetItem *item = _macroList->item(i);
QString itemString = item->data(Qt::UserRole).toString();
if (QString::fromStdString(macro) == itemString) {
idx = i;
break;
}
}
return idx;
}
void MacroActionRandomEdit::MacroSelectionChanged(int idx)
{
if (_loading || !_entryData || idx == -1) {
return;
}
QListWidgetItem *item = _macroList->item(idx);
QString name = item->text();
_macroSelection->SetCurrentMacro(GetMacroByName(name.toUtf8().data()));
}

View File

@@ -17,14 +17,22 @@ const static std::map<SceneVisibilityAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.sceneVisibility.type.hide"},
};
struct VisInfo {
const static std::map<SceneItemSourceType, std::string> sourceItemSourceTypes = {
{SceneItemSourceType::SOURCE,
"AdvSceneSwitcher.action.sceneVisibility.type.source"},
{SceneItemSourceType::SOURCE_GROUP,
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup"},
};
struct VisibilityData {
std::string name;
bool visible;
};
static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
static bool visibilitySourceEnum(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{
VisInfo *vInfo = reinterpret_cast<VisInfo *>(ptr);
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
if (vInfo->name == sourceName) {
obs_sceneitem_set_visible(item, vInfo->visible);
@@ -32,7 +40,26 @@ static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilityEnum, ptr);
obs_scene_enum_items(scene, visibilitySourceEnum, ptr);
}
return true;
}
static bool visibilitySourceTypeEnum(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceTypeName = obs_source_get_display_name(
obs_source_get_id(obs_sceneitem_get_source(item)));
if (vInfo->name == sourceTypeName) {
obs_sceneitem_set_visible(item, vInfo->visible);
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilitySourceTypeEnum, ptr);
}
return true;
@@ -43,18 +70,21 @@ bool MacroActionSceneVisibility::PerformAction()
auto s = obs_weak_source_get_source(_scene.GetScene());
auto scene = obs_scene_from_source(s);
auto sourceName = GetWeakSourceName(_source);
VisInfo vInfo = {sourceName, _action == SceneVisibilityAction::SHOW};
VisibilityData vInfo = {"", _action == SceneVisibilityAction::SHOW};
switch (_action) {
case SceneVisibilityAction::SHOW:
obs_scene_enum_items(scene, visibilityEnum, &vInfo);
switch (_sourceType) {
case SceneItemSourceType::SOURCE:
vInfo.name = sourceName;
obs_scene_enum_items(scene, visibilitySourceEnum, &vInfo);
break;
case SceneVisibilityAction::HIDE:
obs_scene_enum_items(scene, visibilityEnum, &vInfo);
case SceneItemSourceType::SOURCE_GROUP:
vInfo.name = _sourceGroup;
obs_scene_enum_items(scene, visibilitySourceTypeEnum, &vInfo);
break;
default:
break;
}
obs_source_release(s);
return true;
}
@@ -63,10 +93,18 @@ void MacroActionSceneVisibility::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO,
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
it->second.c_str(), GetWeakSourceName(_source).c_str(),
_scene.ToString().c_str());
if (_sourceType == SceneItemSourceType::SOURCE) {
vblog(LOG_INFO,
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
it->second.c_str(),
GetWeakSourceName(_source).c_str(),
_scene.ToString().c_str());
} else {
vblog(LOG_INFO,
"performed visibility action \"%s\" for any source type \"%s\" on scene \"%s\"",
it->second.c_str(), _sourceGroup.c_str(),
_scene.ToString().c_str());
}
} else {
blog(LOG_WARNING, "ignored unknown SceneVisibility action %d",
static_cast<int>(_action));
@@ -77,8 +115,14 @@ bool MacroActionSceneVisibility::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
if (_sourceType == SceneItemSourceType::SOURCE) {
obs_data_set_string(obj, "source",
GetWeakSourceName(_source).c_str());
} else {
obs_data_set_string(obj, "source", _sourceGroup.c_str());
}
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "sourceType", static_cast<int>(_sourceType));
return true;
}
@@ -86,8 +130,11 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_scene.Load(obj);
_sourceType = static_cast<SceneItemSourceType>(
obs_data_get_int(obj, "sourceType"));
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_sourceGroup = sourceName;
_action = static_cast<SceneVisibilityAction>(
obs_data_get_int(obj, "action"));
return true;
@@ -95,9 +142,16 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
std::string MacroActionSceneVisibility::GetShortDesc()
{
if (_source) {
if (_sourceType == SceneItemSourceType::SOURCE && _source) {
return _scene.ToString() + " - " + GetWeakSourceName(_source);
}
if (_sourceType == SceneItemSourceType::SOURCE_GROUP &&
!_sourceGroup.empty()) {
return _scene.ToString() + " - " +
obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup") +
" " + _sourceGroup;
}
return "";
}
@@ -108,31 +162,44 @@ static inline void populateActionSelection(QComboBox *list)
}
}
static inline void populateSourceItemTypeSelection(QComboBox *list)
{
for (auto entry : sourceItemSourceTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_scenes = new SceneSelectionWidget(window(), false, true, true);
_sourceTypes = new QComboBox();
_sources = new QComboBox();
_sources->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_actions = new QComboBox();
populateSourceItemTypeSelection(_sourceTypes);
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SourceTypeChanged(int)));
QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SourceChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{sourceTypes}}", _sourceTypes},
{"{{sources}}", _sources},
{"{{actions}}", _actions},
};
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.SceneVisibility.entry"),
"AdvSceneSwitcher.action.sceneVisibility.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
@@ -148,10 +215,17 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_sourceTypes->setCurrentIndex(
static_cast<int>(_entryData->_sourceType));
_scenes->SetScene(_entryData->_scene);
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
} else {
populateSourceTypeSelection(_sources);
_sources->setCurrentText(_entryData->_sourceGroup.c_str());
}
}
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
@@ -165,6 +239,26 @@ void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
}
_sources->clear();
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->adjustSize();
}
void MacroActionSceneVisibilityEdit::SourceTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_sourceType =
static_cast<SceneItemSourceType>(value);
}
_sources->clear();
if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
populateSceneItemSelection(_sources, _entryData->_scene);
} else {
populateSourceTypeSelection(_sources);
}
}
void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text)
@@ -174,7 +268,15 @@ void MacroActionSceneVisibilityEdit::SourceChanged(const QString &text)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = GetWeakSourceByQString(text);
if (_entryData->_sourceType == SceneItemSourceType::SOURCE) {
_entryData->_source = GetWeakSourceByQString(text);
} else {
if (text == obs_module_text("AdvSceneSwitcher.selectItem")) {
_entryData->_sourceGroup = "";
} else {
_entryData->_sourceGroup = text.toStdString();
}
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

228
src/macro-action-timer.cpp Normal file
View 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();
}
}

View File

@@ -188,6 +188,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
return;
}
auto idx = _entryData->get()->GetIndex();
std::string id = MacroConditionFactory::GetIdByName(text);
auto temp = DurationConstraint();
@@ -198,6 +199,7 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
auto logic = (*_entryData)->GetLogicType();
_entryData->reset();
*_entryData = MacroConditionFactory::Create(id);
(*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic);
auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData);
@@ -276,12 +278,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 +309,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()

View File

@@ -96,6 +96,7 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
{
_sources = new QComboBox();
_filters = new QComboBox();
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_conditions = new QComboBox();
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.filter.getSettings"));
@@ -159,6 +160,7 @@ void MacroConditionFilterEdit::SourceChanged(const QString &text)
}
_filters->clear();
populateFilterSelection(_filters, _entryData->_source);
_filters->adjustSize();
}
void MacroConditionFilterEdit::FilterChanged(const QString &text)

View File

@@ -125,6 +125,8 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
_currentCount = new QLabel(parent);
_resetCount = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.macro.count.reset"));
_pausedWarning = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.macro.pausedWarning"));
_count->setMaximum(10000000);
populateTypeSelection(_types);
@@ -156,9 +158,18 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
mainLayout->addLayout(typesLayout);
mainLayout->addLayout(_settingsLine1);
mainLayout->addLayout(_settingsLine2);
mainLayout->addWidget(_pausedWarning);
setLayout(mainLayout);
_entryData = entryData;
connect(&_countTimer, SIGNAL(timeout()), this, SLOT(UpdateCount()));
_countTimer.start(1000);
_pausedWarning->setVisible(false);
connect(&_pausedTimer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
_pausedTimer.start(1000);
UpdateEntryData();
_loading = false;
}
@@ -237,7 +248,6 @@ void MacroConditionMacroEdit::UpdateEntryData()
_counterConditions->setCurrentIndex(
static_cast<int>(_entryData->_counterCondition));
_count->setValue(_entryData->_count);
ResetTimer();
}
void MacroConditionMacroEdit::MacroChanged(const QString &text)
@@ -248,7 +258,6 @@ void MacroConditionMacroEdit::MacroChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_macro.UpdateRef(text);
ResetTimer();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -303,7 +312,6 @@ void MacroConditionMacroEdit::ResetClicked()
}
_entryData->_macro->ResetCount();
ResetTimer();
}
void MacroConditionMacroEdit::UpdateCount()
@@ -316,9 +324,9 @@ void MacroConditionMacroEdit::UpdateCount()
}
}
void MacroConditionMacroEdit::ResetTimer()
void MacroConditionMacroEdit::UpdatePaused()
{
_timer.reset(new QTimer(this));
connect(_timer.get(), SIGNAL(timeout()), this, SLOT(UpdateCount()));
_timer->start(1000);
_pausedWarning->setVisible(_entryData && _entryData->_macro.get() &&
_entryData->_macro->Paused());
adjustSize();
}

View File

@@ -0,0 +1,184 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-scene-visibility.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <regex>
const std::string MacroConditionSceneVisibility::id = "scene_visibility";
bool MacroConditionSceneVisibility::_registered =
MacroConditionFactory::Register(
MacroConditionSceneVisibility::id,
{MacroConditionSceneVisibility::Create,
MacroConditionSceneVisibilityEdit::Create,
"AdvSceneSwitcher.condition.sceneVisibility"});
static std::map<SceneVisibilityCondition, std::string>
SceneVisibilityConditionTypes = {
{SceneVisibilityCondition::SHOWN,
"AdvSceneSwitcher.condition.sceneVisibility.type.shown"},
{SceneVisibilityCondition::HIDDEN,
"AdvSceneSwitcher.condition.sceneVisibility.type.hidden"},
};
struct VisibilityData {
std::string name;
bool visible;
bool result;
};
static bool visibilityEnum(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
{
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
if (vInfo->name == sourceName &&
obs_sceneitem_visible(item) == vInfo->visible) {
vInfo->result = true;
return false;
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilityEnum, ptr);
}
return true;
}
bool MacroConditionSceneVisibility::CheckCondition()
{
if (!_source) {
return false;
}
auto s = obs_weak_source_get_source(_scene.GetScene());
auto scene = obs_scene_from_source(s);
auto sourceName = GetWeakSourceName(_source);
VisibilityData data = {sourceName,
_condition == SceneVisibilityCondition::SHOWN,
false};
obs_scene_enum_items(scene, visibilityEnum, &data);
obs_source_release(s);
return data.result;
}
bool MacroConditionSceneVisibility::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
return true;
}
bool MacroConditionSceneVisibility::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_condition = static_cast<SceneVisibilityCondition>(
obs_data_get_int(obj, "condition"));
return true;
}
std::string MacroConditionSceneVisibility::GetShortDesc()
{
if (_source) {
return _scene.ToString() + " - " + GetWeakSourceName(_source);
}
return "";
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : SceneVisibilityConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionSceneVisibilityEdit::MacroConditionSceneVisibilityEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneVisibility> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, true, true);
_sources = new QComboBox();
_sources->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_conditions = new QComboBox();
populateConditionSelection(_conditions);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SourceChanged(const QString &)));
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{conditions}}", _conditions},
};
QHBoxLayout *mainLayout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneVisibility.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionSceneVisibilityEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = GetWeakSourceByQString(text);
}
void MacroConditionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = s;
}
_sources->clear();
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->adjustSize();
}
void MacroConditionSceneVisibilityEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<SceneVisibilityCondition>(index);
}
void MacroConditionSceneVisibilityEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_scenes->SetScene(_entryData->_scene);
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
}

View File

@@ -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();
}

View File

@@ -13,11 +13,21 @@ bool MacroConditionStream::_registered = MacroConditionFactory::Register(
static std::map<StreamState, std::string> streamStates = {
{StreamState::STOP, "AdvSceneSwitcher.condition.stream.state.stop"},
{StreamState::START, "AdvSceneSwitcher.condition.stream.state.start"},
{StreamState::STARTING,
"AdvSceneSwitcher.condition.stream.state.starting"},
{StreamState::STOPPING,
"AdvSceneSwitcher.condition.stream.state.stopping"},
};
bool MacroConditionStream::CheckCondition()
{
bool stateMatch = false;
bool streamStarting = switcher->lastStreamStartingTime !=
_lastStreamStartingTime;
bool streamStopping = switcher->lastStreamStoppingTime !=
_lastStreamStoppingTime;
switch (_streamState) {
case StreamState::STOP:
stateMatch = !obs_frontend_streaming_active();
@@ -25,9 +35,22 @@ bool MacroConditionStream::CheckCondition()
case StreamState::START:
stateMatch = obs_frontend_streaming_active();
break;
case StreamState::STARTING:
stateMatch = streamStarting;
break;
case StreamState::STOPPING:
stateMatch = streamStopping;
break;
default:
break;
}
if (streamStarting) {
_lastStreamStartingTime = switcher->lastStreamStartingTime;
}
if (streamStopping) {
_lastStreamStoppingTime = switcher->lastStreamStoppingTime;
}
return stateMatch;
}

View File

@@ -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();
}

View File

@@ -35,7 +35,12 @@ static std::map<VideoCondition, std::string> conditionTypes = {
cv::CascadeClassifier initObjectCascade(std::string &path)
{
cv::CascadeClassifier cascade;
cascade.load(path);
try {
cascade.load(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
}
return cascade;
}
@@ -94,7 +99,10 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
GetWeakSourceName(_videoSource).c_str());
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_bool(obj, "usePatternForChangedCheck",
_usePatternForChangedCheck);
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);
@@ -126,14 +134,17 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath");
_usePatternForChangedCheck =
obs_data_get_bool(obj, "usePatternForChangedCheck");
_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");
@@ -169,32 +180,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;
try {
_objectCascade = initObjectCascade(path);
} catch (...) {
blog(LOG_WARNING, "failed to load model data \"%s\"",
path.c_str());
return false;
}
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)
{
@@ -211,34 +197,109 @@ 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;
}
bool MacroConditionVideo::OutputChanged()
{
if (_usePatternForChangedCheck) {
cv::Mat result;
_patternData = createPatternData(_matchImage);
matchPattern(_screenshotData->image, _patternData,
_patternThreshold, result, _useAlphaAsMask);
return countNonZero(result) == 0;
}
return _screenshotData->image != _matchImage;
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize)
@@ -274,9 +335,9 @@ bool MacroConditionVideo::Compare()
case VideoCondition::DIFFER:
return _screenshotData->image != _matchImage;
case VideoCondition::HAS_CHANGED:
return _screenshotData->image != _matchImage;
return OutputChanged();
case VideoCondition::HAS_NOT_CHANGED:
return _screenshotData->image == _matchImage;
return !OutputChanged();
case VideoCondition::NO_IMAGE:
return _screenshotData->image.isNull();
case VideoCondition::PATTERN:
@@ -353,12 +414,18 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_imagePath = new FileSelection();
_imagePath->Button()->disconnect();
_usePatternForChangedCheck = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"));
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternThreshold = new ThresholdSlider(
0., 1.,
obs_module_text(
"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(
@@ -370,6 +437,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);
@@ -396,8 +465,12 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
SLOT(ImageBrowseButtonClicked()));
QWidget::connect(_usePatternForChangedCheck, SIGNAL(stateChanged(int)),
this, SLOT(UsePatternForChangedCheckChanged(int)));
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,10 +545,13 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
showMatchLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLine1Layout);
mainLayout->addWidget(_usePatternForChangedCheck);
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);
@@ -526,11 +602,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) {
@@ -635,6 +706,18 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
ImagePathChanged(path);
}
void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_usePatternForChangedCheck = value;
_patternThreshold->setVisible(value);
adjustSize();
}
void MacroConditionVideoEdit::PatternThresholdChanged(double value)
{
if (_loading || !_entryData) {
@@ -645,6 +728,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) {
@@ -735,7 +829,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);
}
}
}
@@ -749,7 +843,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);
}
@@ -769,11 +863,12 @@ void MacroConditionVideoEdit::ShowMatchClicked()
}
QImage markedIamge;
QImage pattern = _entryData->GetMatchImage();
if (_entryData->_condition == VideoCondition::PATTERN) {
cv::Mat result;
QImage pattern = _entryData->GetMatchImage();
matchPattern(screenshot->image, pattern,
_entryData->_patternThreshold, result);
_entryData->_patternThreshold, result,
_entryData->_useAlphaAsMask);
if (countNonZero(result) == 0) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
@@ -810,9 +905,13 @@ void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
std::string path = text.toStdString();
if (!_entryData->LoadModelData(path)) {
bool dataLoaded = false;
{
std::lock_guard<std::mutex> lock(switcher->m);
std::string path = text.toStdString();
dataLoaded = _entryData->LoadModelData(path);
}
if (!dataLoaded) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.modelLoadFail"));
}
@@ -835,26 +934,34 @@ bool needsThrottleControls(VideoCondition cond)
cond == VideoCondition::OBJECT;
}
void setLayoutVisible(QLayout *layout, bool visible)
bool needsThreshold(VideoCondition cond)
{
for (int i = 0; i < layout->count(); ++i) {
QWidget *widget = layout->itemAt(i)->widget();
if (widget != NULL) {
widget->setVisible(visible);
}
}
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
bool patternControlIsOptional(VideoCondition cond)
{
return cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
void MacroConditionVideoEdit::SetWidgetVisibility()
{
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
_patternThreshold->setVisible(
needsPatternThreshold(_entryData->_condition));
_usePatternForChangedCheck->setVisible(
patternControlIsOptional(_entryData->_condition));
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_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,
@@ -863,6 +970,13 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsObjectControls(_entryData->_condition));
setLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->_condition));
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_usePatternForChangedCheck);
}
adjustSize();
}
@@ -876,7 +990,10 @@ void MacroConditionVideoEdit::UpdateEntryData()
GetWeakSourceName(_entryData->_videoSource).c_str());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
_entryData->_usePatternForChangedCheck);
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
_useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
_minNeighbors->setValue(_entryData->_minNeighbors);

View File

@@ -145,3 +145,8 @@ void MacroSegmentEdit::SetFocusPolicyOfWidgets()
w->installEventFilter(new MouseWheelWidgetAdjustmentGuard(w));
}
}
void MacroSegmentEdit::SetCollapsed(bool collapsed)
{
_section->SetCollapsed(collapsed);
}

View File

@@ -16,7 +16,7 @@ MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent)
firstItem->setEnabled(false);
for (auto &m : switcher->macros) {
addItem(QString::fromStdString(m.Name()));
addItem(QString::fromStdString(m->Name()));
}
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,

View File

@@ -47,7 +47,7 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name)
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->macros.emplace_back(name);
switcher->macros.emplace_back(std::make_shared<Macro>(name));
}
return true;
}
@@ -84,8 +84,11 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_one();
int idx = ui->macros->currentRow();
QString::fromStdString(switcher->macros[idx].Name());
QString::fromStdString(switcher->macros[idx]->Name());
switcher->macros.erase(switcher->macros.begin() + idx);
for (auto &m : switcher->macros) {
m->ResolveMacroRef();
}
}
delete item;
@@ -108,7 +111,7 @@ void AdvSceneSwitcher::on_macroUp_clicked()
switcher->macros.begin() + index - 1);
for (auto &m : switcher->macros) {
m.ResolveMacroRef();
m->ResolveMacroRef();
}
}
}
@@ -125,7 +128,7 @@ void AdvSceneSwitcher::on_macroDown_clicked()
switcher->macros.begin() + index + 1);
for (auto &m : switcher->macros) {
m.ResolveMacroRef();
m->ResolveMacroRef();
}
}
}
@@ -170,42 +173,44 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
emit MacroRenamed(oldName, newName);
}
void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
{
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);
}
ui->macroEditActionHelp->setVisible(actions.size() == 0);
}
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t 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)
@@ -310,12 +315,12 @@ void AdvSceneSwitcher::on_macros_itemChanged(QListWidgetItem *item)
void AdvSceneSwitcher::setupMacroTab()
{
for (auto &m : switcher->macros) {
QString text = QString::fromStdString(m.Name());
QString text = QString::fromStdString(m->Name());
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
item->setData(Qt::UserRole, text);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
if (m.Paused()) {
if (m->Paused()) {
item->setCheckState(Qt::Unchecked);
} else {
item->setCheckState(Qt::Checked);
@@ -388,8 +393,8 @@ void AdvSceneSwitcher::CopyMacro()
return;
}
switcher->macros.back().Load(data);
switcher->macros.back().SetName(name);
switcher->macros.back()->Load(data);
switcher->macros.back()->SetName(name);
obs_data_release(data);
QString text = QString::fromStdString(name);
@@ -400,17 +405,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 +432,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 +441,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 +450,5 @@ void AdvSceneSwitcher::CollapseAllConditions()
if (!m) {
return;
}
for (auto &c : m->Conditions()) {
c->SetCollapsed(true);
}
SetEditMacro(*m);
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, true);
}

View File

@@ -36,6 +36,11 @@ bool Macro::CeckMatch()
{
_matched = false;
for (auto &c : _conditions) {
if (_paused) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
return false;
}
auto startTime = std::chrono::high_resolution_clock::now();
bool cond = c->CheckCondition();
auto endTime = std::chrono::high_resolution_clock::now();
@@ -89,15 +94,11 @@ bool Macro::CeckMatch()
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
// Condition checks shall still be run even if macro is paused.
// Otherwise conditions might behave in unexpected ways when resuming.
//
// For example, audio could immediately match after unpause, when it
// matched before it was paused due to timers not being updated.
if (_paused) {
vblog(LOG_INFO, "Macro %s is paused", _name.c_str());
_matched = false;
return false;
// TODO: Move back to PerformAction() once new scene collection frontend
// events are available - see:
// https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
if (_matched && _count != std::numeric_limits<int>::max()) {
_count++;
}
return _matched;
@@ -113,9 +114,6 @@ bool Macro::PerformAction()
return false;
}
}
if (ret && _count != std::numeric_limits<int>::max()) {
_count++;
}
return ret;
}
@@ -125,6 +123,21 @@ void Macro::SetName(const std::string &name)
SetHotkeysDesc();
}
void Macro::ResetTimers()
{
for (auto &c : _conditions) {
c->ResetDuration();
}
}
void Macro::SetPaused(bool pause)
{
if (_paused && !pause) {
ResetTimers();
}
_paused = pause;
}
void Macro::UpdateActionIndices()
{
int idx = 0;
@@ -290,13 +303,18 @@ void Macro::ResolveMacroRef()
MacroRefCondition *ref =
dynamic_cast<MacroRefCondition *>(c.get());
if (ref) {
ref->_macro.UpdateRef();
ref->ResolveMacroRef();
}
}
for (auto &a : _actions) {
MacroRefAction *ref = dynamic_cast<MacroRefAction *>(a.get());
if (ref) {
ref->_macro.UpdateRef();
ref->ResolveMacroRef();
}
MultiMacroRefAction *ref2 =
dynamic_cast<MultiMacroRefAction *>(a.get());
if (ref2) {
ref2->ResolveMacroRef();
}
}
}
@@ -445,7 +463,7 @@ void SwitcherData::saveMacros(obs_data_t *obj)
for (auto &m : macros) {
obs_data_t *array_obj = obs_data_create();
m.Save(array_obj);
m->Save(array_obj);
obs_data_array_push_back(macroArray, array_obj);
obs_data_release(array_obj);
@@ -463,14 +481,14 @@ void SwitcherData::loadMacros(obs_data_t *obj)
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(macroArray, i);
macros.emplace_back();
macros.back().Load(array_obj);
macros.emplace_back(std::make_shared<Macro>());
macros.back()->Load(array_obj);
obs_data_release(array_obj);
}
obs_data_array_release(macroArray);
for (auto &m : macros) {
m.ResolveMacroRef();
m->ResolveMacroRef();
}
}
@@ -478,11 +496,11 @@ bool SwitcherData::checkMacros()
{
bool ret = false;
for (auto &m : macros) {
if (m.CeckMatch()) {
if (m->CeckMatch()) {
ret = true;
// This has to be performed here for now as actions are
// not performed immediately after checking conditions.
if (m.SwitchesScene()) {
if (m->SwitchesScene()) {
switcher->macroSceneSwitched = true;
}
}
@@ -492,12 +510,15 @@ bool SwitcherData::checkMacros()
bool SwitcherData::runMacros()
{
for (auto &m : macros) {
if (m.Matched()) {
vblog(LOG_INFO, "running macro: %s", m.Name().c_str());
if (!m.PerformAction()) {
// TODO: Don't rely on creating a copy of each macro once new frontend
// events are available - see:
// https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
for (auto m : macros) {
if (m->Matched()) {
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
if (!m->PerformAction()) {
blog(LOG_WARNING, "abort macro: %s",
m.Name().c_str());
m->Name().c_str());
return false;
}
}
@@ -508,8 +529,8 @@ bool SwitcherData::runMacros()
Macro *GetMacroByName(const char *name)
{
for (auto &m : switcher->macros) {
if (m.Name() == name) {
return &m;
if (m->Name() == name) {
return m.get();
}
}
@@ -570,3 +591,10 @@ void MacroRefAction::ResolveMacroRef()
{
_macro.UpdateRef();
}
void MultiMacroRefAction::ResolveMacroRef()
{
for (auto &m : _macros) {
m.UpdateRef();
}
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -266,7 +266,7 @@ bool VideoSwitch::loadImageFromFile()
return false;
}
matchImage =
matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
return true;
}

View File

@@ -12,6 +12,7 @@
#include <QTimer>
#include <QMessageBox>
#include <QJsonDocument>
#include <QSystemTrayIcon>
#include <unordered_map>
#include <regex>
#include <set>
@@ -213,10 +214,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();
@@ -228,6 +229,16 @@ void clearLayout(QLayout *layout)
}
}
void setLayoutVisible(QLayout *layout, bool visible)
{
for (int i = 0; i < layout->count(); ++i) {
QWidget *widget = layout->itemAt(i)->widget();
if (widget != NULL) {
widget->setVisible(visible);
}
}
}
bool compareIgnoringLineEnding(QString &s1, QString &s2)
{
// Let QT deal with different types of lineendings
@@ -443,6 +454,17 @@ bool DisplayMessage(const QString &msg, bool question)
return false;
}
void DisplayTrayMessage(const QString &title, const QString &msg)
{
auto tray = reinterpret_cast<QSystemTrayIcon *>(
obs_frontend_get_system_tray());
if (!tray) {
return;
}
tray->showMessage(title, msg);
}
void addSelectionEntry(QComboBox *sel, const char *description, bool selectable,
const char *tooltip)
{
@@ -788,7 +810,8 @@ void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
{
std::set<QString> names;
if (s.GetType() == SceneSelectionType::CURRENT) {
if (s.GetType() == SceneSelectionType::CURRENT ||
s.GetType() == SceneSelectionType::PREVIOUS) {
auto enumScenes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
@@ -816,6 +839,36 @@ void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
list->setCurrentIndex(0);
}
void populateSourceTypeSelection(QComboBox *list)
{
std::set<QString> sourceTypeNames;
auto enumSourcesTypes = [](void *param, obs_source_t *source) {
if (!source) {
return true;
}
std::set<QString> *names =
reinterpret_cast<std::set<QString> *>(param);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) {
return true;
}
names->insert(
obs_source_get_display_name(obs_source_get_id(source)));
return true;
};
obs_enum_sources(enumSourcesTypes, &sourceTypeNames);
for (auto &name : sourceTypeNames) {
if (!name.isEmpty()) {
list->addItem(name);
}
}
list->model()->sort(0);
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
list->setCurrentIndex(0);
}
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor endColor, QString specifier,
bool once)