Compare commits

...

53 Commits
1.15.3 ... 1.16

Author SHA1 Message Date
WarmUpTill
08933a3683 Add transition condition 2021-10-03 07:51:09 -07:00
WarmUpTill
9be00ba3ca Rework to use transition selection widget 2021-10-03 07:51:09 -07:00
WarmUpTill
cce15529fe Adjust return type 2021-10-03 07:51:09 -07:00
WarmUpTill
588f90f0b1 Add transition selection widget 2021-10-03 07:51:09 -07:00
WarmUpTill
d3220c40dc Add any transition selection in populate function 2021-10-03 07:51:09 -07:00
WarmUpTill
79c659b4fa Reduce logging 2021-10-03 01:38:08 -07:00
WarmUpTill
292adb990d Add option to throttle object and pattern recognition 2021-10-03 01:38:08 -07:00
WarmUpTill
b7a0f6c5d0 Add object detection option 2021-10-03 01:38:08 -07:00
WarmUpTill
b3f1aff03c Add file selection widget
Rework the following macro conditions and actions to use this widget:
 - file action
 - run action
 - file condition
2021-10-03 01:38:08 -07:00
WarmUpTill
aea9d1b319 Update build instructions 2021-10-03 01:38:08 -07:00
WarmUpTill
1fd724e4f4 Include OpenCV in build 2021-10-03 01:38:08 -07:00
WarmUpTill
3d1c613f1c Add pattern match using openCV 2021-10-03 01:38:08 -07:00
WarmUpTill
9ca2f4470b Add opencv as submodule 2021-10-03 01:38:08 -07:00
WarmUpTill
34527b08a9 Fix crash in getSourceSettings() 2021-10-02 14:14:57 -07:00
WarmUpTill
3ae1050488 CI: Switch to obs tag 27.1.1 2021-09-30 12:13:15 -07:00
WarmUpTill
d7ec7c6e40 Limit key press to OBS by default on MacOS 2021-09-30 10:42:57 -07:00
WarmUpTill
e98ee64974 Set canSimulateKeyPresses to false on Macos (not implemented) 2021-09-30 10:42:57 -07:00
WarmUpTill
7c6428b27e Include press duration option and option to limit key press to OBS 2021-09-30 10:42:57 -07:00
WarmUpTill
aca9b84fc6 Fix rapid scene switch restarting certain transitions (#313)
When using certain transition types repeated scene switches to the same target scene will restart a transition that is already in progress.
This can be avoided by using obs_frontend_get_current_scene() instead of switcher->currentScene during the check if the target scene is already active.
obs_frontend_get_current_scene() will already point to the new scene, even if the transition is still in progess, while switcher->currentScene will only be updated once a transition is finished.
2021-09-26 14:04:01 +02:00
WarmUpTill
d397f5e406 Add transition action 2021-09-19 05:15:49 -07:00
WarmUpTill
97cbcd7e61 Add condition "scene has not changed" 2021-09-18 10:21:54 -07:00
WarmUpTill
64fd8dd50d Add "apt update" calls 2021-09-18 10:21:35 -07:00
WarmUpTill
a0dd1cf3ef Remove unused variable 2021-09-16 10:27:32 -07:00
WarmUpTill
25cf51abea Add option to specify lib and data install location 2021-09-13 12:50:10 -07:00
Joao Eriberto Mota Filho
3c8f5088d3 Fix word "unknown" 2021-09-13 10:58:39 -07:00
Joao Eriberto Mota Filho
66f3c74cf3 Fix some mistakes in debian CI test
Some mistakes found when packaging for Debian:
  - Remove trash from rules and enforce hardening.
  - Use the same licensing from Debian package to avoid a misinterpretation
    from FTP Master.
  - Fix target variable in debian/rules
2021-09-13 10:58:39 -07:00
WarmUpTill
6c23110fb9 Strip Linux binary debug symbols to reduce release artifact size 2021-09-12 11:09:00 -07:00
WarmUpTill
c9001df97c Add libxss-dev dependency 2021-09-12 11:09:00 -07:00
WarmUpTill
76fa1f8942 Add backward compatibility check to support OBS 25 2021-09-12 10:20:59 -07:00
Joao Eriberto Mota Filho
55ca146a82 Install missing obs-frontend-api.h 2021-09-12 10:20:59 -07:00
Joao Eriberto Mota Filho
a3625242de Create CI test for Debian packaging (in Ubuntu 20.04) 2021-09-12 10:20:59 -07:00
WarmUpTill
db4214c918 Remove dependency to <util/util.hpp> 2021-09-12 03:11:21 -07:00
WarmUpTill
f024199e2d Add backwards compatibility check 2021-09-12 03:11:21 -07:00
WarmUpTill
299e7bc868 Improve build instructions 2021-09-12 03:11:21 -07:00
WarmUpTill
0262e066bf Update cmake script to work better for out of tree builds
Use package name 'CURL' for out of tree builds.
Removed ObsPluginHelpers.cmake and FindLibObs.cmake as they disturbed
out of tree build.
2021-09-12 03:11:21 -07:00
WarmUpTill
bee75336d0 Add option to for macro condition scene to trigger on any scene change 2021-09-10 15:14:09 -07:00
WarmUpTill
96b472776a Remove unused variable 2021-09-10 15:14:09 -07:00
WarmUpTill
fb457f723a Add macro action scene swap 2021-09-10 14:21:29 -07:00
WarmUpTill
eee3e05c10 Add action to switch preview scene 2021-09-02 10:06:54 -07:00
WarmUpTill
ccc147a1b3 Fix empty paths not saving on file tab 2021-08-27 23:40:00 +02:00
WarmUpTill
45a149f6df Also save window geo when using ESC or the close button 2021-08-27 09:10:50 -07:00
WarmUpTill
076be21d40 Validate window position before setting it 2021-08-27 09:10:50 -07:00
WarmUpTill
ab5bdb32f5 Highlight newly added macro segments 2021-08-27 08:43:28 -07:00
WarmUpTill
8cfed048cc Create copy of previous macro segment when inserting new segment 2021-08-27 08:43:28 -07:00
WarmUpTill
c842595214 Add "size" section to scene item transform info 2021-08-27 08:43:08 -07:00
WarmUpTill
8fa3e8b038 Fix crash on clicking "Get settings" if source or filter is null 2021-08-27 08:43:08 -07:00
WarmUpTill
174c17edea Escape settings when pressing "Get settings" and regex is active 2021-08-27 08:43:08 -07:00
WarmUpTill
9d8fadda80 Remove unused include 2021-08-27 08:43:08 -07:00
WarmUpTill
58028d3c3d Add macro condition "scene transform" 2021-08-27 08:43:08 -07:00
WarmUpTill
3dcabe543f Add matchJson(), escapeRegex() move TransformState functions to utility 2021-08-27 08:43:08 -07:00
WarmUpTill
1a7ebc5569 Fix warning 2021-08-19 12:56:34 -07:00
WarmUpTill
522ab2963e Fix typo 2021-08-19 12:56:34 -07:00
WarmUpTill
d9bd98ce9c Parallelize windows build 2021-08-19 12:56:34 -07:00
84 changed files with 355496 additions and 666 deletions

49
.github/workflows/build-debian.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: debian-build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: check_libobs_revision
run: |
sudo apt update
sudo apt install libobs-dev
mkdir source
cd source
dpkg -l libobs-dev | tr -s " "| grep libobs | cut -d" " -f3 > libobs.rev
- name: install_frontend_header
run: |
[ -e /usr/include/obs/obs-frontend-api.h ] && { echo "ERROR: obs-frontend-api.h already in system. Maybe the package libobs-dev is installing it."; exit 1; }
cd source
LIBOBSREV=$(cat libobs.rev)
sudo apt update
sudo apt install devscripts
dget -u http://archive.ubuntu.com/ubuntu/pool/universe/o/obs-studio/obs-studio_$LIBOBSREV.dsc
cd ..
sudo find -name obs-frontend-api.h -exec cp {} /usr/include/obs/ \;
egrep '#include <obs.h>' /usr/include/obs/obs-frontend-api.h || { echo "ERROR: check if the sed commands are needed from now."; exit 1; }
sudo sed -i 's/#include <obs.h>/#include <obs\/obs.h>/' /usr/include/obs/obs-frontend-api.h
sudo sed -i 's/#include <util\/darray.h>/#include <obs\/util\/darray.h>/' /usr/include/obs/obs-frontend-api.h
- name: create_tarball
run: |
cd ..
tar --exclude=.git -cvzf obs-scene-switcher_0.1+testonly.orig.tar.gz SceneSwitcher
- name: create_debian_dir
run: |
cp -a CI/linux/debian .
- name: install_dependencies
run: |
# 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
- name: build
run: |
debuild --no-lintian --no-sign

View File

@@ -9,6 +9,7 @@ on:
env:
PLUGIN_NAME: SceneSwitcher
LIB_NAME: advanced-scene-switcher
OBS_TAG: 27.1.1
jobs:
macos64:
@@ -23,6 +24,7 @@ jobs:
with:
repository: obsproject/obs-studio
submodules: 'recursive'
ref: 'refs/tags/${{ env.OBS_TAG }}'
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4
with:
@@ -57,6 +59,15 @@ jobs:
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz
tar -xf ./macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp"
xattr -r -d com.apple.quarantine /tmp/obsdeps
- name: 'Build prerequisite: OpenCV'
shell: bash
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}/deps/opencv
mkdir build
cd build
cmake -DBUILD_LIST="core,imgproc,objdetect" ..
make -j4
make install
- name: Configure
shell: bash
run: |
@@ -111,6 +122,7 @@ jobs:
with:
repository: obsproject/obs-studio
submodules: 'recursive'
ref: 'refs/tags/${{ env.OBS_TAG }}'
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4
with:
@@ -168,7 +180,8 @@ jobs:
qtbase5-private-dev \
libqt5svg5-dev \
swig \
libxss-dev
libxss-dev \
libopencv-dev
- name: 'Configure'
shell: bash
run: |
@@ -186,6 +199,7 @@ jobs:
FILE_NAME=${{ env.PLUGIN_NAME }}-$FILE_DATE-${{ github.sha }}-linux64.tar.gz
echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV
mkdir -p ./${{ env.LIB_NAME }}/bin/64bit/
strip -d ./build/UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.LIB_NAME }}.so
mv ./build/UI/frontend-plugins/${{ env.PLUGIN_NAME }}/${{ env.LIB_NAME }}.so ./${{ env.LIB_NAME }}/bin/64bit/${{ env.LIB_NAME }}.so
mv ./UI/frontend-plugins/${{ env.PLUGIN_NAME }}/data ./${{ env.LIB_NAME }}/data
tar -cvzf "${FILE_NAME}" ${{ env.LIB_NAME }}
@@ -197,6 +211,9 @@ jobs:
windows:
name: Windows
runs-on: [windows-latest]
strategy:
matrix:
arch: [32, 64]
env:
QT_VERSION: 5.10.1
CMAKE_GENERATOR: "Visual Studio 16 2019"
@@ -210,6 +227,7 @@ jobs:
with:
repository: obsproject/obs-studio
submodules: 'recursive'
ref: 'refs/tags/${{ env.OBS_TAG }}'
- name: Checkout plugin
uses: actions/checkout@v2.3.4
with:
@@ -228,53 +246,97 @@ jobs:
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}/cmbuild/deps"
- name: 'Build prerequisite: OpenCV'
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}/deps/opencv
mkdir build
cd build
if ( ${{ matrix.arch }} -eq 32 )
{
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DBUILD_LIST="core,imgproc,objdetect" ..
}
else
{
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DBUILD_LIST="core,imgproc,objdetect" ..
}
msbuild /m /p:Configuration=Release OpenCV.sln
msbuild INSTALL.vcxproj
- name: Configure
run: |
mkdir ./package
mkdir ./build32
cd ./build32
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
cd ..
mkdir ./build64
cd ./build64
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017_64" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
mkdir ./build${{ matrix.arch }}
cd ./build${{ matrix.arch }}
Get-Location
if ( ${{ matrix.arch }} -eq 32 )
{
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"Win32" -DOpenCV_DIR="${{ github.workspace }}/UI/frontend-plugins/SceneSwitcher/deps/opencv/build/" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win32" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
}
else
{
cmake -G"${{ env.CMAKE_GENERATOR }}" -A"x64" -DOpenCV_DIR="${{ github.workspace }}/UI/frontend-plugins/SceneSwitcher/deps/opencv/build/" -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DBUILD_BROWSER=false -DBUILD_CAPTIONS=false -DCOMPILE_D3D12_HOOK=false -DDepsPath="${{ github.workspace }}/cmbuild/deps/win64" -DQTDIR="${{ github.workspace }}/cmbuild/QT/${{ env.QT_VERSION }}/msvc2017_64" -DCOPIED_DEPENDENCIES=FALSE -DCOPY_DEPENDENCIES=TRUE ..
}
- name: Build
run: |
msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-studio.sln
msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-studio.sln
msbuild /m /p:Configuration=RelWithDebInfo .\build${{ matrix.arch }}\obs-studio.sln
- name: Package
if: success()
run: |
$env:FILE_DATE=(Get-Date -UFormat "%F")
$env:FILE_NAME="${{ env.PLUGIN_NAME }}-${env:FILE_DATE}-${{ github.sha }}-windows"
echo "FILE_NAME=${env:FILE_NAME}" >> ${env:GITHUB_ENV}
robocopy .\build32\rundir\RelWithDebInfo\obs-plugins\32bit\ .\package\obs-plugins\32bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
robocopy .\build64\rundir\RelWithDebInfo\obs-plugins\64bit\ .\package\obs-plugins\64bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
robocopy .\build64\rundir\RelWithDebInfo\data\obs-plugins\${{ env.LIB_NAME }}\ .\package\data\obs-plugins\${{ env.LIB_NAME }}\ /E /XF .gitignore
robocopy .\build${{ matrix.arch }}\rundir\RelWithDebInfo\obs-plugins\${{ matrix.arch }}bit\ .\package\obs-plugins\${{ matrix.arch }}bit ${{ env.LIB_NAME }}.* /E /XF .gitignore
robocopy .\build${{ matrix.arch }}\rundir\RelWithDebInfo\data\obs-plugins\${{ env.LIB_NAME }}\ .\package\data\obs-plugins\${{ env.LIB_NAME }}\ /E /XF .gitignore
cp UI/frontend-plugins/${{ env.PLUGIN_NAME }}/deps/opencv/build/bin/Release/*dll package/obs-plugins/${{ matrix.arch }}bit
exit 0
- name: Publish zip
if: success()
uses: actions/upload-artifact@v2.2.1
with:
name: '${{ env.FILE_NAME }}'
name: '${{ env.FILE_NAME }}-${{ matrix.arch }}bit'
path: package/*
- name: "Package Installer (Prereqs)"
windows-installer:
needs: [windows]
name: 'Create Windows Installer'
runs-on: [windows-latest]
steps:
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
- name: "Prepare innosetup"
run: |
curl "-kL" "https://files.jrsoftware.org/is/6/innosetup-6.0.3.exe" "-f" "--retry" "5" "-o" "inno.exe"
.\inno.exe /VERYSILENT /SP- /SUPPRESSMSGBOXES /NORESTART
mkdir build
cd build
cmake ..
continue-on-error: true
- name: Download artifacts
uses: actions/download-artifact@v2
with:
path: artifacts
- name: "Prepare package dir"
run: |
curl "-kL" "https://github.com/Xaymar/msvc-redist-helper/releases/download/0.1/msvc-redist-helper-64.exe" "-f" "--retry" "5" "-o" "msvc-redist-helper-64.exe"
curl "-kL" "https://github.com/Xaymar/msvc-redist-helper/releases/download/0.1/msvc-redist-helper-32.exe" "-f" "--retry" "5" "-o" "msvc-redist-helper-32.exe"
curl "-kL" "https://files.jrsoftware.org/is/6/innosetup-6.0.3.exe" "-f" "--retry" "5" "-o" "inno.exe"
.\inno.exe /VERYSILENT /SP- /SUPPRESSMSGBOXES /NORESTART
- name: "Package Installer (Compile)"
$env:FILE_DATE=(Get-Date -UFormat "%F")
$env:FILE_NAME="${{ env.PLUGIN_NAME }}-${env:FILE_DATE}-${{ github.sha }}-windows"
echo "FILE_NAME=${env:FILE_NAME}" >> ${env:GITHUB_ENV}
mkdir package
cd package
cp -r ../artifacts/${env:FILE_NAME}-32bit/* .
cp -r ../artifacts/${env:FILE_NAME}-64bit/obs-plugins/* obs-plugins
- name: "Compile"
run: |
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' /Qp ".\build64\UI\frontend-plugins\${{ env.PLUGIN_NAME }}\CI\windows\setup.iss"
- name: Publish installer
& 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' /Qp ".\build\CI\windows\setup.iss"
- name: "Publish"
if: success()
uses: actions/upload-artifact@v2.2.0
with:
name: '${{ env.FILE_NAME }}-installer'
path: build64/UI/frontend-plugins/SceneSwitcher/CI/windows/Output/*.exe
path: build/CI/windows/Output/*.exe
release:
needs: [macos64, ubuntu64, windows]
needs: [macos64, ubuntu64, windows, windows-installer]
name: 'Create Release'
runs-on: [ubuntu-18.04]
steps:
@@ -292,7 +354,8 @@ jobs:
mkdir -p ${{ env.PLUGIN_NAME }}/Linux ${{ env.PLUGIN_NAME }}/MacOs ${{ env.PLUGIN_NAME }}/Windows
tar xf artifacts/${{ env.PLUGIN_NAME }}*-linux64.tar.gz/${{ env.PLUGIN_NAME }}*-linux64.tar.gz -C ${{ env.PLUGIN_NAME }}/Linux/
mv artifacts/${{ env.PLUGIN_NAME }}*-macos.pkg/${{ env.PLUGIN_NAME }}*-macos.pkg ${{ env.PLUGIN_NAME }}/MacOs/${{ env.PLUGIN_NAME }}.pkg
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows/* ${{ env.PLUGIN_NAME }}/Windows/
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows-32bit/* ${{ env.PLUGIN_NAME }}/Windows/
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows-64bit/obs-plugins/* ${{ env.PLUGIN_NAME }}/Windows/obs-plugins
mv ./artifacts/${{ env.PLUGIN_NAME }}*-windows-installer/*.exe ${{ env.PLUGIN_NAME }}/Windows/
rm ${{ env.PLUGIN_NAME }}/Windows/obs-plugins/32bit/advanced-scene-switcher.pdb
rm ${{ env.PLUGIN_NAME }}/Windows/obs-plugins/64bit/advanced-scene-switcher.pdb

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "deps/websocketpp"]
path = deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
[submodule "deps/opencv"]
path = deps/opencv
url = https://github.com/opencv/opencv.git

View File

@@ -16,7 +16,20 @@ git clone --recursive https://github.com/WarmUpTill/SceneSwitcher.git
```
Then modify the obs-studio/UI/frontend-plugins/CMakeLists.txt file and add an entry for the scene switcher:
```
add_subdirectory(SceneSwitcher)
```
Now follow the [build instructions for obs-studio](https://obsproject.com/wiki/install-instructions) for your particular platform.
Note that on Linux systems it might be necessary to additionally install the following packages to fulfill the dependencies to `XTest`, `XScreensaver` and `OpenCV` - exact command may differ:
```
sudo apt-get install \
libxtst-dev \
libxss-dev \
libopencv-dev
```
## Compiling out of tree
### Prerequisites
@@ -41,7 +54,7 @@ code of OBS Studio, located at [source_directory]/libobs/.
- **LIBOBS_FRONTEND_INCLUDE_DIR** (path) : location of the obs-frontend-api
subfolder in the source code of OBS Studio, located at [source_directory]/UI/obs-frontend-api.
- **CURL_LIB** (filepath) : location of the libcurl.lib file
- **CURL_LIBRARY** (filepath) : location of the libcurl.lib file
(part of the dependencies2019/win64/bin folder used to build OBS)
- **CURL_INCLUDE_DIR** (path) : location of the curl
subfolder in OBS dependencies folder: ".../dependencies2019/win64/include/"
@@ -55,6 +68,14 @@ Assuming that you set up Qt via QT installer:
Just keep hitting configure until all the vars are filled out. Then hit generate.
### Linux
Install dependencies `XTest`, `XScreensaver` and `OpenCV` - exact command may differ:
```
sudo apt-get install \
libxtst-dev \
libxss-dev \
libopencv-dev
```
Most versions of Linux you can use cmake-gui or the command line.
**For the command line:**

View File

@@ -0,0 +1,5 @@
obs-scene-switcher (0.1+testonly) focal; urgency=medium
* Initial release (Closes: #123456)
-- Joao Eriberto Mota Filho <eriberto@debian.org> Fri, 03 Sep 2021 11:39:52 -0300

22
CI/linux/debian/control Normal file
View File

@@ -0,0 +1,22 @@
Source: obs-scene-switcher
Section: video
Priority: optional
Maintainer: Joao Eriberto Mota Filho <eriberto@debian.org>
Build-Depends: cmake,
debhelper-compat (= 12),
libcurl4-openssl-dev,
libobs-dev,
libxtst-dev,
qtbase5-dev,
libxss-dev,
libopencv-dev
Standards-Version: 4.6.0
Homepage: https://obsproject.com/forum/resources/advanced-scene-switcher.395/
Rules-Requires-Root: no
Package: obs-scene-switcher
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: automated scene switcher for OBS Studio (plugin)
Advanced Scene Switcher is based on the built in scene switcher. It extends
its functionality by adding several features.

60
CI/linux/debian/copyright Normal file
View File

@@ -0,0 +1,60 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: SceneSwitcher
Upstream-Contact: https://github.com/WarmUpTill/SceneSwitcher/issues
Source: https://github.com/WarmUpTill/SceneSwitcher
Files: *
Copyright: 2016-2021 WarmUpTill <WarmUpTill@web.de>
License: GPL-2
Files: cmake/GetGitRevisionDescription.cmake
cmake/GetGitRevisionDescription.cmake.in
Copyright: 2009-2013 Iowa State University
2013-2020 Contributors
2013-2020 Ryan Pavlik
License: BSL-1.0
Files: debian/*
Copyright: 2021 Joao Eriberto Mota Filho <eriberto@debian.org>
License: BSL-1.0
License: GPL-2
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
License: BSL-1.0
Boost Software License - Version 1.0 - August 17th, 2003
.
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
.
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

16
CI/linux/debian/rules Normal file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/make -f
#export DH_VERBOSE = 1
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export QT_SELECT = qt5
%:
dh $@
dh_auto_configure -- -DBUILD_OUT_OF_TREE=1 \
-DLIBOBS_INCLUDE_DIR=/usr/include/obs \
-DLIBOBS_LIB=/usr/lib/${DEB_TARGET_MULTIARCH}/libobs.so \
-DLIBOBS_FRONTEND_INCLUDE_DIR=/usr/include/obs \
-DLIBOBS_FRONTEND_API_LIB=/usr/lib/${DEB_TARGET_MULTIARCH}/libobs-frontend-api.so

View File

@@ -0,0 +1 @@
3.0 (native)

View File

@@ -46,6 +46,294 @@
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_objdetect.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_features2d.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_core.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_calib3d.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_calib3d.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_imgproc.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_features2d.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_core.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_flann.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_calib3d.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_objdetect.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_imgproc.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_flann.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_imgproc.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_flann.4.5.3.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_features2d.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_core.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
<dict>
<key>CHILDREN</key>
<array/>
<key>GID</key>
<integer>80</integer>
<key>PATH</key>
<string>../../deps/opencv/build/lib/libopencv_objdetect.4.5.dylib</string>
<key>PATH_TYPE</key>
<integer>3</integer>
<key>PERMISSIONS</key>
<integer>493</integer>
<key>TYPE</key>
<integer>3</integer>
<key>UID</key>
<integer>0</integer>
</dict>
</array>
<key>GID</key>
<integer>80</integer>

View File

@@ -44,25 +44,14 @@ include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp" @ONLY)
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR
"${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR
"${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}"
ISS_MSVC_REDIST_HELPER_DIR)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in"
"${CMAKE_CURRENT_BINARY_DIR}/CI/windows/setup.iss" @ONLY)
endif()
# Windows installer
if(WIN32)
get_filename_component(ISS_PLUGIN_FILES_DIR
"${CMAKE_BINARY_DIR}\\..\\package" ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_PLUGIN_FILES_DIR}" ISS_PLUGIN_FILES_DIR)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR
"${CMAKE_PACKAGE_PREFIX}\\.." ABSOLUTE)
get_filename_component(ISS_MSVC_REDIST_HELPER_DIR "${CMAKE_BINARY_DIR}\\.."
ABSOLUTE)
file(TO_NATIVE_PATH "${ISS_MSVC_REDIST_HELPER_DIR}"
ISS_MSVC_REDIST_HELPER_DIR)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/CI/windows/setup.iss.in"
@@ -73,19 +62,67 @@ endif()
if(BUILD_OUT_OF_TREE)
set(CMAKE_PREFIX_PATH "${QTDIR}")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/external")
include(external/FindLibObs.cmake)
find_package(LibObs REQUIRED)
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_path(LIBOBS_FRONTEND_INCLUDE_DIR HINTS ${LIBOBS_INCLUDE_DIRS})
find_file(
LIBOBS_FRONTEND_API_LIB
NAMES libobs-frontend-api.*
HINTS ${LIBOBS_LIB})
find_package(LibObs)
find_package(LibObs-frontend-api)
if(LibObs_FOUND)
set(LIBOBS_LIB ${LIBOBS_LIBRARIES})
set(LIBOBS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIRS})
endif()
if(LibObs-frontend-api_FOUND)
set(LIBOBS_FRONTEND_API_LIB ${LIBOBS-FRONTEND-API_LIBRARIES})
set(LIBOBS_FRONTEND_INCLUDE_DIR ${LIBOBS-FRONTEND-API_INCLUDE_DIR})
endif()
include_directories("${LIBOBS_INCLUDE_DIRS}" "${LIBOBS_FRONTEND_INCLUDE_DIR}"
if(NOT LIBOBS_LIB)
message(FATAL_ERROR "obs library not found - please set LIBOBS_LIB")
endif()
if(NOT LIBOBS_FRONTEND_API_LIB)
message(
FATAL_ERROR
"libobs frontend-api library not found - please set LIBOBS_FRONTEND_API_LIB"
)
endif()
if(NOT LIBOBS_INCLUDE_DIR)
message(
FATAL_ERROR "obs.hpp header not found - please set LIBOBS_INCLUDE_DIR")
endif()
if(NOT LIBOBS_FRONTEND_INCLUDE_DIR)
message(
FATAL_ERROR
" obs-frontend-api.h not found - please set LIBOBS_FRONTEND_INCLUDE_DIR"
)
endif()
include_directories("${LIBOBS_INCLUDE_DIR}" "${LIBOBS_FRONTEND_INCLUDE_DIR}"
${Qt5Core_INCLUDES} ${Qt5Widgets_INCLUDES})
find_package(CURL REQUIRED)
include_directories("${CURL_INCLUDE_DIRS}")
# Backwards compatability with older OBS versions
if(LibObs_VERSION_MAJOR GREATER_EQUAL 27)
add_definitions(-DVCAM_SUPPORTED)
else()
message(
WARNING
"OBS version ${LibObs_VERSION_MAJOR} found - disabling virtual camera functionality"
)
endif()
if(LibObs_VERSION_MAJOR GREATER_EQUAL 26)
add_definitions(-DREPLAYBUFFER_SUPPORTED)
else()
message(
WARNING
"OBS version ${LibObs_VERSION_MAJOR} found - disabling replay buffer functionality"
)
endif()
else()
find_package(Libcurl REQUIRED)
include_directories("${LIBCURL_INCLUDE_DIRS}")
add_definitions(-DVCAM_SUPPORTED)
add_definitions(-DREPLAYBUFFER_SUPPORTED)
endif()
# Platform specific settings
@@ -97,9 +134,6 @@ if(APPLE)
include_directories(${COCOA})
endif()
find_package(Libcurl REQUIRED)
include_directories("${LIBCURL_INCLUDE_DIRS}")
if(UNIX AND NOT APPLE)
find_package(X11 REQUIRED COMPONENTS Xtst Xss)
link_libraries(${X11_LIBRARIES})
@@ -130,6 +164,10 @@ 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)
@@ -169,15 +207,18 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-macro.hpp
src/headers/macro-action-media.hpp
src/headers/macro-action-plugin-state.hpp
src/headers/macro-action-preview-scene.hpp
src/headers/macro-action-recording.hpp
src/headers/macro-action-replay-buffer.hpp
src/headers/macro-action-run.hpp
src/headers/macro-action-scene-order.hpp
src/headers/macro-action-scene-swap.hpp
src/headers/macro-action-scene-switch.hpp
src/headers/macro-action-scene-transform.hpp
src/headers/macro-action-scene-visibility.hpp
src/headers/macro-action-source.hpp
src/headers/macro-action-streaming.hpp
src/headers/macro-action-transition.hpp
src/headers/macro-action-virtual-cam.hpp
src/headers/macro-action-wait.hpp
src/headers/macro-condition-edit.hpp
@@ -195,10 +236,12 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-condition-recording.hpp
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.hpp
src/headers/macro-condition-source.hpp
src/headers/macro-condition-streaming.hpp
src/headers/macro-condition-timer.hpp
src/headers/macro-condition-transition.hpp
src/headers/macro-condition-video.hpp
src/headers/macro-condition-virtual-cam.hpp
src/headers/macro-condition-window.hpp
@@ -210,8 +253,10 @@ set(advanced-scene-switcher_HEADERS
src/headers/hotkey.hpp
src/headers/scene-selection.hpp
src/headers/screenshot-helper.hpp
src/headers/transition-selection.hpp
src/headers/name-dialog.hpp
src/headers/duration-control.hpp
src/headers/file-selection.hpp
src/headers/section.hpp
src/headers/platform-funcs.hpp
src/headers/utility.hpp
@@ -235,6 +280,7 @@ set(advanced-scene-switcher_SOURCES
src/switch-window.cpp
src/switch-media.cpp
src/switch-network.cpp
src/file-selection.cpp
src/hotkey.cpp
src/general.cpp
src/switch-pause.cpp
@@ -251,15 +297,18 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-macro.cpp
src/macro-action-media.cpp
src/macro-action-plugin-state.cpp
src/macro-action-preview-scene.cpp
src/macro-action-recording.cpp
src/macro-action-replay-buffer.cpp
src/macro-action-run.cpp
src/macro-action-scene-order.cpp
src/macro-action-scene-swap.cpp
src/macro-action-scene-switch.cpp
src/macro-action-scene-transform.cpp
src/macro-action-scene-visibility.cpp
src/macro-action-source.cpp
src/macro-action-streaming.cpp
src/macro-action-transition.cpp
src/macro-action-virtual-cam.cpp
src/macro-action-wait.cpp
src/macro-condition-edit.cpp
@@ -277,10 +326,12 @@ set(advanced-scene-switcher_SOURCES
src/macro-condition-recording.cpp
src/macro-condition-replay-buffer.cpp
src/macro-condition-scene-order.cpp
src/macro-condition-scene-transform.cpp
src/macro-condition-scene.cpp
src/macro-condition-source.cpp
src/macro-condition-streaming.cpp
src/macro-condition-timer.cpp
src/macro-condition-transition.cpp
src/macro-condition-video.cpp
src/macro-condition-virtual-cam.cpp
src/macro-condition-window.cpp
@@ -292,6 +343,7 @@ set(advanced-scene-switcher_SOURCES
src/curl-helper.cpp
src/scene-selection.cpp
src/screenshot-helper.cpp
src/transition-selection.cpp
src/name-dialog.cpp
src/duration-control.cpp
src/section.cpp
@@ -310,8 +362,13 @@ add_library(
# Out of tree build
if(BUILD_OUT_OF_TREE)
target_link_libraries(
advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS}
${LIBOBS_LIB} ${LIBOBS_FRONTEND_API_LIB} Qt5::Core Qt5::Widgets)
advanced-scene-switcher
${advanced-scene-switcher_PLATFORM_LIBS}
${OpenCV_LIBRARIES}
${LIBOBS_LIB}
${LIBOBS_FRONTEND_API_LIB}
Qt5::Core
Qt5::Widgets)
# Additional commands to install the module in the correct place. Find all the
# translation files so we can copy them to the correct place later on.
@@ -324,24 +381,28 @@ if(BUILD_OUT_OF_TREE)
# Linux
if(UNIX AND NOT APPLE)
if(ARCH EQUAL 64)
set(ARCH_NAME "x86_64")
else()
set(ARCH_NAME "i686")
if(NOT LIB_OUT_DIR)
set(LIB_OUT_DIR "/lib/obs-plugins")
endif()
if(NOT DATA_OUT_DIR)
set(DATA_OUT_DIR "/share/obs/obs-plugins/advanced-scene-switcher")
endif()
set_target_properties(advanced-scene-switcher PROPERTIES PREFIX "")
install(TARGETS advanced-scene-switcher
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
install(
FILES ${ASS_TRANSLATION_FILES}
DESTINATION
"${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/advanced-scene-switcher/locale"
)
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
install(DIRECTORY data/locale
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
install(DIRECTORY data/res
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
endif()
else()
# In tree build
target_link_libraries(
advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS}
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,5 +1,5 @@
# SceneSwitcher
An automated scene switcher for OBS Studio
An automated scene switcher for OBS Studio.
More information can be found on https://obsproject.com/forum/resources/automatic-scene-switching.395/.
@@ -13,7 +13,7 @@ For the Windows and MacOS platforms, it is recommended to run the provided insta
For Linux the Snap package manager offers an OBS Studio installation which is bundled with the plugin:
```
sudo snap install obs-studi
sudo snap install obs-studio
```
If that is not an option you will have to ...

View File

@@ -93,14 +93,16 @@ AdvSceneSwitcher.condition.cursor.hideFrame="Hide frame"
AdvSceneSwitcher.condition.cursor.entry.line1="Cursor is {{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
AdvSceneSwitcher.condition.cursor.entry.line2="Cursor is currently at {{xPos}} x {{yPos}}"
AdvSceneSwitcher.condition.scene="Scene"
AdvSceneSwitcher.condition.scene.type.current="Current"
AdvSceneSwitcher.condition.scene.type.previous="Previous"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} scene is {{scenes}}"
AdvSceneSwitcher.condition.scene.type.current="Current scene is"
AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
AdvSceneSwitcher.condition.scene.type.changed="Scene chagned"
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}}"
AdvSceneSwitcher.condition.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"
AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} {{browseButton}} matches:"
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} matches:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Media"
@@ -111,10 +113,26 @@ AdvSceneSwitcher.condition.video.condition.differ="does not match"
AdvSceneSwitcher.condition.video.condition.hasChanged="has changed"
AdvSceneSwitcher.condition.video.condition.hasNotChanged="has not changed"
AdvSceneSwitcher.condition.video.condition.noImage="has no output"
AdvSceneSwitcher.condition.video.condition.pattern="matches pattern"
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.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}}"
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.objectScaleThreshold="Scale factor: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
AdvSceneSwitcher.condition.video.showMatch="Show match"
AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!"
AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!"
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.minSize="Minimum size: {{minSizeX}} x {{minSizeY}}"
AdvSceneSwitcher.condition.video.entry.maxSize="Maximum size: {{maxSizeX}} x {{maxSizeY}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
AdvSceneSwitcher.condition.stream="Streaming"
AdvSceneSwitcher.condition.stream.state.start="Stream running"
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
@@ -191,6 +209,19 @@ 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.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On {{scenes}} {{sources}} matches transform"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.transition="Transition"
AdvSceneSwitcher.condition.transition.type.current="Current transition type is"
AdvSceneSwitcher.condition.transition.type.duration="Current transition duration is"
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}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
@@ -226,7 +257,7 @@ AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.run.arguments="Arguments:"
AdvSceneSwitcher.action.run.addArgument="Add argument"
AdvSceneSwitcher.action.run.addArgumentDescription="Add new argument:"
AdvSceneSwitcher.action.run.entry="Run {{filePath}} {{browseButton}}"
AdvSceneSwitcher.action.run.entry="Run {{filePath}}"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
@@ -274,8 +305,9 @@ AdvSceneSwitcher.action.hotkey.leftAlt="Left Alt"
AdvSceneSwitcher.action.hotkey.rightAlt="Right Alt"
AdvSceneSwitcher.action.hotkey.leftMeta="Left Meta"
AdvSceneSwitcher.action.hotkey.rightMeta="Right Meta"
AdvSceneSwitcher.action.hotkey.onlyOBS="Send keypress only to OBS"
AdvSceneSwitcher.action.hotkey.disabled="Cannot simulate key presses - functionality disabled!"
AdvSceneSwitcher.action.hotkey.entry="Press {{keys}}"
AdvSceneSwitcher.action.hotkey.entry="Press {{keys}} for {{duration}} milliseconds"
AdvSceneSwitcher.action.sceneOrder="Scene item order"
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Move up"
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Move down"
@@ -289,7 +321,14 @@ AdvSceneSwitcher.action.sceneTransform.entry="On {{scenes}} transform {{sources}
AdvSceneSwitcher.action.file="File"
AdvSceneSwitcher.action.file.type.write="Write"
AdvSceneSwitcher.action.file.type.append="Append"
AdvSceneSwitcher.action.file.entry="{{actions}} to {{filePath}} {{browseButton}}:"
AdvSceneSwitcher.action.file.entry="{{actions}} to {{filePath}}:"
AdvSceneSwitcher.action.previewScene="Switch preview scene"
AdvSceneSwitcher.action.previewScene.entry="Switch preview scene to {{scenes}}"
AdvSceneSwitcher.action.SceneSwap="Swap scene (Studio mode)"
AdvSceneSwitcher.action.SceneSwap.entry="Swap preview and program scene in studio mode"
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"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
@@ -546,6 +585,7 @@ AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectAnyScene="Any Scene"
AdvSceneSwitcher.currentTransition="Current Transition"
AdvSceneSwitcher.anyTransition="Any Transition"
AdvSceneSwitcher.selectTransition="--select transition--"
AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectSource="--select source--"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
deps/opencv vendored Submodule

Submodule deps/opencv added at ad6e82942b

View File

@@ -1,107 +0,0 @@
# This module can be copied and used by external plugins for OBS
#
# Once done these will be defined:
#
# LIBOBS_FOUND
# LIBOBS_INCLUDE_DIRS
# LIBOBS_LIBRARIES
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_OBS QUIET obs libobs)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_lib_suffix 64)
else()
set(_lib_suffix 32)
endif()
if(DEFINED CMAKE_BUILD_TYPE)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(_build_type_base "debug")
else()
set(_build_type_base "release")
endif()
endif()
find_path(LIBOBS_INCLUDE_DIR
NAMES obs.h
HINTS
ENV obsPath${_lib_suffix}
ENV obsPath
${obsPath}
PATHS
/usr/include /usr/local/include /opt/local/include /sw/include
PATH_SUFFIXES
libobs
)
function(find_obs_lib base_name repo_build_path lib_name)
string(TOUPPER "${base_name}" base_name_u)
if(DEFINED _build_type_base)
set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}")
set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}")
endif()
find_library(${base_name_u}_LIB
NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}
HINTS
ENV obsPath${_lib_suffix}
ENV obsPath
${obsPath}
${_${base_name_u}_LIBRARY_DIRS}
PATHS
/usr/lib /usr/local/lib /opt/local/lib /sw/lib
PATH_SUFFIXES
lib${_lib_suffix} lib
libs${_lib_suffix} libs
bin${_lib_suffix} bin
../lib${_lib_suffix} ../lib
../libs${_lib_suffix} ../libs
../bin${_lib_suffix} ../bin
# base repo non-msvc-specific search paths
${_build_type_${repo_build_path}}
${_build_type_${repo_build_path}${_lib_suffix}}
build/${repo_build_path}
build${_lib_suffix}/${repo_build_path}
# base repo msvc-specific search paths on windows
build${_lib_suffix}/${repo_build_path}/Debug
build${_lib_suffix}/${repo_build_path}/RelWithDebInfo
build/${repo_build_path}/Debug
build/${repo_build_path}/RelWithDebInfo
)
endfunction()
find_obs_lib(LIBOBS libobs obs)
if(MSVC)
find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR)
mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB)
if(LIBOBS_FOUND)
if(MSVC)
if (NOT DEFINED W32_PTHREADS_LIB)
message(FATAL_ERROR "Could not find the w32-pthreads library" )
endif()
set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads)
endif()
set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR})
set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB})
include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake)
# allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg)
if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/")
set(INCLUDED_LIBOBS_CMAKE_MODULES true)
endif()
else()
message(FATAL_ERROR "Could not find the libobs library" )
endif()

View File

@@ -1,163 +0,0 @@
# Functions for generating external plugins
set(EXTERNAL_PLUGIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rundir")
# Fix XCode includes to ignore warnings on system includes
function(target_include_directories_system _target)
if(XCODE)
foreach(_arg ${ARGN})
if("${_arg}" STREQUAL "PRIVATE" OR "${_arg}" STREQUAL "PUBLIC" OR "${_arg}" STREQUAL "INTERFACE")
set(_scope ${_arg})
else()
target_compile_options(${_target} ${_scope} -isystem${_arg})
endif()
endforeach()
else()
target_include_directories(${_target} SYSTEM ${_scope} ${ARGN})
endif()
endfunction()
function(install_external_plugin_data_internal target source_dir target_dir)
install(DIRECTORY ${source_dir}/
DESTINATION "${target}/${target_dir}"
USE_SOURCE_PERMISSIONS)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/${source_dir}" "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/${target_dir}"
VERBATIM)
endfunction()
# Installs data
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data
function(install_external_plugin_data target data_loc)
install_external_plugin_data_internal(${target} ${data_loc} "data")
endfunction()
# Installs data in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data being installed
function(install_external_plugin_arch_data target data_loc)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install_external_plugin_data_internal(${target} ${data_loc} "data${_bit_suffix}")
endfunction()
# Installs data in the target's bin directory
# 'target' is the destination target project being installed to
# 'data_loc' specifies the directory of the data being installed
function(install_external_plugin_data_to_bin target data_loc)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install_external_plugin_data_internal(${target} ${data_loc} "bin${_bit_suffix}")
endfunction()
# Installs an additional binary to a target
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_additional target additional_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "64bit/")
else()
set(_bit_suffix "32bit/")
endif()
set_target_properties(${additional_target} PROPERTIES
PREFIX "")
install(TARGETS ${additional_target}
LIBRARY DESTINATION "bin"
RUNTIME DESTINATION "bin")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/bin/${_bit_suffix}$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs the binary of the target
# 'target' is the target project being installed
function(install_external_plugin target)
install_external_plugin_additional(${target} ${target})
endfunction()
# Installs the binary and data of the target
# 'target' is the destination target project being installed to
function(install_external_plugin_with_data target data_loc)
install_external_plugin(${target})
install_external_plugin_data(${target} ${data_loc})
endfunction()
# Installs an additional binary to the data of a target
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_bin_to_data target additional_target)
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data"
RUNTIME DESTINATION "data")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data/$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs an additional binary in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_bin_to_arch_data target additional_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data${_bit_suffix}"
RUNTIME DESTINATION "data${_bit_suffix}")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"$<TARGET_FILE:${additional_target}>"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data${_bit_suffix}/$<TARGET_FILE_NAME:${additional_target}>"
VERBATIM)
endfunction()
# Installs an additional file in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac.
# 'target' is the destination target project being installed to
# 'additional_target' specifies the additional binary
function(install_external_plugin_data_file_to_arch_data target additional_target file_target)
if(APPLE)
set(_bit_suffix "")
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_bit_suffix "/64bit")
else()
set(_bit_suffix "/32bit")
endif()
get_filename_component(file_target_name ${file_target} NAME)
install(TARGETS ${additional_target}
LIBRARY DESTINATION "data${_bit_suffix}"
RUNTIME DESTINATION "data${_bit_suffix}")
add_custom_command(TARGET ${additional_target} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
"${file_target}"
"${EXTERNAL_PLUGIN_OUTPUT_DIR}/$<CONFIGURATION>/${target}/data${_bit_suffix}/${file_target_name}"
VERBATIM)
endfunction()

View File

@@ -360,8 +360,7 @@ void switchScene(const sceneSwitchInfo &sceneSwitch)
}
obs_source_t *source = obs_weak_source_get_source(sceneSwitch.scene);
obs_source_t *currentSource =
obs_weak_source_get_source(switcher->currentScene);
obs_source_t *currentSource = obs_frontend_get_current_scene();
if (source && source != currentSource) {
transitionData currentTransitionData;
@@ -476,6 +475,9 @@ extern "C" void FreeSceneSwitcher()
void handleSceneChange()
{
switcher->lastSceneChangeTime =
std::chrono::high_resolution_clock::now();
// Stop waiting if scene was changed
if (switcher->sceneChangedDuringWait()) {
switcher->cv.notify_one();
@@ -545,6 +547,12 @@ void setReplayBufferSaved()
switcher->replayBufferSaved = true;
}
void setTranstionEnd()
{
switcher->lastTransitionEndTime =
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()
@@ -576,9 +584,14 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
resetLiveTime();
break;
#ifdef REPLAYBUFFER_SUPPORTED
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
setReplayBufferSaved();
break;
#endif
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
setTranstionEnd();
break;
default:
break;
}

49
src/file-selection.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "headers/file-selection.hpp"
#include <obs-module.h>
#include <QLayout>
#include <QFileDialog>
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
: _type(type), QWidget(parent)
{
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(PathChange()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(_filePath);
layout->addWidget(_browseButton);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
void FileSelection::SetPath(const QString &path)
{
_filePath->setText(path);
}
void FileSelection::BrowseButtonClicked()
{
QString path;
if (_type == FileSelection::Type::WRITE) {
path = QFileDialog::getSaveFileName(this);
} else {
path = QFileDialog::getOpenFileName(this);
}
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
emit PathChanged(path);
}
void FileSelection::PathChange()
{
emit PathChanged(_filePath->text());
}

View File

@@ -3,12 +3,21 @@
#include "headers/version.h"
#include <QFileDialog>
#include <QGuiApplication>
#if __linux__
#include <QDesktopWidget>
#endif
QMetaObject::Connection inactivePluse;
void AdvSceneSwitcher::on_close_clicked()
{
done(0);
close();
}
void AdvSceneSwitcher::reject()
{
close();
}
void AdvSceneSwitcher::UpdateNonMatchingScene(const QString &name)
@@ -377,9 +386,23 @@ void AdvSceneSwitcher::setTabOrder()
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
bool windowPosValid(QPoint pos)
{
#if __linux__
// QDesktopWidget::availableGeometry() is deprecated.
// Switch to QGuiApplication::screenAt() when it is available on most Linux platforms.
QDesktopWidget *desktop = QApplication::desktop();
auto screen = desktop->availableGeometry(pos);
return (pos.x() > screen.x()) && (pos.y() > screen.y()) &&
(pos.x() < screen.width() && (pos.y() < screen.height()));
#else
return !!QGuiApplication::screenAt(pos);
#endif
}
void AdvSceneSwitcher::restoreWindowGeo()
{
if (switcher->saveWindowGeo) {
if (switcher->saveWindowGeo && windowPosValid(switcher->windowPos)) {
this->resize(switcher->windowSize);
this->move(switcher->windowPos);
}

View File

@@ -30,6 +30,7 @@ public:
AdvSceneSwitcher(QWidget *parent);
void reject() override;
void closeEvent(QCloseEvent *event) override;
void SetStarted();
@@ -49,6 +50,8 @@ public:
bool addNewMacro(std::string &name);
Macro *getSelectedMacro();
void SetEditMacro(Macro &m);
void HighlightAction(int idx);
void HighlightCondition(int idx);
void ConnectControlSignals(MacroActionEdit *);
void ConnectControlSignals(MacroConditionEdit *);
void SwapActions(Macro *m, int pos1, int pos2);

View File

@@ -0,0 +1,30 @@
#pragma once
#include <QLineEdit>
#include <QPushButton>
#include <QWidget>
class FileSelection : public QWidget {
Q_OBJECT
public:
enum class Type {
READ,
WRITE,
};
FileSelection(FileSelection::Type type = FileSelection::Type::READ,
QWidget *parent = 0);
void SetPath(const QString &);
QPushButton *Button() { return _browseButton; }
private slots:
void BrowseButtonClicked();
void PathChange();
signals:
void PathChanged(const QString &);
private:
Type _type;
QLineEdit *_filePath;
QPushButton *_browseButton;
};

View File

@@ -2,6 +2,7 @@
#include <QSpinBox>
#include <QPlainTextEdit>
#include "macro-action-edit.hpp"
#include "file-selection.hpp"
enum class FileAction {
WRITE,
@@ -47,16 +48,14 @@ public:
}
private slots:
void FilePathChanged();
void BrowseButtonClicked();
void PathChanged(const QString &text);
void TextChanged();
void ActionChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
QLineEdit *_filePath;
QPushButton *_browseButton;
FileSelection *_filePath;
QPlainTextEdit *_text;
QComboBox *_actions;
std::shared_ptr<MacroActionFile> _entryData;

View File

@@ -3,6 +3,7 @@
#include "hotkey.hpp"
#include <QCheckBox>
#include <QSpinBox>
#include <QHBoxLayout>
class MacroActionHotkey : public MacroAction {
@@ -27,6 +28,12 @@ public:
bool _rightAlt = false;
bool _leftMeta = false;
bool _rightMeta = false;
int _duration = 300; // in ms
#ifdef __APPLE__
bool _onlySendToObs = true;
#else
bool _onlySendToObs = false;
#endif
private:
static bool _registered;
@@ -59,6 +66,8 @@ private slots:
void RAltChanged(int state);
void LMetaChanged(int state);
void RMetaChanged(int state);
void DurationChanged(int ms);
void OnlySendToOBSChanged(int state);
protected:
QComboBox *_keys;
@@ -70,10 +79,14 @@ protected:
QCheckBox *_rightAlt;
QCheckBox *_leftMeta;
QCheckBox *_rightMeta;
QSpinBox *_duration;
QCheckBox *_onlySendToOBS;
QLabel *_noKeyPressSimulationWarning;
std::shared_ptr<MacroActionHotkey> _entryData;
private:
void SetWarningVisibility();
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -0,0 +1,54 @@
#pragma once
#include "macro-action-edit.hpp"
#include "scene-selection.hpp"
class MacroActionPreviewScene : public MacroAction {
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<MacroActionPreviewScene>();
}
SceneSelection _scene;
private:
static bool _registered;
static const std::string id;
};
class MacroActionPreviewSceneEdit : public QWidget {
Q_OBJECT
public:
MacroActionPreviewSceneEdit(
QWidget *parent,
std::shared_ptr<MacroActionPreviewScene> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionPreviewSceneEdit(
parent,
std::dynamic_pointer_cast<MacroActionPreviewScene>(
action));
}
private slots:
void SceneChanged(const SceneSelection &);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
std::shared_ptr<MacroActionPreviewScene> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -1,3 +1,5 @@
#ifdef REPLAYBUFFER_SUPPORTED
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
@@ -65,3 +67,5 @@ private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};
#endif

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-action-edit.hpp"
#include "file-selection.hpp"
#include <QLineEdit>
#include <QPushButton>
@@ -43,8 +44,7 @@ public:
}
private slots:
void FilePathChanged();
void BrowseButtonClicked();
void PathChanged(const QString &text);
void AddArg();
void RemoveArg();
void ArgUp();
@@ -56,8 +56,7 @@ protected:
std::shared_ptr<MacroActionRun> _entryData;
private:
QLineEdit *_filePath;
QPushButton *_browseButton;
FileSelection *_filePath;
QListWidget *_argList;
QPushButton *_addArg;
QPushButton *_removeArg;

View File

@@ -0,0 +1,38 @@
#pragma once
#include "macro-action-edit.hpp"
class MacroActionSceneSwap : public MacroAction {
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<MacroActionSceneSwap>();
}
private:
static bool _registered;
static const std::string id;
};
class MacroActionSceneSwapEdit : public QWidget {
Q_OBJECT
public:
MacroActionSceneSwapEdit(
QWidget *parent,
std::shared_ptr<MacroActionSceneSwap> entryData = nullptr);
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionSceneSwapEdit(
parent, std::dynamic_pointer_cast<MacroActionSceneSwap>(
action));
}
protected:
std::shared_ptr<MacroActionSceneSwap> _entryData;
};

View File

@@ -0,0 +1,68 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include "transition-selection.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
class MacroActionTransition : public MacroAction {
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<MacroActionTransition>();
}
bool _setDuration = true;
bool _setType = true;
TransitionSelection _transition;
Duration _duration;
private:
static bool _registered;
static const std::string id;
};
class MacroActionTransitionEdit : public QWidget {
Q_OBJECT
public:
MacroActionTransitionEdit(
QWidget *parent,
std::shared_ptr<MacroActionTransition> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionTransitionEdit(
parent,
std::dynamic_pointer_cast<MacroActionTransition>(
action));
}
private slots:
void SetTypeChanged(int state);
void SetDurationChanged(int state);
void TransitionChanged(const TransitionSelection &);
void DurationChanged(double seconds);
signals:
void HeaderInfoChanged(const QString &);
protected:
QCheckBox *_setType;
QCheckBox *_setDuration;
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
QHBoxLayout *_typeLayout;
QHBoxLayout *_durationLayout;
std::shared_ptr<MacroActionTransition> _entryData;
private:
bool _loading = true;
};

View File

@@ -1,3 +1,5 @@
#ifdef VCAM_SUPPORTED
#pragma once
#include "macro-action-edit.hpp"
@@ -55,3 +57,5 @@ private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};
#endif

View File

@@ -1,5 +1,7 @@
#pragma once
#include "macro.hpp"
#include "file-selection.hpp"
#include <QWidget>
#include <QComboBox>
#include <QDateTime>
@@ -61,8 +63,7 @@ public:
private slots:
void FileTypeChanged(int index);
void FilePathChanged();
void BrowseButtonClicked();
void PathChanged(const QString &text);
void MatchTextChanged();
void UseRegexChanged(int state);
void CheckModificationDateChanged(int state);
@@ -72,8 +73,7 @@ signals:
protected:
QComboBox *_fileType;
QLineEdit *_filePath;
QPushButton *_browseButton;
FileSelection *_filePath;
QPlainTextEdit *_matchText;
QCheckBox *_useRegex;
QCheckBox *_checkModificationDate;

View File

@@ -1,3 +1,5 @@
#ifdef REPLAYBUFFER_SUPPORTED
#pragma once
#include "macro.hpp"
#include <QWidget>
@@ -54,3 +56,5 @@ protected:
private:
bool _loading = true;
};
#endif

View File

@@ -0,0 +1,69 @@
#pragma once
#include "macro.hpp"
#include "scene-selection.hpp"
#include <QSpinBox>
#include <QPlainTextEdit>
#include <QCheckBox>
class MacroConditionSceneTransform : 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<MacroConditionSceneTransform>();
}
SceneSelection _scene;
OBSWeakSource _source;
bool _regex = false;
std::string _settings = "";
private:
static bool _registered;
static const std::string id;
};
class MacroConditionSceneTransformEdit : public QWidget {
Q_OBJECT
public:
MacroConditionSceneTransformEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneTransform> entryData =
nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionSceneTransformEdit(
parent,
std::dynamic_pointer_cast<MacroConditionSceneTransform>(
cond));
}
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const QString &text);
void GetSettingsClicked();
void SettingsChanged();
void RegexChanged(int);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sources;
QPushButton *_getSettings;
QPlainTextEdit *_settings;
QCheckBox *_regex;
std::shared_ptr<MacroConditionSceneTransform> _entryData;
private:
bool _loading = true;
};

View File

@@ -8,6 +8,8 @@
enum class SceneType {
CURRENT,
PREVIOUS,
CHANGED,
NOTCHANGED,
};
class MacroConditionScene : public MacroCondition {
@@ -26,6 +28,7 @@ public:
SceneType _type;
private:
std::chrono::high_resolution_clock::time_point _lastSceneChangeTime{};
static bool _registered;
static const std::string id;
};
@@ -58,5 +61,6 @@ protected:
std::shared_ptr<MacroConditionScene> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -0,0 +1,79 @@
#pragma once
#include "macro.hpp"
#include "duration-control.hpp"
#include "transition-selection.hpp"
#include <QWidget>
#include <QComboBox>
enum class TransitionCondition {
CURRENT,
DURATION,
STARTED,
ENDED,
};
class MacroConditionTransition : 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<MacroConditionTransition>();
}
void ConnectToTransitionSignals();
void DisconnectTransitionSignals();
TransitionCondition _condition = TransitionCondition::CURRENT;
TransitionSelection _transition;
Duration _duration;
private:
static void TransitionStarted(void *data, calldata_t *);
static void TransitionEnded(void *data, calldata_t *);
bool _started = false;
bool _ended = false;
std::chrono::high_resolution_clock::time_point _lastTransitionEndTime{};
static bool _registered;
static const std::string id;
};
class MacroConditionTransitionEdit : public QWidget {
Q_OBJECT
public:
MacroConditionTransitionEdit(
QWidget *parent,
std::shared_ptr<MacroConditionTransition> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionTransitionEdit(
parent,
std::dynamic_pointer_cast<MacroConditionTransition>(
cond));
}
private slots:
void ConditionChanged(int cond);
void TransitionChanged(const TransitionSelection &);
void DurationChanged(double seconds);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_conditions;
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
QLabel *_durationSuffix;
std::shared_ptr<MacroConditionTransition> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,10 +1,14 @@
#pragma once
#include "macro.hpp"
#include "screenshot-helper.hpp"
#include "file-selection.hpp"
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
#include <QHBoxLayout>
#include <chrono>
#include <opencv2/opencv.hpp>
enum class VideoCondition {
MATCH,
@@ -12,8 +16,13 @@ enum class VideoCondition {
HAS_NOT_CHANGED,
HAS_CHANGED,
NO_IMAGE,
PATTERN,
OBJECT,
};
constexpr int minMinNeighbors = 3;
constexpr int maxMinNeighbors = 6;
class MacroConditionVideo : public MacroCondition {
public:
bool CheckCondition();
@@ -28,20 +37,65 @@ public:
}
void GetScreenshot();
bool LoadImageFromFile();
bool LoadModelData(std::string &path);
std::string GetModelDataPath() { return _modelDataPath; }
void ResetLastMatch() { _lastMatchResult = false; }
OBSWeakSource _videoSource;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
double _patternThreshold = 0.8;
cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1;
int _minNeighbors = minMinNeighbors;
int _minSizeX = 0;
int _minSizeY = 0;
int _maxSizeX = 0;
int _maxSizeY = 0;
bool _throttleEnabled = false;
int _throttleCount = 3;
private:
bool ScreenshotContainsPattern();
bool ScreenshotContainsObject();
bool Compare();
bool CheckShouldBeSkipped();
std::unique_ptr<AdvSSScreenshotObj> _screenshotData = nullptr;
QImage _matchImage;
std::string _modelDataPath =
obs_get_module_data_path(obs_current_module()) +
std::string(
"/res/cascadeClassifiers/haarcascade_frontalface_alt.xml");
bool _lastMatchResult = false;
int _runCount = 0;
static bool _registered;
static const std::string id;
};
class ThresholdSlider : public QWidget {
Q_OBJECT
public:
ThresholdSlider(double min = 0., double max = 1.,
const QString &label = "threshold",
const QString &description = "", QWidget *parent = 0);
void SetDoubleValue(double);
public slots:
void NotifyValueChanged(int value);
signals:
void DoubleValueChanged(double value);
private:
void SetDoubleValueText(double);
QLabel *_value;
QSlider *_slider;
double _scale = 100.0;
int _precision = 2;
};
class MacroConditionVideoEdit : public QWidget {
Q_OBJECT
@@ -59,23 +113,55 @@ public:
}
void UpdatePreviewTooltip();
void SetFilePath(const QString &text);
private slots:
void SourceChanged(const QString &text);
void ConditionChanged(int cond);
void FilePathChanged();
void BrowseButtonClicked();
void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked();
void PatternThresholdChanged(double);
void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(double);
void MinNeighborsChanged(int value);
void MinSizeXChanged(int value);
void MinSizeYChanged(int value);
void MaxSizeXChanged(int value);
void MaxSizeYChanged(int value);
void ThrottleEnableChanged(int value);
void ThrottleCountChanged(int value);
void ShowMatchClicked();
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_videoSelection;
QComboBox *_condition;
QLineEdit *_filePath;
QPushButton *_browseButton;
FileSelection *_imagePath;
ThresholdSlider *_patternThreshold;
FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout;
ThresholdSlider *_objectScaleThreshold;
QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors;
QHBoxLayout *_minSizeControlLayout;
QSpinBox *_minSizeX;
QSpinBox *_minSizeY;
QHBoxLayout *_maxSizeControlLayout;
QSpinBox *_maxSizeX;
QSpinBox *_maxSizeY;
QHBoxLayout *_throttleControlLayout;
QCheckBox *_throttleEnable;
QSpinBox *_throttleCount;
QPushButton *_showMatch;
std::shared_ptr<MacroConditionVideo> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,3 +1,5 @@
#ifdef VCAM_SUPPORTED
#pragma once
#include "macro.hpp"
#include <QWidget>
@@ -52,3 +54,5 @@ protected:
private:
bool _loading = true;
};
#endif

View File

@@ -10,6 +10,6 @@ std::pair<int, int> getCursorPos();
int secondsSinceLastInput();
void GetProcessList(QStringList &processes);
bool isInFocus(const QString &executable);
void PressKeys(const std::vector<HotkeyType> keys);
void PressKeys(const std::vector<HotkeyType> keys, int duration);
void PlatformInit();
void PlatformCleanup();

View File

@@ -79,6 +79,8 @@ struct SwitcherData {
obs_source_t *waitScene = nullptr;
OBSWeakSource currentScene = nullptr;
OBSWeakSource previousScene = nullptr;
std::chrono::high_resolution_clock::time_point lastSceneChangeTime{};
std::chrono::high_resolution_clock::time_point lastTransitionEndTime{};
OBSWeakSource lastRandomScene;
SceneGroup *lastRandomSceneGroup;
OBSWeakSource nonMatchingScene;

View File

@@ -0,0 +1,46 @@
#pragma once
#include "utility.hpp"
#include <QComboBox>
enum class TransitionSelectionType {
TRANSITION,
CURRENT,
ANY,
};
class TransitionSelection {
public:
void Save(obs_data_t *obj, const char *name = "transition",
const char *typeName = "transitionType");
void Load(obs_data_t *obj, const char *name = "transition",
const char *typeName = "transitionType");
TransitionSelectionType GetType() { return _type; }
OBSWeakSource GetTransition();
std::string ToString();
private:
OBSWeakSource _transition;
TransitionSelectionType _type = TransitionSelectionType::TRANSITION;
friend class TransitionSelectionWidget;
};
class TransitionSelectionWidget : public QComboBox {
Q_OBJECT
public:
TransitionSelectionWidget(QWidget *parent, bool current = true,
bool any = false);
void SetTransition(TransitionSelection &);
void Repopulate(bool current, bool any);
signals:
void TransitionChanged(const TransitionSelection &);
private slots:
void SelectionChanged(const QString &name);
private:
bool IsCurrentTransitionSelected(const QString &name);
bool IsAnyTransitionSelected(const QString &name);
};

View File

@@ -30,25 +30,25 @@ bool compareSourceSettings(const OBSWeakSource &source,
std::vector<obs_scene_item *> getSceneItemsWithName(OBSScene scene,
std::string &name);
std::string getDataFilePath(const std::string &file);
bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex);
QString formatJsonString(std::string);
QString formatJsonString(const char *);
/**
* Populate layout with labels and widgets based on provided text
*
* @param text Text based on which labels are generated and widgets are placed.
* @param layout Layout in which the widgets and labels will be placed.
* @param placeholders Map containing a mapping of placeholder strings to widgets.
* @param addStretch Add addStretch() to layout.
*/
QString escapeForRegex(QString &s);
void loadTransformState(obs_data_t *obj, struct obs_transform_info &info,
struct obs_sceneitem_crop &crop);
bool saveTransformState(obs_data_t *obj, struct obs_transform_info &info,
struct obs_sceneitem_crop &crop);
std::string getSceneItemTransform(obs_scene_item *item);
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);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor,
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor = QColor(0, 0, 0, 0),
QString specifier = "QLabel ");
QString specifier = "QLabel ",
bool once = false);
void listAddClicked(QListWidget *list, QWidget *newWidget,
QPushButton *addButton = nullptr,
QMetaObject::Connection *addHighlight = nullptr);
@@ -58,8 +58,7 @@ bool DisplayMessage(const QString &msg, bool question = false);
void addSelectionEntry(QComboBox *sel, const char *description,
bool selectable = false, const char *tooltip = "");
void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
bool addSelect = true,
bool selectable = false);
bool addAny = false);
void populateWindowSelection(QComboBox *sel, bool addSelect = true);
void populateAudioSelection(QComboBox *sel, bool addSelect = true);
void populateVideoSelection(QComboBox *sel, bool addSelect = true);

View File

@@ -524,7 +524,7 @@ static std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_NumpadEnter, XK_KP_Enter},
};
void PressKeys(const std::vector<HotkeyType> keys)
void PressKeys(const std::vector<HotkeyType> keys, int duration)
{
if (!canSimulateKeyPresses) {
return;
@@ -545,7 +545,7 @@ void PressKeys(const std::vector<HotkeyType> keys)
}
XFlush(display);
std::this_thread::sleep_for(std::chrono::milliseconds(300));
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
// Release keys
for (auto &key : keys) {

View File

@@ -134,12 +134,22 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
return;
}
MacroActionSwitchScene temp;
std::string id = temp.GetId();
std::string id;
if (idx - 1 >= 0) {
id = macro->Actions().at(idx - 1)->GetId();
} else {
MacroActionSwitchScene temp;
id = temp.GetId();
}
std::lock_guard<std::mutex> lock(switcher->m);
auto action = macro->Actions().emplace(macro->Actions().begin() + idx,
MacroActionFactory::Create(id));
macro->Actions().emplace(macro->Actions().begin() + idx,
MacroActionFactory::Create(id));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Actions().at(idx - 1)->Save(data);
macro->Actions().at(idx)->Load(data);
obs_data_release(data);
}
macro->UpdateActionIndices();
// All entry pointers in existing edit widgets after the new entry will
@@ -148,6 +158,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightAction(idx);
}
void AdvSceneSwitcher::on_actionAdd_clicked()

View File

@@ -86,9 +86,7 @@ MacroActionFileEdit::MacroActionFileEdit(
QWidget *parent, std::shared_ptr<MacroActionFile> entryData)
: QWidget(parent)
{
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_filePath = new FileSelection(FileSelection::Type::WRITE);
_text = new QPlainTextEdit();
_actions = new QComboBox();
@@ -96,10 +94,8 @@ MacroActionFileEdit::MacroActionFileEdit(
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_text, SIGNAL(textChanged()), this,
SLOT(TextChanged()));
;
@@ -107,7 +103,6 @@ MacroActionFileEdit::MacroActionFileEdit(
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
{"{{matchText}}", _text},
{"{{actions}}", _actions},
};
@@ -131,41 +126,22 @@ void MacroActionFileEdit::UpdateEntryData()
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_filePath->setText(QString::fromStdString(_entryData->_file));
_filePath->SetPath(QString::fromStdString(_entryData->_file));
_text->setPlainText(QString::fromStdString(_entryData->_text));
}
void MacroActionFileEdit::FilePathChanged()
void MacroActionFileEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_file = _filePath->text().toUtf8().constData();
_entryData->_file = text.toUtf8().constData();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionFileEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path = QFileDialog::getSaveFileName(
this,
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectWrite")),
QDir::currentPath(),
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
FilePathChanged();
}
void MacroActionFileEdit::TextChanged()
{
if (_loading || !_entryData) {

View File

@@ -1,18 +1,196 @@
#include "headers/macro-action-hotkey.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#include <obs-interaction.h>
const std::string MacroActionHotkey::id = "hotkey";
// TODO:
// I can't seem to get this to work so drop support for this functionality
// on MacOS
#if !__APPLE__
bool MacroActionHotkey::_registered = MacroActionFactory::Register(
MacroActionHotkey::id,
{MacroActionHotkey::Create, MacroActionHotkeyEdit::Create,
"AdvSceneSwitcher.action.hotkey"});
#endif
static std::unordered_map<HotkeyType, long> keyTable = {
// Chars
{HotkeyType::Key_A, OBS_KEY_A},
{HotkeyType::Key_B, OBS_KEY_B},
{HotkeyType::Key_C, OBS_KEY_C},
{HotkeyType::Key_D, OBS_KEY_D},
{HotkeyType::Key_E, OBS_KEY_E},
{HotkeyType::Key_F, OBS_KEY_F},
{HotkeyType::Key_G, OBS_KEY_G},
{HotkeyType::Key_H, OBS_KEY_H},
{HotkeyType::Key_I, OBS_KEY_I},
{HotkeyType::Key_J, OBS_KEY_J},
{HotkeyType::Key_K, OBS_KEY_K},
{HotkeyType::Key_L, OBS_KEY_L},
{HotkeyType::Key_M, OBS_KEY_M},
{HotkeyType::Key_N, OBS_KEY_N},
{HotkeyType::Key_O, OBS_KEY_O},
{HotkeyType::Key_P, OBS_KEY_P},
{HotkeyType::Key_Q, OBS_KEY_Q},
{HotkeyType::Key_R, OBS_KEY_R},
{HotkeyType::Key_S, OBS_KEY_S},
{HotkeyType::Key_T, OBS_KEY_T},
{HotkeyType::Key_U, OBS_KEY_U},
{HotkeyType::Key_V, OBS_KEY_V},
{HotkeyType::Key_W, OBS_KEY_W},
{HotkeyType::Key_X, OBS_KEY_X},
{HotkeyType::Key_Y, OBS_KEY_Y},
{HotkeyType::Key_Z, OBS_KEY_Z},
// Numbers
{HotkeyType::Key_0, OBS_KEY_0},
{HotkeyType::Key_1, OBS_KEY_1},
{HotkeyType::Key_2, OBS_KEY_2},
{HotkeyType::Key_3, OBS_KEY_3},
{HotkeyType::Key_4, OBS_KEY_4},
{HotkeyType::Key_5, OBS_KEY_5},
{HotkeyType::Key_6, OBS_KEY_6},
{HotkeyType::Key_7, OBS_KEY_7},
{HotkeyType::Key_8, OBS_KEY_8},
{HotkeyType::Key_9, OBS_KEY_9},
{HotkeyType::Key_F1, OBS_KEY_F1},
{HotkeyType::Key_F2, OBS_KEY_F2},
{HotkeyType::Key_F3, OBS_KEY_F3},
{HotkeyType::Key_F4, OBS_KEY_F4},
{HotkeyType::Key_F5, OBS_KEY_F5},
{HotkeyType::Key_F6, OBS_KEY_F6},
{HotkeyType::Key_F7, OBS_KEY_F7},
{HotkeyType::Key_F8, OBS_KEY_F8},
{HotkeyType::Key_F9, OBS_KEY_F9},
{HotkeyType::Key_F10, OBS_KEY_F10},
{HotkeyType::Key_F11, OBS_KEY_F11},
{HotkeyType::Key_F12, OBS_KEY_F12},
{HotkeyType::Key_F13, OBS_KEY_F13},
{HotkeyType::Key_F14, OBS_KEY_F14},
{HotkeyType::Key_F15, OBS_KEY_F15},
{HotkeyType::Key_F16, OBS_KEY_F16},
{HotkeyType::Key_F17, OBS_KEY_F17},
{HotkeyType::Key_F18, OBS_KEY_F18},
{HotkeyType::Key_F19, OBS_KEY_F19},
{HotkeyType::Key_F20, OBS_KEY_F20},
{HotkeyType::Key_F21, OBS_KEY_F21},
{HotkeyType::Key_F22, OBS_KEY_F22},
{HotkeyType::Key_F23, OBS_KEY_F23},
{HotkeyType::Key_F24, OBS_KEY_F24},
{HotkeyType::Key_Escape, OBS_KEY_ESCAPE},
{HotkeyType::Key_Space, OBS_KEY_SPACE},
{HotkeyType::Key_Return, OBS_KEY_RETURN},
{HotkeyType::Key_Backspace, OBS_KEY_BACKSPACE},
{HotkeyType::Key_Tab, OBS_KEY_TAB},
{HotkeyType::Key_Shift_L, OBS_KEY_SHIFT},
{HotkeyType::Key_Shift_R, OBS_KEY_SHIFT},
{HotkeyType::Key_Control_L, OBS_KEY_CONTROL},
{HotkeyType::Key_Control_R, OBS_KEY_CONTROL},
{HotkeyType::Key_Alt_L, OBS_KEY_ALT},
{HotkeyType::Key_Alt_R, OBS_KEY_ALT},
{HotkeyType::Key_Win_L, OBS_KEY_META},
{HotkeyType::Key_Win_R, OBS_KEY_META},
{HotkeyType::Key_Apps, OBS_KEY_APPLICATIONLEFT},
{HotkeyType::Key_CapsLock, OBS_KEY_CAPSLOCK},
{HotkeyType::Key_NumLock, OBS_KEY_NUMLOCK},
{HotkeyType::Key_ScrollLock, OBS_KEY_SCROLLLOCK},
{HotkeyType::Key_PrintScreen, OBS_KEY_PRINT},
{HotkeyType::Key_Pause, OBS_KEY_PAUSE},
{HotkeyType::Key_Insert, OBS_KEY_INSERT},
{HotkeyType::Key_Delete, OBS_KEY_DELETE},
{HotkeyType::Key_PageUP, OBS_KEY_PAGEUP},
{HotkeyType::Key_PageDown, OBS_KEY_PAGEDOWN},
{HotkeyType::Key_Home, OBS_KEY_HOME},
{HotkeyType::Key_End, OBS_KEY_END},
{HotkeyType::Key_Left, OBS_KEY_LEFT},
{HotkeyType::Key_Up, OBS_KEY_UP},
{HotkeyType::Key_Right, OBS_KEY_RIGHT},
{HotkeyType::Key_Down, OBS_KEY_DOWN},
{HotkeyType::Key_Numpad0, OBS_KEY_NUM0},
{HotkeyType::Key_Numpad1, OBS_KEY_NUM1},
{HotkeyType::Key_Numpad2, OBS_KEY_NUM2},
{HotkeyType::Key_Numpad3, OBS_KEY_NUM3},
{HotkeyType::Key_Numpad4, OBS_KEY_NUM4},
{HotkeyType::Key_Numpad5, OBS_KEY_NUM5},
{HotkeyType::Key_Numpad6, OBS_KEY_NUM6},
{HotkeyType::Key_Numpad7, OBS_KEY_NUM7},
{HotkeyType::Key_Numpad8, OBS_KEY_NUM8},
{HotkeyType::Key_Numpad9, OBS_KEY_NUM9},
{HotkeyType::Key_NumpadAdd, OBS_KEY_NUMPLUS},
{HotkeyType::Key_NumpadSubtract, OBS_KEY_NUMMINUS},
{HotkeyType::Key_NumpadMultiply, OBS_KEY_NUMASTERISK},
{HotkeyType::Key_NumpadDivide, OBS_KEY_NUMSLASH},
{HotkeyType::Key_NumpadDecimal, OBS_KEY_NUMPERIOD},
//{HotkeyType::Key_NumpadEnter, ???},
};
obs_key_combination keysToOBSKeycombo(const std::vector<HotkeyType> &keys)
{
obs_key_combination combo{};
auto it = keyTable.find(keys.back());
if (it != keyTable.end()) {
combo.key = (obs_key_t)it->second;
}
if (keys.size() == 1) {
return combo;
}
for (int i = 0; i < keys.size() - 1; i++) {
switch (keys[i]) {
case HotkeyType::Key_Shift_L:
combo.modifiers |= INTERACT_SHIFT_KEY;
break;
case HotkeyType::Key_Shift_R:
combo.modifiers |= INTERACT_SHIFT_KEY;
break;
case HotkeyType::Key_Control_L:
combo.modifiers |= INTERACT_CONTROL_KEY;
break;
case HotkeyType::Key_Control_R:
combo.modifiers |= INTERACT_CONTROL_KEY;
break;
case HotkeyType::Key_Alt_L:
combo.modifiers |= INTERACT_ALT_KEY;
break;
case HotkeyType::Key_Alt_R:
combo.modifiers |= INTERACT_ALT_KEY;
break;
case HotkeyType::Key_Win_L:
combo.modifiers |= INTERACT_COMMAND_KEY;
break;
case HotkeyType::Key_Win_R:
combo.modifiers |= INTERACT_COMMAND_KEY;
break;
case HotkeyType::Key_CapsLock:
combo.modifiers |= INTERACT_CAPS_KEY;
break;
default:
break;
}
}
return combo;
}
void InjectKeys(const std::vector<HotkeyType> &keys, int duration)
{
auto combo = keysToOBSKeycombo(keys);
if (obs_key_combination_is_empty(combo)) {
return;
}
// I am not sure why this is necessary
obs_hotkey_inject_event(combo, false);
obs_hotkey_inject_event(combo, true);
std::this_thread::sleep_for(std::chrono::milliseconds(duration));
obs_hotkey_inject_event(combo, false);
}
bool MacroActionHotkey::PerformAction()
{
@@ -35,13 +213,25 @@ bool MacroActionHotkey::PerformAction()
if (_rightAlt) {
keys.push_back(HotkeyType::Key_Alt_R);
}
if (_leftMeta) {
keys.push_back(HotkeyType::Key_Win_L);
}
if (_rightMeta) {
keys.push_back(HotkeyType::Key_Win_R);
}
if (_key != HotkeyType::Key_NoKey) {
keys.push_back(_key);
}
if (!keys.empty()) {
std::thread t([keys]() { PressKeys(keys); });
t.detach();
int dur = _duration;
if (_onlySendToObs) {
std::thread t([keys, dur]() { InjectKeys(keys, dur); });
t.detach();
} else {
std::thread t([keys, dur]() { PressKeys(keys, dur); });
t.detach();
}
}
return true;
@@ -64,6 +254,8 @@ bool MacroActionHotkey::Save(obs_data_t *obj)
obs_data_set_bool(obj, "right_alt", _rightAlt);
obs_data_set_bool(obj, "left_meta", _leftMeta);
obs_data_set_bool(obj, "right_meta", _rightMeta);
obs_data_set_int(obj, "duration", _duration);
obs_data_set_bool(obj, "onlyOBS", _onlySendToObs);
return true;
}
@@ -79,6 +271,8 @@ bool MacroActionHotkey::Load(obs_data_t *obj)
_rightAlt = obs_data_get_bool(obj, "right_alt");
_leftMeta = obs_data_get_bool(obj, "left_meta");
_rightMeta = obs_data_get_bool(obj, "right_meta");
_duration = obs_data_get_int(obj, "duration");
_onlySendToObs = obs_data_get_bool(obj, "onlyOBS");
return true;
}
@@ -213,6 +407,12 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
obs_module_text("AdvSceneSwitcher.action.hotkey.leftMeta"));
_rightMeta = new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.hotkey.rightMeta"));
_duration = new QSpinBox();
_duration->setMaximum(5000);
_onlySendToOBS = new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.hotkey.onlyOBS"));
_noKeyPressSimulationWarning = new QLabel(
obs_module_text("AdvSceneSwitcher.action.hotkey.disabled"));
populateKeySelection(_keys);
@@ -234,10 +434,15 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
SLOT(LMetaChanged(int)));
QWidget::connect(_rightMeta, SIGNAL(stateChanged(int)), this,
SLOT(RMetaChanged(int)));
QWidget::connect(_duration, SIGNAL(valueChanged(int)), this,
SLOT(DurationChanged(int)));
QWidget::connect(_onlySendToOBS, SIGNAL(stateChanged(int)), this,
SLOT(OnlySendToOBSChanged(int)));
QHBoxLayout *line1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{keys}}", _keys},
{"{{duration}}", _duration},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.hotkey.entry"),
line1Layout, widgetPlaceholders);
@@ -256,11 +461,8 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
if (!canSimulateKeyPresses) {
mainLayout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.action.hotkey.disabled")));
}
mainLayout->addWidget(_onlySendToOBS);
mainLayout->addWidget(_noKeyPressSimulationWarning);
setLayout(mainLayout);
@@ -269,6 +471,12 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
_loading = false;
}
void MacroActionHotkeyEdit::SetWarningVisibility()
{
_noKeyPressSimulationWarning->setVisible(!_entryData->_onlySendToObs &&
!canSimulateKeyPresses);
}
void MacroActionHotkeyEdit::UpdateEntryData()
{
if (!_entryData) {
@@ -284,6 +492,9 @@ void MacroActionHotkeyEdit::UpdateEntryData()
_rightAlt->setChecked(_entryData->_rightAlt);
_leftMeta->setChecked(_entryData->_leftMeta);
_rightMeta->setChecked(_entryData->_rightMeta);
_duration->setValue(_entryData->_duration);
_onlySendToOBS->setChecked(_entryData->_onlySendToObs);
SetWarningVisibility();
}
void MacroActionHotkeyEdit::LShiftChanged(int state)
@@ -366,6 +577,27 @@ void MacroActionHotkeyEdit::RMetaChanged(int state)
_entryData->_rightMeta = state;
}
void MacroActionHotkeyEdit::DurationChanged(int ms)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration = ms;
}
void MacroActionHotkeyEdit::OnlySendToOBSChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_onlySendToObs = state;
SetWarningVisibility();
}
void MacroActionHotkeyEdit::KeyChanged(int key)
{
if (_loading || !_entryData) {

View File

@@ -0,0 +1,87 @@
#include "headers/macro-action-preview-scene.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const std::string MacroActionPreviewScene::id = "preview_scene";
bool MacroActionPreviewScene::_registered = MacroActionFactory::Register(
MacroActionPreviewScene::id,
{MacroActionPreviewScene::Create, MacroActionPreviewSceneEdit::Create,
"AdvSceneSwitcher.action.previewScene"});
bool MacroActionPreviewScene::PerformAction()
{
auto s = obs_weak_source_get_source(_scene.GetScene());
obs_frontend_set_current_preview_scene(s);
obs_source_release(s);
return true;
}
void MacroActionPreviewScene::LogAction()
{
vblog(LOG_INFO, "set preview scene to \"%s\"",
_scene.ToString().c_str());
}
bool MacroActionPreviewScene::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_scene.Save(obj);
return true;
}
bool MacroActionPreviewScene::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_scene.Load(obj);
return true;
}
std::string MacroActionPreviewScene::GetShortDesc()
{
return _scene.ToString();
}
MacroActionPreviewSceneEdit::MacroActionPreviewSceneEdit(
QWidget *parent, std::shared_ptr<MacroActionPreviewScene> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, false);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.previewScene.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionPreviewSceneEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_scenes->SetScene(_entryData->_scene);
}
void MacroActionPreviewSceneEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scene = s;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

View File

@@ -1,3 +1,5 @@
#ifdef REPLAYBUFFER_SUPPORTED
#include "headers/macro-action-replay-buffer.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
@@ -119,3 +121,5 @@ void MacroActionReplayBufferEdit::ActionChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<ReplayBufferAction>(value);
}
#endif

View File

@@ -65,9 +65,7 @@ MacroActionRunEdit::MacroActionRunEdit(
QWidget *parent, std::shared_ptr<MacroActionRun> entryData)
: QWidget(parent)
{
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_filePath = new FileSelection();
_argList = new QListWidget();
_addArg = new QPushButton();
_addArg->setMaximumSize(QSize(22, 22));
@@ -90,10 +88,8 @@ MacroActionRunEdit::MacroActionRunEdit(
"themeID", QVariant(QString::fromUtf8("downArrowIconSmall")));
_argDown->setFlat(true);
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_addArg, SIGNAL(clicked()), this, SLOT(AddArg()));
QWidget::connect(_removeArg, SIGNAL(clicked()), this,
SLOT(RemoveArg()));
@@ -103,7 +99,6 @@ MacroActionRunEdit::MacroActionRunEdit(
auto *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.run.entry"),
entryLayout, widgetPlaceholders);
@@ -137,40 +132,25 @@ void MacroActionRunEdit::UpdateEntryData()
if (!_entryData) {
return;
}
_filePath->setText(QString::fromStdString(_entryData->_path));
_filePath->SetPath(QString::fromStdString(_entryData->_path));
for (auto &arg : _entryData->_args) {
QListWidgetItem *item = new QListWidgetItem(arg, _argList);
item->setData(Qt::UserRole, arg);
}
}
void MacroActionRunEdit::FilePathChanged()
void MacroActionRunEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_path = _filePath->text().toUtf8().constData();
_entryData->_path = text.toUtf8().constData();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionRunEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path = QFileDialog::getOpenFileName(this);
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
FilePathChanged();
}
void MacroActionRunEdit::AddArg()
{
if (_loading || !_entryData) {

View File

@@ -0,0 +1,46 @@
#include "headers/macro-action-scene-swap.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const std::string MacroActionSceneSwap::id = "scene_swap";
bool MacroActionSceneSwap::_registered = MacroActionFactory::Register(
MacroActionSceneSwap::id,
{MacroActionSceneSwap::Create, MacroActionSceneSwapEdit::Create,
"AdvSceneSwitcher.action.sceneSwap"});
bool MacroActionSceneSwap::PerformAction()
{
obs_frontend_preview_program_trigger_transition();
return true;
}
void MacroActionSceneSwap::LogAction()
{
vblog(LOG_INFO, "trigger preview to active scene transition");
}
bool MacroActionSceneSwap::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
return true;
}
bool MacroActionSceneSwap::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
return true;
}
MacroActionSceneSwapEdit::MacroActionSceneSwapEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneSwap> entryData)
: QWidget(parent)
{
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.SceneSwap.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
}

View File

@@ -10,51 +10,6 @@ bool MacroActionSceneTransform::_registered = MacroActionFactory::Register(
MacroActionSceneTransformEdit::Create,
"AdvSceneSwitcher.action.sceneTransform"});
static void load_transform_state(obs_data_t *obj,
struct obs_transform_info &info,
struct obs_sceneitem_crop &crop)
{
obs_data_get_vec2(obj, "pos", &info.pos);
obs_data_get_vec2(obj, "scale", &info.scale);
info.rot = (float)obs_data_get_double(obj, "rot");
info.alignment = (uint32_t)obs_data_get_int(obj, "alignment");
info.bounds_type =
(enum obs_bounds_type)obs_data_get_int(obj, "bounds_type");
info.bounds_alignment =
(uint32_t)obs_data_get_int(obj, "bounds_alignment");
obs_data_get_vec2(obj, "bounds", &info.bounds);
crop.top = (int)obs_data_get_int(obj, "top");
crop.bottom = (int)obs_data_get_int(obj, "bottom");
crop.left = (int)obs_data_get_int(obj, "left");
crop.right = (int)obs_data_get_int(obj, "right");
}
bool save_transform_state(obs_data_t *obj, struct obs_transform_info &info,
struct obs_sceneitem_crop &crop)
{
struct vec2 pos = info.pos;
struct vec2 scale = info.scale;
float rot = info.rot;
uint32_t alignment = info.alignment;
uint32_t bounds_type = info.bounds_type;
uint32_t bounds_alignment = info.bounds_alignment;
struct vec2 bounds = info.bounds;
obs_data_set_vec2(obj, "pos", &pos);
obs_data_set_vec2(obj, "scale", &scale);
obs_data_set_double(obj, "rot", rot);
obs_data_set_int(obj, "alignment", alignment);
obs_data_set_int(obj, "bounds_type", bounds_type);
obs_data_set_vec2(obj, "bounds", &bounds);
obs_data_set_int(obj, "bounds_alignment", bounds_alignment);
obs_data_set_int(obj, "top", crop.top);
obs_data_set_int(obj, "bottom", crop.bottom);
obs_data_set_int(obj, "left", crop.left);
obs_data_set_int(obj, "right", crop.right);
return true;
}
bool MacroActionSceneTransform::PerformAction()
{
auto s = obs_weak_source_get_source(_scene.GetScene(false));
@@ -63,24 +18,10 @@ bool MacroActionSceneTransform::PerformAction()
auto items = getSceneItemsWithName(scene, name);
for (auto &item : items) {
// Merge new settings with current settings
struct obs_transform_info info;
struct obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &info);
obs_sceneitem_get_crop(item, &crop);
auto temp = obs_data_create();
save_transform_state(temp, info, crop);
auto temp2 = obs_data_create();
save_transform_state(temp, _info, _crop);
obs_data_apply(temp, temp2);
load_transform_state(temp, info, crop);
obs_data_release(temp);
obs_data_release(temp2);
// Apply settings
obs_sceneitem_defer_update_begin(item);
obs_sceneitem_set_info(item, &info);
obs_sceneitem_set_crop(item, &crop);
obs_sceneitem_set_info(item, &_info);
obs_sceneitem_set_crop(item, &_crop);
obs_sceneitem_defer_update_end(item);
obs_sceneitem_release(item);
@@ -102,7 +43,7 @@ bool MacroActionSceneTransform::Save(obs_data_t *obj)
MacroAction::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
save_transform_state(obj, _info, _crop);
saveTransformState(obj, _info, _crop);
return true;
}
@@ -112,7 +53,7 @@ bool MacroActionSceneTransform::Load(obs_data_t *obj)
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
load_transform_state(obj, _info, _crop);
loadTransformState(obj, _info, _crop);
return true;
}
@@ -127,7 +68,7 @@ std::string MacroActionSceneTransform::GetShortDesc()
std::string MacroActionSceneTransform::GetSettings()
{
auto data = obs_data_create();
save_transform_state(data, _info, _crop);
saveTransformState(data, _info, _crop);
std::string json = obs_data_get_json(data);
obs_data_release(data);
return json;
@@ -139,7 +80,22 @@ void MacroActionSceneTransform::SetSettings(std::string &settings)
if (!data) {
return;
}
load_transform_state(data, _info, _crop);
loadTransformState(data, _info, _crop);
if (obs_data_has_user_value(data, "size")) {
auto obj = obs_data_get_obj(data, "size");
auto source = obs_weak_source_get_source(_source);
if (double h = obs_data_get_double(obj, "height")) {
_info.scale.y =
h / double(obs_source_get_height(source));
}
if (double w = obs_data_get_double(obj, "width")) {
_info.scale.x =
w / double(obs_source_get_width(source));
}
obs_data_release(obj);
obs_source_release(source);
}
obs_data_release(data);
}
@@ -247,16 +203,7 @@ void MacroActionSceneTransformEdit::GetSettingsClicked()
return;
}
struct obs_transform_info info;
struct obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &info);
obs_sceneitem_get_crop(item, &crop);
auto data = obs_data_create();
save_transform_state(data, info, crop);
auto json = obs_data_get_json(data);
_settings->setPlainText(formatJsonString(json));
obs_data_release(data);
_settings->setPlainText(formatJsonString(getSceneItemTransform(item)));
}
void MacroActionSceneTransformEdit::SettingsChanged()

View File

@@ -0,0 +1,169 @@
#include "headers/macro-action-transition.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const std::string MacroActionTransition::id = "transition";
bool MacroActionTransition::_registered = MacroActionFactory::Register(
MacroActionTransition::id,
{MacroActionTransition::Create, MacroActionTransitionEdit::Create,
"AdvSceneSwitcher.action.transition"});
bool MacroActionTransition::PerformAction()
{
if (_setType) {
auto t =
obs_weak_source_get_source(_transition.GetTransition());
obs_frontend_set_current_transition(t);
obs_source_release(t);
}
if (_setDuration) {
obs_frontend_set_transition_duration(_duration.seconds * 1000);
}
return true;
}
void MacroActionTransition::LogAction()
{
if (_setDuration) {
vblog(LOG_INFO, "set transition duration to %s",
_duration.ToString().c_str());
}
if (_setType) {
vblog(LOG_INFO, "set transition type to \"%s\"",
_transition.ToString().c_str());
}
}
bool MacroActionTransition::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
_duration.Save(obj);
_transition.Save(obj);
obs_data_set_bool(obj, "setDuration", _setDuration);
obs_data_set_bool(obj, "setType", _setType);
return true;
}
bool MacroActionTransition::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_duration.Load(obj);
_transition.Load(obj);
_setDuration = obs_data_get_bool(obj, "setDuration");
_setType = obs_data_get_bool(obj, "setType");
return true;
}
std::string MacroActionTransition::GetShortDesc()
{
return _transition.ToString();
}
MacroActionTransitionEdit::MacroActionTransitionEdit(
QWidget *parent, std::shared_ptr<MacroActionTransition> entryData)
: QWidget(parent)
{
_transitions = new TransitionSelectionWidget(this, false);
_duration = new DurationSelection(this, false);
_setType = new QCheckBox();
_setDuration = new QCheckBox();
QWidget::connect(_transitions,
SIGNAL(TransitionChanged(const TransitionSelection &)),
this,
SLOT(TransitionChanged(const TransitionSelection &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_setType, SIGNAL(stateChanged(int)), this,
SLOT(SetTypeChanged(int)));
QWidget::connect(_setDuration, SIGNAL(stateChanged(int)), this,
SLOT(SetDurationChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
{"{{setType}}", _setType},
{"{{setDuration}}", _setDuration},
};
_typeLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line1"),
_typeLayout, widgetPlaceholders);
_durationLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line2"),
_durationLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_typeLayout);
mainLayout->addLayout(_durationLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionTransitionEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_setDuration->setChecked(_entryData->_setDuration);
_duration->SetDuration(_entryData->_duration);
_setType->setChecked(_entryData->_setType);
_transitions->SetTransition(_entryData->_transition);
_transitions->setEnabled(_entryData->_setType);
_duration->setEnabled(_entryData->_setDuration);
}
void MacroActionTransitionEdit::TransitionChanged(const TransitionSelection &t)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_transition = t;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionTransitionEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroActionTransitionEdit::SetTypeChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_setType = state;
_transitions->setEnabled(state);
if (state) {
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
} else {
emit HeaderInfoChanged("");
}
}
void MacroActionTransitionEdit::SetDurationChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_setDuration = state;
_duration->setEnabled(state);
}

View File

@@ -1,3 +1,5 @@
#ifdef VCAM_SUPPORTED
#include "headers/macro-action-virtual-cam.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
@@ -107,3 +109,5 @@ void MacroActionVCamEdit::ActionChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<VCamAction>(value);
}
#endif

View File

@@ -254,14 +254,24 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
return;
}
MacroConditionScene temp;
std::string id = temp.GetId();
std::string id;
if (idx - 1 >= 0) {
id = macro->Conditions().at(idx - 1)->GetId();
} else {
MacroConditionScene temp;
id = temp.GetId();
}
std::lock_guard<std::mutex> lock(switcher->m);
bool root = idx == 0;
auto cond =
macro->Conditions().emplace(macro->Conditions().begin() + idx,
MacroConditionFactory::Create(id));
if (idx - 1 >= 0) {
auto data = obs_data_create();
macro->Conditions().at(idx - 1)->Save(data);
macro->Conditions().at(idx)->Load(data);
obs_data_release(data);
}
auto logic = root ? LogicType::ROOT_NONE : LogicType::NONE;
(*cond)->SetLogicType(logic);
macro->UpdateConditionIndices();
@@ -272,6 +282,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
//
// If performance should become a concern this has to be revisited.
SetEditMacro(*macro);
HighlightCondition(idx);
}
void AdvSceneSwitcher::on_conditionAdd_clicked()

View File

@@ -137,10 +137,7 @@ MacroConditionFileEdit::MacroConditionFileEdit(
: QWidget(parent)
{
_fileType = new QComboBox();
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_browseButton->setStyleSheet("border:1px solid gray;");
_filePath = new FileSelection();
_matchText = new QPlainTextEdit();
_useRegex = new QCheckBox(
obs_module_text("AdvSceneSwitcher.fileTab.useRegExp"));
@@ -151,10 +148,8 @@ MacroConditionFileEdit::MacroConditionFileEdit(
QWidget::connect(_fileType, SIGNAL(currentIndexChanged(int)), this,
SLOT(FileTypeChanged(int)));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(PathChanged(const QString &)));
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged()));
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
@@ -170,7 +165,6 @@ MacroConditionFileEdit::MacroConditionFileEdit(
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fileType}}", _fileType},
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
{"{{matchText}}", _matchText},
{"{{useRegex}}", _useRegex},
{"{{checkModificationDate}}", _checkModificationDate},
@@ -209,7 +203,7 @@ void MacroConditionFileEdit::UpdateEntryData()
_fileType->setCurrentIndex(static_cast<int>(_entryData->_fileType));
_filePath->setText(QString::fromStdString(_entryData->_file));
_filePath->SetPath(QString::fromStdString(_entryData->_file));
_matchText->setPlainText(QString::fromStdString(_entryData->_text));
_useRegex->setChecked(_entryData->_useRegex);
_checkModificationDate->setChecked(_entryData->_useTime);
@@ -225,10 +219,10 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
FileType type = static_cast<FileType>(index);
if (type == FileType::LOCAL) {
_browseButton->setDisabled(false);
_filePath->Button()->setDisabled(false);
_checkModificationDate->setDisabled(false);
} else {
_browseButton->setDisabled(true);
_filePath->Button()->setDisabled(true);
_checkModificationDate->setDisabled(true);
}
@@ -236,37 +230,18 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
_entryData->_fileType = type;
}
void MacroConditionFileEdit::FilePathChanged()
void MacroConditionFileEdit::PathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_file = _filePath->text().toUtf8().constData();
_entryData->_file = text.toUtf8().constData();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionFileEdit::BrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path = QFileDialog::getOpenFileName(
this,
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectRead")),
QDir::currentPath(),
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
if (path.isEmpty()) {
return;
}
_filePath->setText(path);
FilePathChanged();
}
void MacroConditionFileEdit::MatchTextChanged()
{
if (_loading || !_entryData) {

View File

@@ -191,8 +191,11 @@ void MacroConditionFilterEdit::GetSettingsClicked()
return;
}
_settings->setPlainText(
formatJsonString(getSourceSettings(_entryData->_filter)));
QString json = formatJsonString(getSourceSettings(_entryData->_filter));
if (_entryData->_regex) {
json = escapeForRegex(json);
}
_settings->setPlainText(json);
}
void MacroConditionFilterEdit::SettingsChanged()

View File

@@ -1,3 +1,5 @@
#ifdef REPLAYBUFFER_SUPPORTED
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-replay-buffer.hpp"
#include "headers/utility.hpp"
@@ -106,3 +108,5 @@ void MacroConditionReplayBufferEdit::UpdateEntryData()
_state->setCurrentIndex(static_cast<int>(_entryData->_state));
}
#endif

View File

@@ -3,8 +3,6 @@
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include <regex>
const std::string MacroConditionSceneOrder::id = "scene_order";
bool MacroConditionSceneOrder::_registered = MacroConditionFactory::Register(

View File

@@ -0,0 +1,205 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-scene-transform.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const std::string MacroConditionSceneTransform::id = "scene_transform";
bool MacroConditionSceneTransform::_registered =
MacroConditionFactory::Register(
MacroConditionSceneTransform::id,
{MacroConditionSceneTransform::Create,
MacroConditionSceneTransformEdit::Create,
"AdvSceneSwitcher.condition.sceneTransform"});
bool MacroConditionSceneTransform::CheckCondition()
{
bool ret = false;
auto s = obs_weak_source_get_source(_scene.GetScene(false));
auto scene = obs_scene_from_source(s);
auto name = GetWeakSourceName(_source);
auto items = getSceneItemsWithName(scene, name);
for (auto &item : items) {
auto json = getSceneItemTransform(item);
if (matchJson(json, _settings, _regex)) {
ret = true;
}
obs_sceneitem_release(item);
}
obs_source_release(s);
return ret;
}
bool MacroConditionSceneTransform::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
_scene.Save(obj);
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
obs_data_set_string(obj, "settings", _settings.c_str());
obs_data_set_bool(obj, "regex", _regex);
return true;
}
bool MacroConditionSceneTransform::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_scene.Load(obj);
const char *sourceName = obs_data_get_string(obj, "source");
_source = GetWeakSourceByName(sourceName);
_settings = obs_data_get_string(obj, "settings");
_regex = obs_data_get_bool(obj, "regex");
return true;
}
std::string MacroConditionSceneTransform::GetShortDesc()
{
if (_source) {
return _scene.ToString() + " - " + GetWeakSourceName(_source);
}
return "";
}
MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSceneTransform> entryData)
: QWidget(parent)
{
_scenes = new SceneSelectionWidget(window(), false, false, true);
_sources = new QComboBox();
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.getTransform"));
_settings = new QPlainTextEdit();
_regex = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.regex"));
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(_getSettings, SIGNAL(clicked()), this,
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
SLOT(RegexChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
{"{{settings}}", _settings}, {"{{getSettings}}", _getSettings},
{"{{regex}}", _regex},
};
QHBoxLayout *line1Layout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line1"),
line1Layout, widgetPlaceholders);
QHBoxLayout *line2Layout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line2"),
line2Layout, widgetPlaceholders, false);
QHBoxLayout *line3Layout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.sceneTransform.entry.line3"),
line3Layout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionSceneTransformEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_scenes->SetScene(_entryData->_scene);
populateSceneItemSelection(_sources, _entryData->_scene);
_sources->setCurrentText(
GetWeakSourceName(_entryData->_source).c_str());
_regex->setChecked(_entryData->_regex);
if (_entryData->_source) {
_settings->setPlainText(
QString::fromStdString(_entryData->_settings));
}
}
void MacroConditionSceneTransformEdit::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);
}
void MacroConditionSceneTransformEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = GetWeakSourceByQString(text);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionSceneTransformEdit::GetSettingsClicked()
{
if (_loading || !_entryData || !_entryData->_scene.GetScene(false) ||
!_entryData->_source) {
return;
}
auto s = obs_weak_source_get_source(_entryData->_scene.GetScene(false));
auto scene = obs_scene_from_source(s);
auto name = GetWeakSourceName(_entryData->_source);
auto item = obs_scene_find_source_recursive(scene, name.c_str());
obs_source_release(s);
if (!item) {
return;
}
QString json = formatJsonString(getSceneItemTransform(item));
if (_entryData->_regex) {
json = escapeForRegex(json);
}
_settings->setPlainText(json);
}
void MacroConditionSceneTransformEdit::SettingsChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_settings = _settings->toPlainText().toStdString();
}
void MacroConditionSceneTransformEdit::RegexChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_regex = state;
}

View File

@@ -13,18 +13,33 @@ bool MacroConditionScene::_registered = MacroConditionFactory::Register(
static std::map<SceneType, std::string> sceneTypes = {
{SceneType::CURRENT, "AdvSceneSwitcher.condition.scene.type.current"},
{SceneType::PREVIOUS, "AdvSceneSwitcher.condition.scene.type.previous"},
{SceneType::CHANGED, "AdvSceneSwitcher.condition.scene.type.changed"},
{SceneType::NOTCHANGED,
"AdvSceneSwitcher.condition.scene.type.notChanged"},
};
bool MacroConditionScene::CheckCondition()
{
bool sceneMatch = false;
if (_type == SceneType::CURRENT) {
sceneMatch = switcher->currentScene == _scene.GetScene(false);
} else {
sceneMatch = switcher->previousScene == _scene.GetScene(false);
bool sceneChanged = _lastSceneChangeTime !=
switcher->lastSceneChangeTime;
if (sceneChanged) {
_lastSceneChangeTime = switcher->lastSceneChangeTime;
}
return sceneMatch;
switch (_type) {
case SceneType::CURRENT:
return switcher->currentScene == _scene.GetScene(false);
case SceneType::PREVIOUS:
return switcher->previousScene == _scene.GetScene(false);
case SceneType::CHANGED:
return sceneChanged;
case SceneType::NOTCHANGED:
return !sceneChanged;
default:
break;
}
return false;
}
bool MacroConditionScene::Save(obs_data_t *obj)
@@ -103,6 +118,13 @@ void MacroConditionSceneEdit::TypeChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<SceneType>(value);
SetWidgetVisibility();
}
void MacroConditionSceneEdit::SetWidgetVisibility()
{
_scenes->setVisible(_entryData->_type == SceneType::CURRENT ||
_entryData->_type == SceneType::PREVIOUS);
}
void MacroConditionSceneEdit::UpdateEntryData()
@@ -113,4 +135,5 @@ void MacroConditionSceneEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene);
_sceneType->setCurrentIndex(static_cast<int>(_entryData->_type));
SetWidgetVisibility();
}

View File

@@ -170,8 +170,11 @@ void MacroConditionSourceEdit::GetSettingsClicked()
return;
}
_settings->setPlainText(
formatJsonString(getSourceSettings(_entryData->_source)));
QString json = formatJsonString(getSourceSettings(_entryData->_source));
if (_entryData->_regex) {
json = escapeForRegex(json);
}
_settings->setPlainText(json);
}
void MacroConditionSourceEdit::SettingsChanged()

View File

@@ -0,0 +1,272 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-transition.hpp"
#include "headers/utility.hpp"
#include "headers/advanced-scene-switcher.hpp"
const std::string MacroConditionTransition::id = "transition";
bool MacroConditionTransition::_registered = MacroConditionFactory::Register(
MacroConditionTransition::id,
{MacroConditionTransition::Create, MacroConditionTransitionEdit::Create,
"AdvSceneSwitcher.condition.transition", false});
static std::map<TransitionCondition, std::string> filterConditionTypes = {
{TransitionCondition::CURRENT,
"AdvSceneSwitcher.condition.transition.type.current"},
{TransitionCondition::DURATION,
"AdvSceneSwitcher.condition.transition.type.duration"},
{TransitionCondition::STARTED,
"AdvSceneSwitcher.condition.transition.type.started"},
{TransitionCondition::ENDED,
"AdvSceneSwitcher.condition.transition.type.ended"},
};
bool isCurrentTransition(OBSWeakSource &t)
{
bool match;
auto tSource = obs_frontend_get_current_transition();
auto tWeakSource = obs_source_get_weak_source(tSource);
match = t == tWeakSource;
obs_weak_source_release(tWeakSource);
obs_source_release(tSource);
return match;
}
// Relies on the fact that switcher->currentScene will only be updated on event
// OBS_FRONTEND_EVENT_SCENE_CHANGED but obs_frontend_get_current_scene() will
// already return the scene to be transitioned to.
bool anySceneTransitionStarted()
{
bool ret;
auto currentSceneSrouce = obs_frontend_get_current_scene();
auto currentScene = obs_source_get_weak_source(currentSceneSrouce);
ret = switcher->currentScene != currentScene;
obs_weak_source_release(currentScene);
obs_source_release(currentSceneSrouce);
return ret;
}
bool MacroConditionTransition::CheckCondition()
{
bool anyTransitionEnded = _lastTransitionEndTime !=
switcher->lastTransitionEndTime;
bool transitionStarted = false;
bool transitionEnded = false;
if (_transition.GetType() == TransitionSelectionType::ANY) {
transitionStarted = anySceneTransitionStarted();
transitionEnded = anyTransitionEnded;
} else {
transitionStarted = _started;
transitionEnded = _ended;
}
bool ret = false;
switch (_condition) {
case TransitionCondition::CURRENT: {
auto transition = _transition.GetTransition();
ret = isCurrentTransition(transition);
break;
}
case TransitionCondition::DURATION:
ret = _duration.seconds * 1000 ==
obs_frontend_get_transition_duration();
break;
case TransitionCondition::STARTED:
ret = transitionStarted;
break;
case TransitionCondition::ENDED:
ret = transitionEnded;
break;
default:
break;
}
// Reset for next interval
if (_started) {
_started = false;
}
if (_ended) {
_ended = false;
}
if (anyTransitionEnded) {
_lastTransitionEndTime = switcher->lastTransitionEndTime;
}
return ret;
}
void MacroConditionTransition::ConnectToTransitionSignals()
{
auto source = obs_weak_source_get_source(_transition.GetTransition());
auto sh = obs_source_get_signal_handler(source);
signal_handler_connect(sh, "transition_start", TransitionStarted, this);
signal_handler_connect(sh, "transition_stop", TransitionEnded, this);
obs_source_release(source);
}
void MacroConditionTransition::DisconnectTransitionSignals()
{
auto source = obs_weak_source_get_source(_transition.GetTransition());
auto sh = obs_source_get_signal_handler(source);
signal_handler_disconnect(sh, "transition_start", TransitionStarted,
this);
signal_handler_disconnect(sh, "transition_stop", TransitionEnded, this);
obs_source_release(source);
}
void MacroConditionTransition::TransitionStarted(void *data, calldata_t *)
{
auto *transitionCond = static_cast<MacroConditionTransition *>(data);
transitionCond->_started = true;
}
void MacroConditionTransition::TransitionEnded(void *data, calldata_t *)
{
auto *transitionCond = static_cast<MacroConditionTransition *>(data);
transitionCond->_ended = true;
}
bool MacroConditionTransition::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_transition.Save(obj);
_duration.Save(obj);
return true;
}
bool MacroConditionTransition::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_condition = static_cast<TransitionCondition>(
obs_data_get_int(obj, "condition"));
_transition.Load(obj);
_duration.Load(obj);
ConnectToTransitionSignals();
return true;
}
std::string MacroConditionTransition::GetShortDesc()
{
return _transition.ToString();
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : filterConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionTransitionEdit::MacroConditionTransitionEdit(
QWidget *parent, std::shared_ptr<MacroConditionTransition> entryData)
: QWidget(parent)
{
_conditions = new QComboBox();
_transitions = new TransitionSelectionWidget(this, true, true);
_duration = new DurationSelection(this, false);
_durationSuffix = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.transition.durationSuffix"));
populateConditionSelection(_conditions);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_transitions,
SIGNAL(TransitionChanged(const TransitionSelection &)),
this,
SLOT(TransitionChanged(const TransitionSelection &)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{conditions}}", _conditions},
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
{"{{durationSuffix}}", _durationSuffix},
};
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.transition.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionTransitionEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition =
static_cast<TransitionCondition>(index);
}
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionTransitionEdit::TransitionChanged(
const TransitionSelection &t)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->DisconnectTransitionSignals();
_entryData->_transition = t;
_entryData->ConnectToTransitionSignals();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionTransitionEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroConditionTransitionEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_transitions->setVisible(
_entryData->_condition == TransitionCondition::CURRENT ||
_entryData->_condition == TransitionCondition::STARTED ||
_entryData->_condition == TransitionCondition::ENDED);
_duration->setVisible(_entryData->_condition ==
TransitionCondition::DURATION);
_durationSuffix->setVisible(_entryData->_condition ==
TransitionCondition::DURATION);
bool addCurrent = _entryData->_condition ==
TransitionCondition::DURATION;
bool addAny = _entryData->_condition == TransitionCondition::STARTED ||
_entryData->_condition == TransitionCondition::ENDED;
_transitions->Repopulate(addCurrent, addAny);
}
void MacroConditionTransitionEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
SetWidgetVisibility();
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_transitions->SetTransition(_entryData->_transition);
_duration->SetDuration(_entryData->_duration);
}

View File

@@ -26,26 +26,61 @@ static std::map<VideoCondition, std::string> conditionTypes = {
"AdvSceneSwitcher.condition.video.condition.hasChanged"},
{VideoCondition::NO_IMAGE,
"AdvSceneSwitcher.condition.video.condition.noImage"},
{VideoCondition::PATTERN,
"AdvSceneSwitcher.condition.video.condition.pattern"},
{VideoCondition::OBJECT,
"AdvSceneSwitcher.condition.video.condition.object"},
};
cv::CascadeClassifier initObjectCascade(std::string &path)
{
cv::CascadeClassifier cascade;
cascade.load(path);
return cascade;
}
bool requiresFileInput(VideoCondition t)
{
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER;
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER ||
t == VideoCondition::PATTERN;
}
bool MacroConditionVideo::CheckShouldBeSkipped()
{
if (_condition != VideoCondition::PATTERN &&
_condition != VideoCondition::OBJECT) {
return false;
}
if (_throttleEnabled) {
if (_runCount <= _throttleCount) {
_runCount++;
return true;
} else {
_runCount = 0;
}
}
return false;
}
bool MacroConditionVideo::CheckCondition()
{
bool match = false;
if (_screenshotData) {
if (_screenshotData->done) {
match = Compare();
if (CheckShouldBeSkipped()) {
return _lastMatchResult;
}
if (!requiresFileInput(_condition)) {
_matchImage = std::move(_screenshotData->image);
}
_screenshotData.reset(nullptr);
if (_screenshotData && _screenshotData->done) {
match = Compare();
_lastMatchResult = match;
if (!requiresFileInput(_condition)) {
_matchImage = std::move(_screenshotData->image);
}
_screenshotData.reset(nullptr);
} else {
match = _lastMatchResult;
}
GetScreenshot();
@@ -59,9 +94,30 @@ 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_double(obj, "threshold", _patternThreshold);
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
obs_data_set_double(obj, "scaleFactor", _scaleFactor);
obs_data_set_int(obj, "minNeighbors", _minNeighbors);
obs_data_set_int(obj, "minSizeX", _minSizeX);
obs_data_set_int(obj, "minSizeY", _minSizeY);
obs_data_set_int(obj, "maxSizeX", _maxSizeX);
obs_data_set_int(obj, "maxSizeY", _maxSizeY);
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
obs_data_set_int(obj, "throttleCount", _throttleCount);
return true;
}
bool isScaleFactorValid(double scaleFactor)
{
return scaleFactor > 1.;
}
bool isMinNeighborsValid(int minNeighbors)
{
return minNeighbors >= minMinNeighbors &&
minNeighbors <= maxMinNeighbors;
}
bool MacroConditionVideo::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
@@ -70,10 +126,31 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath");
_patternThreshold = obs_data_get_double(obj, "threshold");
_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)) {
_minNeighbors = minMinNeighbors;
}
_minSizeX = obs_data_get_int(obj, "minSizeX");
_minSizeY = obs_data_get_int(obj, "minSizeY");
_maxSizeX = obs_data_get_int(obj, "maxSizeX");
_maxSizeY = obs_data_get_int(obj, "maxSizeY");
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
_throttleCount = obs_data_get_int(obj, "throttleCount");
if (requiresFileInput(_condition)) {
(void)LoadImageFromFile();
}
if (_condition == VideoCondition::OBJECT) {
LoadModelData(_modelDataPath);
}
return true;
}
@@ -104,6 +181,91 @@ bool MacroConditionVideo::LoadImageFromFile()
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.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)
{
if (img.isNull()) {
return cv::Mat();
}
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
(uchar *)img.bits(), img.bytesPerLine());
}
QImage MatToQImage(const cv::Mat &mat)
{
if (mat.empty()) {
return QImage();
}
return QImage(mat.data, mat.cols, mat.rows, mat.step,
QImage::Format::Format_RGBX8888);
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result)
{
if (img.isNull() || pattern.isNull()) {
return;
}
if (img.height() < pattern.height() || img.width() < pattern.width()) {
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);
}
bool MacroConditionVideo::ScreenshotContainsPattern()
{
cv::Mat result;
matchPattern(_screenshotData->image, _matchImage, _patternThreshold,
result);
return countNonZero(result) > 0;
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize)
{
if (img.isNull() || cascade.empty()) {
return {};
}
auto i = QImageToMat(img);
cv::Mat frameGray;
cv::cvtColor(i, frameGray, cv::COLOR_BGR2GRAY);
equalizeHist(frameGray, frameGray);
std::vector<cv::Rect> objects;
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
0, minSize, maxSize);
return objects;
}
bool MacroConditionVideo::ScreenshotContainsObject()
{
auto objects = matchObject(_screenshotData->image, _objectCascade,
_scaleFactor, _minNeighbors,
{_minSizeX, _minSizeY},
{_maxSizeX, _maxSizeY});
return objects.size() > 0;
}
bool MacroConditionVideo::Compare()
{
switch (_condition) {
@@ -117,6 +279,10 @@ bool MacroConditionVideo::Compare()
return _screenshotData->image == _matchImage;
case VideoCondition::NO_IMAGE:
return _screenshotData->image.isNull();
case VideoCondition::PATTERN:
return ScreenshotContainsPattern();
case VideoCondition::OBJECT:
return ScreenshotContainsObject();
default:
break;
}
@@ -130,42 +296,190 @@ static inline void populateConditionSelection(QComboBox *list)
}
}
ThresholdSlider::ThresholdSlider(double min, double max, const QString &label,
const QString &description, QWidget *parent)
: QWidget(parent)
{
_slider = new QSlider();
_slider->setOrientation(Qt::Horizontal);
_slider->setRange(min * _scale, max * _scale);
_value = new QLabel();
QString labelText = label + QString("0.");
for (int i = 0; i < _precision; i++) {
labelText.append(QString("0"));
}
_value->setText(labelText);
connect(_slider, SIGNAL(valueChanged(int)), this,
SLOT(NotifyValueChanged(int)));
QVBoxLayout *mainLayout = new QVBoxLayout();
QHBoxLayout *sliderLayout = new QHBoxLayout();
sliderLayout->addWidget(_value);
sliderLayout->addWidget(_slider);
mainLayout->addLayout(sliderLayout);
if (!description.isEmpty()) {
mainLayout->addWidget(new QLabel(description));
}
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
}
void ThresholdSlider::SetDoubleValue(double value)
{
_slider->setValue(value * _scale);
SetDoubleValueText(value);
}
void ThresholdSlider::NotifyValueChanged(int value)
{
double doubleValue = value / _scale;
SetDoubleValueText(doubleValue);
emit DoubleValueChanged(doubleValue);
}
void ThresholdSlider::SetDoubleValueText(double value)
{
QString labelText = _value->text();
labelText.chop(_precision + 2); // 2 for the part left of the "."
labelText.append(QString::number(value, 'f', _precision));
_value->setText(labelText);
}
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent)
{
_videoSelection = new QComboBox();
_condition = new QComboBox();
_filePath = new QLineEdit();
_browseButton =
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
_filePath->setFixedWidth(100);
_imagePath = new FileSelection();
_imagePath->Button()->disconnect();
_patternThreshold = new ThresholdSlider(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"));
_browseButton->setStyleSheet("border:1px solid gray;");
_modelDataPath = new FileSelection();
_objectScaleThreshold = new ThresholdSlider(
1.1, 5.,
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"));
_minNeighbors = new QSpinBox();
_minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors);
_minSizeX = new QSpinBox();
_minSizeY = new QSpinBox();
_minSizeX->setMaximum(1024);
_minSizeY->setMaximum(1024);
_maxSizeX = new QSpinBox();
_maxSizeY = new QSpinBox();
_maxSizeX->setMaximum(4096);
_maxSizeY->setMaximum(4096);
_throttleEnable = new QCheckBox();
_throttleCount = new QSpinBox();
_throttleCount->setMinimum(1 * switcher->interval);
_throttleCount->setMaximum(10 * switcher->interval);
_throttleCount->setSingleStep(switcher->interval);
_showMatch = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.video.showMatch"));
QWidget::connect(_videoSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_filePath, SIGNAL(editingFinished()), this,
SLOT(FilePathChanged()));
QWidget::connect(_browseButton, SIGNAL(clicked()), this,
SLOT(BrowseButtonClicked()));
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
SLOT(ImageBrowseButtonClicked()));
QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)),
this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_objectScaleThreshold,
SIGNAL(DoubleValueChanged(double)), this,
SLOT(ObjectScaleThresholdChanged(double)));
QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this,
SLOT(MinNeighborsChanged(int)));
QWidget::connect(_minSizeX, SIGNAL(valueChanged(int)), this,
SLOT(MinSizeXChanged(int)));
QWidget::connect(_minSizeY, SIGNAL(valueChanged(int)), this,
SLOT(MinSizeYChanged(int)));
QWidget::connect(_maxSizeX, SIGNAL(valueChanged(int)), this,
SLOT(MaxSizeXChanged(int)));
QWidget::connect(_maxSizeY, SIGNAL(valueChanged(int)), this,
SLOT(MaxSizeYChanged(int)));
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
this, SLOT(ModelPathChanged(const QString &)));
QWidget::connect(_throttleEnable, SIGNAL(stateChanged(int)), this,
SLOT(ThrottleEnableChanged(int)));
QWidget::connect(_throttleCount, SIGNAL(valueChanged(int)), this,
SLOT(ThrottleCountChanged(int)));
QWidget::connect(_showMatch, SIGNAL(clicked()), this,
SLOT(ShowMatchClicked()));
populateVideoSelection(_videoSelection);
populateConditionSelection(_condition);
QHBoxLayout *mainLayout = new QHBoxLayout;
QHBoxLayout *entryLine1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoSources}}", _videoSelection},
{"{{condition}}", _condition},
{"{{filePath}}", _filePath},
{"{{browseButton}}", _browseButton},
{"{{imagePath}}", _imagePath},
{"{{minNeighbors}}", _minNeighbors},
{"{{minSizeX}}", _minSizeX},
{"{{minSizeY}}", _minSizeY},
{"{{maxSizeX}}", _maxSizeX},
{"{{maxSizeY}}", _maxSizeY},
{"{{modelDataPath}}", _modelDataPath},
{"{{throttleEnable}}", _throttleEnable},
{"{{throttleCount}}", _throttleCount},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
mainLayout, widgetPlaceholders);
entryLine1Layout, widgetPlaceholders);
_modelPathLayout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.modelPath"),
_modelPathLayout, widgetPlaceholders);
_neighborsControlLayout = new QHBoxLayout;
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.minNeighbor"),
_neighborsControlLayout, widgetPlaceholders);
_minSizeControlLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.minSize"),
_minSizeControlLayout, widgetPlaceholders);
_maxSizeControlLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.maxSize"),
_maxSizeControlLayout, widgetPlaceholders);
_throttleControlLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.video.entry.throttle"),
_throttleControlLayout, widgetPlaceholders);
QHBoxLayout *showMatchLayout = new QHBoxLayout;
showMatchLayout->addWidget(_showMatch);
showMatchLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLine1Layout);
mainLayout->addWidget(_patternThreshold);
mainLayout->addLayout(_modelPathLayout);
mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addLayout(_minSizeControlLayout);
mainLayout->addLayout(_maxSizeControlLayout);
mainLayout->addLayout(showMatchLayout);
mainLayout->addLayout(_throttleControlLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -199,12 +513,6 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
this->setToolTip(html);
}
void MacroConditionVideoEdit::SetFilePath(const QString &text)
{
_filePath->setText(text);
FilePathChanged();
}
void MacroConditionVideoEdit::SourceChanged(const QString &text)
{
if (_loading || !_entryData) {
@@ -213,10 +521,16 @@ void MacroConditionVideoEdit::SourceChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_videoSource = GetWeakSourceByQString(text);
_entryData->ResetLastMatch();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
bool needsPatternThreshold(VideoCondition cond)
{
return cond == VideoCondition::PATTERN;
}
void MacroConditionVideoEdit::ConditionChanged(int cond)
{
if (_loading || !_entryData) {
@@ -225,14 +539,8 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_condition = static_cast<VideoCondition>(cond);
if (requiresFileInput(_entryData->_condition)) {
_filePath->show();
_browseButton->show();
} else {
_filePath->hide();
_browseButton->hide();
}
_entryData->ResetLastMatch();
SetWidgetVisibility();
// Reload image data to avoid incorrect matches.
//
@@ -242,22 +550,28 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
if (_entryData->_condition == VideoCondition::OBJECT) {
auto path = _entryData->GetModelDataPath();
_entryData->_objectCascade = initObjectCascade(path);
}
}
void MacroConditionVideoEdit::FilePathChanged()
void MacroConditionVideoEdit::ImagePathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_file = _filePath->text().toUtf8().constData();
_entryData->_file = text.toUtf8().constData();
_entryData->ResetLastMatch();
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
}
void MacroConditionVideoEdit::BrowseButtonClicked()
void MacroConditionVideoEdit::ImageBrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
@@ -310,13 +624,246 @@ void MacroConditionVideoEdit::BrowseButtonClicked()
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!screenshot->done) {
DisplayMessage("Failed to get screenshot of source!");
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
return;
}
screenshot->image.save(path);
}
SetFilePath(path);
_imagePath->SetPath(path);
ImagePathChanged(path);
}
void MacroConditionVideoEdit::PatternThresholdChanged(double value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_patternThreshold = value;
}
void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_scaleFactor = value;
}
void MacroConditionVideoEdit::MinNeighborsChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_minNeighbors = value;
}
void MacroConditionVideoEdit::MinSizeXChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_minSizeX = value;
}
void MacroConditionVideoEdit::MinSizeYChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_minSizeY = value;
}
void MacroConditionVideoEdit::MaxSizeXChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_maxSizeX = value;
}
void MacroConditionVideoEdit::MaxSizeYChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_maxSizeY = value;
}
void MacroConditionVideoEdit::ThrottleEnableChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_throttleEnabled = value;
_throttleCount->setEnabled(value);
}
void MacroConditionVideoEdit::ThrottleCountChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_throttleCount = value / switcher->interval;
}
QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
{
auto matchImg = QImageToMat(image);
for (int row = 0; row < matchResult.rows - 1; row++) {
for (int col = 0; col < matchResult.cols - 1; col++) {
if (matchResult.at<float>(row, col) != 0.0) {
rectangle(matchImg, {col, row},
cv::Point(col + pattern.width(),
row + pattern.height()),
cv::Scalar(255, 0, 0, 0), 2, 8, 0);
}
}
}
return MatToQImage(matchImg);
}
QImage markObjects(QImage &image, std::vector<cv::Rect> &objects)
{
auto frame = QImageToMat(image);
for (size_t i = 0; i < objects.size(); i++) {
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);
}
return MatToQImage(frame);
}
void MacroConditionVideoEdit::ShowMatchClicked()
{
auto source = obs_weak_source_get_source(_entryData->_videoSource);
auto screenshot = std::make_unique<AdvSSScreenshotObj>(source);
obs_source_release(source);
if (!screenshot->done) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!screenshot->done) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
return;
}
QImage markedIamge;
QImage pattern = _entryData->GetMatchImage();
if (_entryData->_condition == VideoCondition::PATTERN) {
cv::Mat result;
matchPattern(screenshot->image, pattern,
_entryData->_patternThreshold, result);
if (countNonZero(result) == 0) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
return;
}
markedIamge = markPatterns(result, screenshot->image, pattern);
} else if (_entryData->_condition == VideoCondition::OBJECT) {
auto objects = matchObject(
screenshot->image, _entryData->_objectCascade,
_entryData->_scaleFactor, _entryData->_minNeighbors,
{_entryData->_minSizeX, _entryData->_minSizeY},
{_entryData->_maxSizeX, _entryData->_maxSizeY});
if (objects.empty()) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
return;
}
markedIamge = markObjects(screenshot->image, objects);
}
QLabel *label = new QLabel;
label->setPixmap(QPixmap::fromImage(markedIamge));
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(label);
QDialog dialog;
dialog.setLayout(layout);
dialog.setWindowTitle("Advanced Scene Switcher");
dialog.exec();
}
void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
std::string path = text.toStdString();
if (!_entryData->LoadModelData(path)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.modelLoadFail"));
}
}
bool needsShowMatch(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT;
}
bool needsObjectControls(VideoCondition cond)
{
return cond == VideoCondition::OBJECT;
}
bool needsThrottleControls(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT;
}
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);
}
}
}
void MacroConditionVideoEdit::SetWidgetVisibility()
{
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
_patternThreshold->setVisible(
needsPatternThreshold(_entryData->_condition));
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
_objectScaleThreshold->setVisible(
needsObjectControls(_entryData->_condition));
setLayoutVisible(_neighborsControlLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_minSizeControlLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_maxSizeControlLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_modelPathLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->_condition));
adjustSize();
}
void MacroConditionVideoEdit::UpdateEntryData()
@@ -328,10 +875,17 @@ void MacroConditionVideoEdit::UpdateEntryData()
_videoSelection->setCurrentText(
GetWeakSourceName(_entryData->_videoSource).c_str());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_filePath->setText(QString::fromStdString(_entryData->_file));
if (!requiresFileInput(_entryData->_condition)) {
_filePath->hide();
_browseButton->hide();
}
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_patternThreshold->SetDoubleValue(_entryData->_patternThreshold);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
_minNeighbors->setValue(_entryData->_minNeighbors);
_minSizeX->setValue(_entryData->_minSizeX);
_minSizeY->setValue(_entryData->_minSizeY);
_maxSizeX->setValue(_entryData->_maxSizeX);
_maxSizeY->setValue(_entryData->_maxSizeY);
_throttleEnable->setChecked(_entryData->_throttleEnabled);
_throttleCount->setValue(_entryData->_throttleCount *
switcher->interval);
SetWidgetVisibility();
}

View File

@@ -1,3 +1,5 @@
#ifdef VCAM_SUPPORTED
#include "headers/macro-condition-edit.hpp"
#include "headers/macro-condition-virtual-cam.hpp"
#include "headers/utility.hpp"
@@ -99,3 +101,5 @@ void MacroConditionVCamEdit::UpdateEntryData()
_states->setCurrentIndex(static_cast<int>(_entryData->_state));
}
#endif

View File

@@ -5,6 +5,7 @@
#include "headers/name-dialog.hpp"
#include "headers/utility.hpp"
#include <QColor>
#include <QMenu>
static QMetaObject::Connection addPulse;
@@ -207,6 +208,34 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
}
}
void AdvSceneSwitcher::HighlightAction(int idx)
{
auto item = ui->macroEditActionLayout->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), "QLabel ",
true);
}
void AdvSceneSwitcher::HighlightCondition(int idx)
{
auto item = ui->macroEditConditionLayout->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), "QLabel ",
true);
}
void AdvSceneSwitcher::ConnectControlSignals(MacroActionEdit *c)
{
connect(c, &MacroActionEdit::AddAt, this,

View File

@@ -79,7 +79,7 @@ bool Macro::CeckMatch()
break;
default:
blog(LOG_WARNING,
"ignoring unkown condition check for '%s'",
"ignoring unknown condition check for '%s'",
_name.c_str());
break;
}
@@ -249,7 +249,7 @@ bool Macro::Load(obs_data_t *obj)
setValidLogic(c, root, _name);
} else {
blog(LOG_WARNING,
"discarding condition entry with unkown id (%s) for macro %s",
"discarding condition entry with unknown id (%s) for macro %s",
id.c_str(), _name.c_str());
}
@@ -273,7 +273,7 @@ bool Macro::Load(obs_data_t *obj)
_actions.back()->Load(array_obj);
} else {
blog(LOG_WARNING,
"discarding action entry with unkown id (%s) for macro %s",
"discarding action entry with unknown id (%s) for macro %s",
id.c_str(), _name.c_str());
}

View File

@@ -12,7 +12,7 @@
#include <map>
#include <thread>
bool canSimulateKeyPresses = true;
bool canSimulateKeyPresses = false;
void GetWindowList(std::vector<std::string> &windows)
{
@@ -405,7 +405,7 @@ static std::map<HotkeyType, CGKeyCode> keyTable = {
{HotkeyType::Key_NumpadEnter, kVK_ANSI_KeypadEnter},
};
void PressKeys(const std::vector<HotkeyType> keys)
void PressKeys(const std::vector<HotkeyType> keys, int duration)
{
// TODO:
// I can't seem to get this to work so drop support for this functionality

View File

@@ -140,7 +140,7 @@ void SceneSelectionWidget::SetScene(SceneSelection &s)
}
}
static int isFirstEntry(QComboBox *l, QString name, int idx)
static bool isFirstEntry(QComboBox *l, QString name, int idx)
{
for (auto i = l->count() - 1; i >= 0; i--) {
if (l->itemText(i) == name) {

View File

@@ -149,7 +149,7 @@ void SceneTrigger::logMatch()
actionName = "STOP VIRTUAL CAMERA";
break;
default:
actionName = "UNKOWN";
actionName = "UNKNOWN";
break;
}
@@ -185,18 +185,22 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
case sceneTriggerAction::STOP_STREAMING:
obs_frontend_streaming_stop();
break;
#ifdef REPLAYBUFFER_SUPPORTED
case sceneTriggerAction::START_REPLAY_BUFFER:
obs_frontend_replay_buffer_start();
break;
case sceneTriggerAction::STOP_REPLAY_BUFFER:
obs_frontend_replay_buffer_stop();
break;
#endif
#ifdef VCAM_SUPPORTED
case sceneTriggerAction::START_VCAM:
obs_frontend_start_virtualcam();
break;
case sceneTriggerAction::STOP_VCAM:
obs_frontend_stop_virtualcam();
break;
#endif
default:
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
static_cast<int>(action));
@@ -269,7 +273,7 @@ void SceneTrigger::performAction()
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
t = std::thread(statusThread, duration, stop);
} else {
blog(LOG_WARNING, "ignoring unkown action '%d'",
blog(LOG_WARNING, "ignoring unknown action '%d'",
static_cast<int>(triggerAction));
}

View File

@@ -1,4 +1,5 @@
#include "headers/screenshot-helper.hpp"
#include "headers/advanced-scene-switcher.hpp"
static void ScreenshotTick(void *param, float);
@@ -33,7 +34,9 @@ void AdvSSScreenshotObj::Screenshot()
}
if (!cx || !cy) {
blog(LOG_WARNING, "Cannot screenshot, invalid target size");
vblog(LOG_WARNING,
"Cannot screenshot \"%s\", invalid target size",
obs_source_get_name(source));
obs_remove_tick_callback(ScreenshotTick, this);
done = true;
return;

View File

@@ -51,6 +51,7 @@ void AdvSceneSwitcher::on_readPathLineEdit_textChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.readEnabled = false;
switcher->fileIO.readPath = "";
return;
}
switcher->fileIO.readEnabled = true;
@@ -66,6 +67,7 @@ void AdvSceneSwitcher::on_writePathLineEdit_textChanged(const QString &text)
std::lock_guard<std::mutex> lock(switcher->m);
if (text.isEmpty()) {
switcher->fileIO.writeEnabled = false;
switcher->fileIO.writePath = "";
return;
}
switcher->fileIO.writeEnabled = true;

View File

@@ -0,0 +1,171 @@
#include "headers/transition-selection.hpp"
#include "headers/advanced-scene-switcher.hpp"
void TransitionSelection::Save(obs_data_t *obj, const char *name,
const char *typeName)
{
obs_data_set_int(obj, typeName, static_cast<int>(_type));
switch (_type) {
case TransitionSelectionType::TRANSITION:
obs_data_set_string(obj, name,
GetWeakSourceName(_transition).c_str());
break;
default:
break;
}
}
void TransitionSelection::Load(obs_data_t *obj, const char *name,
const char *typeName)
{
_type = static_cast<TransitionSelectionType>(
obs_data_get_int(obj, typeName));
auto target = obs_data_get_string(obj, name);
switch (_type) {
case TransitionSelectionType::TRANSITION:
_transition = GetWeakTransitionByName(target);
break;
default:
break;
}
}
OBSWeakSource TransitionSelection::GetTransition()
{
switch (_type) {
case TransitionSelectionType::TRANSITION:
return _transition;
case TransitionSelectionType::CURRENT: {
auto source = obs_frontend_get_current_transition();
auto weakSource = obs_source_get_weak_source(source);
obs_weak_source_release(weakSource);
obs_source_release(source);
return weakSource;
}
default:
break;
}
return nullptr;
}
std::string TransitionSelection::ToString()
{
switch (_type) {
case TransitionSelectionType::TRANSITION:
return GetWeakSourceName(_transition);
case TransitionSelectionType::CURRENT:
return obs_module_text("AdvSceneSwitcher.currentTransition");
case TransitionSelectionType::ANY:
return obs_module_text("AdvSceneSwitcher.anyTransition");
default:
break;
}
return "";
}
TransitionSelectionWidget::TransitionSelectionWidget(QWidget *parent,
bool current, bool any)
: QComboBox(parent)
{
// For the rare occasion of a name conflict with current / previous
setDuplicatesEnabled(true);
populateTransitionSelection(this, current, any);
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SelectionChanged(const QString &)));
}
void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
{
// Order of entries
// 1. Any transition
// 2. Current transition
// 3. Previous transition
// 4. Transitions
int idx;
switch (t.GetType()) {
case TransitionSelectionType::TRANSITION:
setCurrentText(QString::fromStdString(t.ToString()));
break;
case TransitionSelectionType::CURRENT:
idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.currentTransition")));
if (idx != -1) {
setCurrentIndex(idx);
}
break;
case TransitionSelectionType::ANY:
idx = findText(QString::fromStdString(
obs_module_text("AdvSceneSwitcher.anyTransition")));
if (idx != -1) {
setCurrentIndex(idx);
}
break;
default:
setCurrentIndex(0);
break;
}
}
void TransitionSelectionWidget::Repopulate(bool current, bool any)
{
this->clear();
populateTransitionSelection(this, current, any);
setCurrentIndex(0);
TransitionSelection t;
emit TransitionChanged(t);
}
static bool isFirstEntry(QComboBox *l, QString name, int idx)
{
for (auto i = l->count() - 1; i >= 0; i--) {
if (l->itemText(i) == name) {
return idx == i;
}
}
// If entry cannot be found we dont want the selection to be empty
return false;
}
bool TransitionSelectionWidget::IsCurrentTransitionSelected(const QString &name)
{
if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.currentTransition")))) {
return isFirstEntry(this, name, currentIndex());
}
return false;
}
bool TransitionSelectionWidget::IsAnyTransitionSelected(const QString &name)
{
if (name == QString::fromStdString((obs_module_text(
"AdvSceneSwitcher.anyTransition")))) {
return isFirstEntry(this, name, currentIndex());
}
return false;
}
void TransitionSelectionWidget::SelectionChanged(const QString &name)
{
TransitionSelection t;
auto transition = GetWeakTransitionByQString(name);
if (transition) {
t._type = TransitionSelectionType::TRANSITION;
t._transition = transition;
}
if (!transition) {
if (IsCurrentTransitionSelected(name)) {
t._type = TransitionSelectionType::CURRENT;
}
if (IsAnyTransitionSelected(name)) {
t._type = TransitionSelectionType::ANY;
}
}
emit TransitionChanged(t);
}

View File

@@ -16,7 +16,6 @@
#include <regex>
#include <set>
#include <obs-module.h>
#include <util/util.hpp>
bool WeakSourceValid(obs_weak_source_t *ws)
{
@@ -140,6 +139,35 @@ getNextDelim(const std::string &text,
return res;
}
std::pair<double, double> getSceneItemSize(obs_scene_item *item)
{
std::pair<double, double> size;
obs_source_t *source = obs_sceneitem_get_source(item);
size.first = double(obs_source_get_width(source));
size.second = double(obs_source_get_height(source));
return size;
}
std::string getSceneItemTransform(obs_scene_item *item)
{
struct obs_transform_info info;
struct obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &info);
obs_sceneitem_get_crop(item, &crop);
auto size = getSceneItemSize(item);
auto data = obs_data_create();
saveTransformState(data, info, crop);
obs_data_t *obj = obs_data_create();
obs_data_set_double(obj, "width", size.first * info.scale.x);
obs_data_set_double(obj, "height", size.second * info.scale.y);
obs_data_set_obj(data, "size", obj);
obs_data_release(obj);
auto json = std::string(obs_data_get_json(data));
obs_data_release(data);
return json;
}
void placeWidgets(std::string text, QBoxLayout *layout,
std::unordered_map<std::string, QWidget *> placeholders,
bool addStretch)
@@ -223,9 +251,17 @@ bool compareIgnoringLineEnding(QString &s1, QString &s2)
std::string getSourceSettings(OBSWeakSource ws)
{
if (!ws) {
return "";
}
std::string settings;
auto s = obs_weak_source_get_source(ws);
obs_data_t *data = obs_source_get_settings(s);
std::string settings = obs_data_get_json(data);
auto json = obs_data_get_json(data);
if (json) {
settings = json;
}
obs_data_release(data);
obs_source_release(s);
@@ -278,31 +314,41 @@ std::vector<obs_scene_item *> getSceneItemsWithName(OBSScene scene,
return itemInfo.items;
}
bool compareSourceSettings(const OBSWeakSource &source,
const std::string &settings, bool useRegex)
// Match json1 with pattern json2
bool matchJson(const std::string &json1, const std::string &json2,
bool useRegex)
{
bool ret = false;
std::string currentSettings = getSourceSettings(source);
// User input and source settings might be formatted differently
currentSettings = formatJsonString(currentSettings).toStdString();
std::string userSettings = formatJsonString(settings).toStdString();
auto j1 = formatJsonString(json1).toStdString();
auto j2 = formatJsonString(json2).toStdString();
if (userSettings.empty()) {
userSettings = settings;
if (j1.empty()) {
j1 = json1;
}
if (j2.empty()) {
j2 = json2;
}
if (useRegex) {
try {
std::regex expr(userSettings);
ret = std::regex_match(currentSettings, expr);
std::regex expr(j2);
ret = std::regex_match(
j1, expr, std::regex_constants::match_default);
} catch (const std::regex_error &) {
}
} else {
ret = currentSettings == userSettings;
ret = j1 == j2;
}
return ret;
}
bool compareSourceSettings(const OBSWeakSource &source,
const std::string &settings, bool useRegex)
{
std::string currentSettings = getSourceSettings(source);
return matchJson(currentSettings, settings, useRegex);
}
std::string getDataFilePath(const std::string &file)
{
std::string root_path = obs_get_module_data_path(obs_current_module());
@@ -323,6 +369,58 @@ QString formatJsonString(const char *json)
return doc.toJson(QJsonDocument::Indented);
}
QString escapeForRegex(QString &s)
{
std::regex specialChars{R"([-[\]{}()*+?.,\^$|#\s])"};
std::string input = s.toStdString();
return QString::fromStdString(
std::regex_replace(input, specialChars, R"(\$&)"));
}
void loadTransformState(obs_data_t *obj, struct obs_transform_info &info,
struct obs_sceneitem_crop &crop)
{
obs_data_get_vec2(obj, "pos", &info.pos);
obs_data_get_vec2(obj, "scale", &info.scale);
info.rot = (float)obs_data_get_double(obj, "rot");
info.alignment = (uint32_t)obs_data_get_int(obj, "alignment");
info.bounds_type =
(enum obs_bounds_type)obs_data_get_int(obj, "bounds_type");
info.bounds_alignment =
(uint32_t)obs_data_get_int(obj, "bounds_alignment");
obs_data_get_vec2(obj, "bounds", &info.bounds);
crop.top = (int)obs_data_get_int(obj, "top");
crop.bottom = (int)obs_data_get_int(obj, "bottom");
crop.left = (int)obs_data_get_int(obj, "left");
crop.right = (int)obs_data_get_int(obj, "right");
}
bool saveTransformState(obs_data_t *obj, struct obs_transform_info &info,
struct obs_sceneitem_crop &crop)
{
struct vec2 pos = info.pos;
struct vec2 scale = info.scale;
float rot = info.rot;
uint32_t alignment = info.alignment;
uint32_t bounds_type = info.bounds_type;
uint32_t bounds_alignment = info.bounds_alignment;
struct vec2 bounds = info.bounds;
obs_data_set_vec2(obj, "pos", &pos);
obs_data_set_vec2(obj, "scale", &scale);
obs_data_set_double(obj, "rot", rot);
obs_data_set_int(obj, "alignment", alignment);
obs_data_set_int(obj, "bounds_type", bounds_type);
obs_data_set_vec2(obj, "bounds", &bounds);
obs_data_set_int(obj, "bounds_alignment", bounds_alignment);
obs_data_set_int(obj, "top", crop.top);
obs_data_set_int(obj, "bottom", crop.bottom);
obs_data_set_int(obj, "left", crop.left);
obs_data_set_int(obj, "right", crop.right);
return true;
}
bool DisplayMessage(const QString &msg, bool question)
{
if (question) {
@@ -387,8 +485,7 @@ void populateSourceSelection(QComboBox *list, bool addSelect)
list->setCurrentIndex(0);
}
void populateTransitionSelection(QComboBox *sel, bool addCurrent,
bool addSelect, bool selectable)
void populateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny)
{
obs_frontend_source_list *transitions = new obs_frontend_source_list();
@@ -404,19 +501,19 @@ void populateTransitionSelection(QComboBox *sel, bool addCurrent,
sel->model()->sort(0);
addSelectionEntry(sel,
obs_module_text("AdvSceneSwitcher.selectTransition"));
sel->setCurrentIndex(0);
if (addCurrent) {
sel->insertItem(
0,
1,
obs_module_text("AdvSceneSwitcher.currentTransition"));
}
if (addSelect) {
addSelectionEntry(
sel,
obs_module_text("AdvSceneSwitcher.selectTransition"),
selectable);
if (addAny) {
sel->insertItem(
1, obs_module_text("AdvSceneSwitcher.anyTransition"));
}
sel->setCurrentIndex(0);
}
void populateWindowSelection(QComboBox *sel, bool addSelect)
@@ -563,13 +660,14 @@ void populateSceneSelection(QComboBox *sel, bool addPrevious, bool addCurrent,
std::deque<SceneGroup> *sceneGroups, bool addSelect,
std::string selectText, bool selectable)
{
BPtr<char *> scenes = obs_frontend_get_scene_names();
char **scenes = obs_frontend_get_scene_names();
char **temp = scenes;
while (*temp) {
const char *name = *temp;
sel->addItem(name);
temp++;
}
bfree(scenes);
if (addSceneGroup && sceneGroups) {
for (auto &sg : *sceneGroups) {
@@ -718,8 +816,9 @@ void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
list->setCurrentIndex(0);
}
QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor,
QColor startColor, QString specifier)
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
QColor endColor, QString specifier,
bool once)
{
widget->setStyleSheet(specifier + "{ \
border-style: outset; \
@@ -735,15 +834,17 @@ QMetaObject::Connection PulseWidget(QWidget *widget, QColor endColor,
paAnimation->setStartValue(startColor);
paAnimation->setEndValue(endColor);
paAnimation->setDuration(1000);
// Play backwards to return to original state on timer end
paAnimation->setDirection(QAbstractAnimation::Backward);
auto con = QWidget::connect(
paAnimation, &QPropertyAnimation::finished, [paAnimation]() {
QTimer::singleShot(1000, [paAnimation] {
paAnimation->start();
QMetaObject::Connection con;
if (!once) {
con = QWidget::connect(
paAnimation, &QPropertyAnimation::finished,
[paAnimation]() {
QTimer::singleShot(1000, [paAnimation] {
paAnimation->start();
});
});
});
}
paAnimation->start();

View File

@@ -391,7 +391,7 @@ static std::unordered_map<HotkeyType, long> keyTable = {
{HotkeyType::Key_NumpadEnter, VK_RETURN},
};
void PressKeys(const std::vector<HotkeyType> keys)
void PressKeys(const std::vector<HotkeyType> keys, int duration)
{
INPUT ip;
ip.type = INPUT_KEYBOARD;
@@ -411,7 +411,7 @@ void PressKeys(const std::vector<HotkeyType> keys)
}
// When instantly releasing the key presses OBS might miss them
Sleep(300);
Sleep(duration);
// Release keys
ip.ki.dwFlags = KEYEVENTF_KEYUP;