Compare commits

...

68 Commits

Author SHA1 Message Date
WarmUpTill
e89343b03e Fix crash when deleting last macro
A crash could occur if the last macro was deleted and the highlighting
of macro segments was enabled due to the macro segments already being
deleted while the widgets representing these segments were not
2022-06-12 06:45:11 -07:00
WarmUpTill
a9c79b02ff Fix UI elements shifting slightly
Content margins of scene item selection widget would cause Transition
action to shift slightly whenever the widget's visibility changed
2022-06-12 04:50:13 -07:00
WarmUpTill
c6541825f8 Add option to select before and after for time of simple date check 2022-06-12 03:46:18 -07:00
WarmUpTill
003a1aa696 Remove delay before saving replay buffer 2022-06-12 03:46:04 -07:00
WarmUpTill
20767630c0 Add option to set show, hide, and override transitions (#471) 2022-06-12 12:45:45 +02:00
WarmUpTill
70fb972f84 Add option to ignore time component in simple check 2022-05-18 04:55:55 -07:00
WarmUpTill
4d65956f25 Fix MacOS memory leak 2022-05-18 04:55:28 -07:00
WarmUpTill
a2e4580970 Fix media and audio condition unexpectedly returning true after unpause 2022-05-12 11:42:39 -07:00
WarmUpTill
8da1e3ad40 Rework time restriction to support "within" duration modifier
Also renamed duration constraint to duration modifier
2022-05-12 11:42:39 -07:00
WarmUpTill
92b6a057f6 Restructure files
- Move condition and action related parts into separate files
 - Move duration constraint to macro condition
 - Move duration constraint widget to macro condition edit
 - Adjust includes
2022-05-12 11:42:39 -07:00
WarmUpTill
70a3ae0245 Fix waiting for transition to complete with fixed duration transitions 2022-05-12 11:42:39 -07:00
WarmUpTill
8cc7b65436 Do not allow duplicates in condtion and action type selection 2022-05-12 11:42:39 -07:00
WarmUpTill
fd18e64e19 Add studio mode action (previously scene swap and preview scene action)
The option to enable and disable studio mode is new
2022-05-12 11:42:39 -07:00
WarmUpTill
e4526c4e81 Enable option to check previous scene during active transition 2022-05-07 16:55:36 -07:00
WarmUpTill
d6ef69a3d4 Add spinbox control to threshold slider 2022-05-06 11:37:43 -07:00
WarmUpTill
b5eb0b3f3e Add option to disable registering of pause control hotkeys 2022-05-06 11:37:28 -07:00
WarmUpTill
22a361f977 Fix scene group not being random 2022-05-04 06:20:10 -07:00
WarmUpTill
a0db023aae cleanup es-ES.ini 2022-05-04 02:28:10 -07:00
EliasDIPA
dc8a60a5e5 Traduccion al Español añadida 2022-05-04 02:18:32 -07:00
WarmUpTill
d47b878b56 Display next match time if repeat is selected 2022-04-24 09:45:22 -07:00
WarmUpTill
9105087036 Show update on repeat option only if repeat is selected 2022-04-24 09:45:22 -07:00
WarmUpTill
e26f41a1a8 Add option to preserve original date in combination with repeat option 2022-04-24 09:45:22 -07:00
WarmUpTill
ff513d8664 Fix cursor condition frame blocking mouse inputs 2022-04-24 06:28:28 -07:00
WarmUpTill
07f11c63f0 Don't allow manipulating area selection when highlighting match 2022-04-02 12:51:57 -07:00
WarmUpTill
6685a1ccaa Do not spam first time setup message 2022-04-02 09:14:26 -07:00
WarmUpTill
ecee919e0b Hide legacy tabs by default and adjust default tab order 2022-04-02 09:14:26 -07:00
WarmUpTill
e8c1b673c0 Allow accessing macro properties even without selecting any macro 2022-04-02 09:14:26 -07:00
WarmUpTill
1a8b185f7d Make date "At"-type check more robust
Previously the "At" check could skip the desired time window if e.g another
macro was performing a long wait wait action or condition was taking
a long time to check.
The date condition will now take into consideration when the last time
was the condition checked a date instead of simply applying a fixed
window of "switcher->interval".
2022-04-02 09:13:01 -07:00
WarmUpTill
eb8a9df627 Create cropped screenshot depending on selected area 2022-04-02 00:29:29 -07:00
WarmUpTill
123d308a0d Add option to select OSB' main video output 2022-03-29 10:02:44 -07:00
WarmUpTill
102b93d3b5 Fix deadlock when changing condition / action type
The video preview dialogs could not exit as the destructor was holding
the main mutex while joining the preview thread, which in turn was
waiting to get the access to the mutex to exit its main loop
2022-03-29 10:02:44 -07:00
WarmUpTill
eeceb7cbd9 Fix random action not being random 2022-03-27 11:45:50 -07:00
WarmUpTill
92add4f090 Update .gitignore 2022-03-26 15:55:48 -07:00
ubuntu
3ed7727c85 Fix warnings 2022-03-26 15:40:19 -07:00
ubuntu
a922d6a73d Add missing reordering of action widgets 2022-03-26 15:40:19 -07:00
WarmUpTill
9ba8a02a11 Add hotkey to move current macro segment selection up / down 2022-03-26 10:05:50 -07:00
WarmUpTill
e61539a878 Reuse macro segment widgets if possible 2022-03-26 10:05:50 -07:00
WarmUpTill
5a78c99703 Ignore delete hotkey if macro tab is not in focus 2022-03-26 10:05:50 -07:00
WarmUpTill
dbbcf04b8a Auto scroll during drag if touching top or bottom areas of list widget 2022-03-26 10:05:50 -07:00
WarmUpTill
6a8066795b Improve drag & drop behaviour of macro segments
- Depending on the drop position you can either drop before or after a
  widget
- Added a visual indicator where the macro segment will be dropped
2022-03-26 10:05:50 -07:00
WarmUpTill
e0763a4957 Improve performance for drag and drop
- Changing the style sheets for the selection frame dynamically considerably
  slowed down the handling of the mouse events.
  Instead two separate widgets with fixed style sheets are now used.
- Recreating the macro segments after dropping a widget took too much
  time leading to unresponsive drop operations
2022-03-26 10:05:50 -07:00
WarmUpTill
1ab9a38914 Selection idx handling was moved to macro segment list widget 2022-03-26 10:05:50 -07:00
WarmUpTill
a6ca266dab Fix potential crash / memory leak when clearing layouts 2022-03-26 10:05:50 -07:00
WarmUpTill
0a8f279e97 Rework macro segment list widget
- Allows drag and drop reorder
- Handles selection change
- Handles highlight
- Handles changing collapse change
- Handles add, insert, remove, and clear functionality
2022-03-26 10:05:50 -07:00
WarmUpTill
1b63978acb Add media seek action 2022-03-26 10:05:40 -07:00
WarmUpTill
8487ef4716 clean up locale 2022-03-26 10:05:40 -07:00
WarmUpTill
ddc2ee4fa5 Remove "Played to end" and replace it with "Playlist end" state
"Ended" can now be used to also detect the end of individual playlist
items.
2022-03-26 10:05:40 -07:00
WarmUpTill
11fede6cc3 Fix start streaming action only working every second attempt 2022-03-18 13:08:31 -07:00
WarmUpTill
1d45072c58 Recurse in setLayoutVisible() 2022-03-15 10:58:29 -07:00
WarmUpTill
d450210d39 Add option to visually select area to check 2022-03-15 10:58:29 -07:00
WarmUpTill
5462334693 Add option to perform video check only on parts of the video source
This will significantly reduce CPU usage if the video source larger than
the selected area and make it easier to only check parts of a video
source output image.
2022-03-15 10:58:29 -07:00
WarmUpTill
293d3dd16c Work around macro handle buttons not being redrawn
Not sure what exaclty causes this - investigating it further is not
worth the time at the moment
2022-03-12 06:35:59 -08:00
WarmUpTill
71b6ae4d78 Add additional settings dialog to macro tab
This settings dialog currently contains debug features to highlight
recently executed macros, actions and conditions.
2022-03-12 06:35:59 -08:00
WarmUpTill
69d6d63dfd Increase macroSplitter size by moving condition controls into handle 2022-03-12 06:35:59 -08:00
WarmUpTill
d693dbc844 Add highlighting of onChagne if actions were prevented recently 2022-03-12 06:35:59 -08:00
WarmUpTill
428e114a0a Highlight recently executed action and true conditions 2022-03-12 06:35:59 -08:00
WarmUpTill
001d8b4714 Move hightlight and update of paused state into MacroListEntryWidget 2022-03-12 06:35:59 -08:00
WarmUpTill
37734445e7 CI: Add automated check for invalid locale files
Also clean up issues found in the existing locale files
2022-03-05 17:09:58 -08:00
WarmUpTill
d1fe5beaeb Fix fade animation triggering unexpectedly for controls 2022-03-05 17:09:58 -08:00
WarmUpTill
036afb4a4f Add option to abort active fade when starting new fade 2022-03-05 15:38:46 -08:00
WarmUpTill
c87589d534 reduce duplicate code 2022-03-05 15:38:46 -08:00
WarmUpTill
1df513585d Add option to specifiy rate of change instead of duration for audio fade 2022-03-05 15:38:46 -08:00
WarmUpTill
e1bacd75b6 Move audio fade threads to generic _helperThreads of macro
This will allow waiting for and stopping audio fades when calling Stop()
for a particular macro
2022-03-05 15:38:46 -08:00
WarmUpTill
7095f4668c Rework wait action to support being interrupted by calling Stop() 2022-03-05 15:38:46 -08:00
WarmUpTill
8131ad3c24 Block when calling Stop() until macro is stopped 2022-03-05 15:38:46 -08:00
WarmUpTill
ac5b2c3f9e Rework handling of fade threads
Previous implementation did not allow ongoing fades to be stopped.
New implementations allows waiting for fade to complete.
2022-03-05 15:38:46 -08:00
WarmUpTill
a86f7d0fd4 Set background of openVR controls transparent 2022-03-05 07:40:14 -08:00
WarmUpTill
34736ffbda Update zh-CN locale (by qwe1154323937) 2022-03-05 01:19:48 -08:00
91 changed files with 5846 additions and 2470 deletions

14
.github/workflows/locale-check.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Check locale
on: [push, pull_request]
jobs:
ubuntu64:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Check locale files
run: |
python3 ./CI/checkLocale.py -p data/locale/

9
.gitignore vendored
View File

@@ -92,6 +92,15 @@ Thumbs.db
*.iml
*.ipr
*.sublime*
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
.history/
*.vsix
forms/advanced-scene-switcher.ui.autosave
# Directories #
###############

93
CI/checkLocale.py Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
import os
import sys
import argparse
defaultLocaleFile = "en-US.ini"
class localeEntry:
locale = ""
placeholders = []
def __init__(self, locale, widgets) -> None:
self.locale = locale
self.placeholders = widgets
def getNonDefaultLocales(dir):
files = []
for filename in os.listdir(dir):
f = os.path.join(dir, filename)
if os.path.isfile(f) and not f.endswith(defaultLocaleFile):
files.append(f)
return files
def getAllLocaleEntriesWithWidgetPlaceholders(file):
localeEntries = []
with open(file, 'r', encoding='UTF-8') as f:
for line in f.readlines():
widgetPlaceholders = []
for word in line.split("{{"):
if not "}}" in word:
continue
word = "{{" + word[:word.rfind("}}")] + "}}"
widgetPlaceholders.append(word)
localeEntries.append(localeEntry(
line.split("=")[0], widgetPlaceholders))
return localeEntries
def getLocaleEntryFrom(entry, list):
for element in list:
if element.locale == entry.locale:
return element
return None
def checkWidgetPlacehodlers(file, expectedPlaceholders):
localeEntries = getAllLocaleEntriesWithWidgetPlaceholders(file)
result = True
for localeEntry in localeEntries:
expectedEntry = getLocaleEntryFrom(localeEntry, expectedPlaceholders)
if expectedEntry is None:
print(
"WARNING: Locale entry \"{}\" from \"{}\" not found in \"{}\"".format(localeEntry.locale, file, defaultLocaleFile))
continue
for p in localeEntry.placeholders:
if p not in expectedEntry.placeholders:
print("WARNING: Locale entry \"{}\" from \"{}\" does contain \"{}\" while \"{}\" does not".format(
localeEntry.locale, file, p, defaultLocaleFile))
for p in expectedEntry.placeholders:
if p not in localeEntry.placeholders:
result = False
print("ERROR: Locale entry \"{}\" from \"{}\" does not contain \"{}\"".format(
localeEntry.locale, file, p))
return result
def main():
parser = argparse.ArgumentParser(
description='Checks for inconsistencies regarding widget placeholders in the different locale files.')
parser.add_argument(
'-p', '--path', help='Path to locale folder', required=True)
args = parser.parse_args()
placeholders = getAllLocaleEntriesWithWidgetPlaceholders(
os.path.join(args.path, defaultLocaleFile))
nonDefaultLocales = getNonDefaultLocales(args.path)
result = True
for f in nonDefaultLocales:
if checkWidgetPlacehodlers(f, placeholders) == False:
result = False
if result == False:
sys.exit(1)
print("SUCCESS: No issues found!")
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -107,6 +107,7 @@ else()
include_directories("${LIBCURL_INCLUDE_DIRS}")
add_definitions(-DVCAM_SUPPORTED)
add_definitions(-DREPLAYBUFFER_SUPPORTED)
add_definitions(-DVISIBILITY_TRANSITIONS_SUPPORTED)
endif()
# Platform specific settings
@@ -190,6 +191,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/switch-sequence.hpp
src/headers/switch-video.hpp
src/headers/switch-generic.hpp
src/headers/macro-action.hpp
src/headers/macro-action-edit.hpp
src/headers/macro-action-audio.hpp
src/headers/macro-action-file.hpp
@@ -198,7 +200,6 @@ 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-profile.hpp
src/headers/macro-action-random.hpp
src/headers/macro-action-recording.hpp
@@ -206,7 +207,6 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-run.hpp
src/headers/macro-action-scene-collection.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
@@ -214,11 +214,13 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-action-sequence.hpp
src/headers/macro-action-source.hpp
src/headers/macro-action-streaming.hpp
src/headers/macro-action-studio-mode.hpp
src/headers/macro-action-systray.hpp
src/headers/macro-action-timer.hpp
src/headers/macro-action-transition.hpp
src/headers/macro-action-virtual-cam.hpp
src/headers/macro-action-wait.hpp
src/headers/macro-condition.hpp
src/headers/macro-condition-edit.hpp
src/headers/macro-condition-audio.hpp
src/headers/macro-condition-cursor.hpp
@@ -246,7 +248,9 @@ set(advanced-scene-switcher_HEADERS
src/headers/macro-condition-virtual-cam.hpp
src/headers/macro-condition-window.hpp
src/headers/macro.hpp
src/headers/macro-ref.hpp
src/headers/macro-list-entry-widget.hpp
src/headers/macro-properties.hpp
src/headers/macro-segment.hpp
src/headers/macro-segment-list.hpp
src/headers/macro-selection.hpp
@@ -293,6 +297,7 @@ set(advanced-scene-switcher_SOURCES
src/switch-audio.cpp
src/switch-video.cpp
src/switch-generic.cpp
src/macro-action.cpp
src/macro-action-edit.cpp
src/macro-action-audio.cpp
src/macro-action-file.cpp
@@ -301,7 +306,6 @@ 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-profile.cpp
src/macro-action-random.cpp
src/macro-action-recording.cpp
@@ -309,7 +313,6 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-run.cpp
src/macro-action-scene-collection.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
@@ -317,11 +320,13 @@ set(advanced-scene-switcher_SOURCES
src/macro-action-sequence.cpp
src/macro-action-source.cpp
src/macro-action-streaming.cpp
src/macro-action-studio-mode.cpp
src/macro-action-systray.cpp
src/macro-action-timer.cpp
src/macro-action-transition.cpp
src/macro-action-virtual-cam.cpp
src/macro-action-wait.cpp
src/macro-condition.cpp
src/macro-condition-edit.cpp
src/macro-condition-audio.cpp
src/macro-condition-cursor.cpp
@@ -349,7 +354,9 @@ set(advanced-scene-switcher_SOURCES
src/macro-condition-virtual-cam.cpp
src/macro-condition-window.cpp
src/macro.cpp
src/macro-ref.cpp
src/macro-list-entry-widget.cpp
src/macro-properties.cpp
src/macro-segment.cpp
src/macro-segment-list.cpp
src/macro-selection.cpp
@@ -372,10 +379,11 @@ set(advanced-scene-switcher_SOURCES
if(DEFINED LibObs_VERSION_MAJOR)
if(LibObs_VERSION_MAJOR GREATER_EQUAL 27)
add_definitions(-DVCAM_SUPPORTED)
add_definitions(-DVISIBILITY_TRANSITIONS_SUPPORTED)
else()
message(
WARNING
"OBS version ${LibObs_VERSION_MAJOR} found - disabling virtual camera functionality"
"OBS version ${LibObs_VERSION_MAJOR} found - disabling virtual camera and hide/show transition functionality"
)
endif()
if(LibObs_VERSION_MAJOR GREATER_EQUAL 26)

View File

@@ -117,7 +117,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="Pausiert"
AdvSceneSwitcher.mediaTab.states.stopped="Gestoppt"
AdvSceneSwitcher.mediaTab.states.ended="Beendet"
AdvSceneSwitcher.mediaTab.states.error="Fehler"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Zu Ende gespielt"
AdvSceneSwitcher.mediaTab.states.any="Beliebig"
AdvSceneSwitcher.mediaTab.timeRestriction.none="Keine Auswahl"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Dauer kürzer"
@@ -223,7 +222,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es
; Network Tab
AdvSceneSwitcher.networkTab.title="Netzwerk"
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden."
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"

View File

@@ -31,7 +31,6 @@ AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
AdvSceneSwitcher.generalTab.generalBehavior.highlightExecutedMacros="Highlight recently executed macros"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
AdvSceneSwitcher.generalTab.priority.threadPriority="Use thread priority"
@@ -80,6 +79,10 @@ AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"
AdvSceneSwitcher.macroTab.minimize="Minimize"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
AdvSceneSwitcher.macroTab.disableHotkeys="Register hotkeys to control pause state of selected macro"
; Macro Logic
AdvSceneSwitcher.logic.none="Ignore entry"
@@ -113,6 +116,7 @@ AdvSceneSwitcher.condition.scene.type.previous="Previous scene is"
AdvSceneSwitcher.condition.scene.type.changed="Scene changed"
AdvSceneSwitcher.condition.scene.type.notChanged="Scene has not changed"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="During transition check for transition target scene"
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Window"
@@ -125,7 +129,8 @@ AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDat
AdvSceneSwitcher.condition.media="Media"
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
AdvSceneSwitcher.condition.media.allOnScene="All media sources on"
AdvSceneSwitcher.condition.media.matchOnChange="Only match on change (Note: This option will be removed in a future version - please use time constraints instead)"
AdvSceneSwitcher.condition.media.matchOnChange="Only match on change (Note: This option will be removed in a future version - please use duration modifiers instead)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Unfortunately not all media source types behave the same (e.g. Media Source vs. VLC Video Source \"Stopped\" state).\nSo please experiment what works for your setup!"
AdvSceneSwitcher.condition.media.entry="{{mediaSources}}{{scenes}} state is {{states}} and {{timeRestrictions}} {{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="exactly matches"
@@ -158,9 +163,14 @@ 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.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area"
AdvSceneSwitcher.condition.video.selectArea.status="Only highlighted area will be checked"
AdvSceneSwitcher.condition.video.width="Width"
AdvSceneSwitcher.condition.video.height="Height"
AdvSceneSwitcher.condition.stream="Streaming"
AdvSceneSwitcher.condition.stream.state.start="Stream running"
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
@@ -259,9 +269,11 @@ AdvSceneSwitcher.condition.date.ignoreDate="If unchecked the date component will
AdvSceneSwitcher.condition.date.ignoreTime="If unchecked the time component will be ignored"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
AdvSceneSwitcher.condition.date.entry.simple="On {{dayOfWeek}} at {{weekTime}}"
AdvSceneSwitcher.condition.date.entry.simple="On {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repeat every {{duration}} on date match"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
@@ -325,7 +337,12 @@ AdvSceneSwitcher.action.audio.type.mute="Mute"
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
AdvSceneSwitcher.action.audio.fade="{{fade}}Fade over {{duration}} seconds"
AdvSceneSwitcher.action.audio.fade.type.duration="over a duration of"
AdvSceneSwitcher.action.audio.fade.type.rate="at a rate of"
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade {{fadeTypes}} {{duration}} seconds."
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}per second."
AdvSceneSwitcher.action.audio.fade.wait="Wait for fade to complete."
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Recording"
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
@@ -335,6 +352,7 @@ AdvSceneSwitcher.action.recording.type.unpause="Unpause recording"
AdvSceneSwitcher.action.recording.pause.hint="Note that depending on your recording settings you might not be able to pause recording"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{pauseHint}}"
AdvSceneSwitcher.action.replay="Replay buffer"
AdvSceneSwitcher.action.replay.saveWarn="Warning: Saving too frequently might result in the replay buffer not actually being saved!"
AdvSceneSwitcher.action.replay.type.stop="Stop replay buffer"
AdvSceneSwitcher.action.replay.type.start="Start replay buffer"
AdvSceneSwitcher.action.replay.type.save="Save replay buffer"
@@ -374,7 +392,8 @@ AdvSceneSwitcher.action.media.type.stop="Stop"
AdvSceneSwitcher.action.media.type.restart="Restart"
AdvSceneSwitcher.action.media.type.next="Next"
AdvSceneSwitcher.action.media.type.previous="Previous"
AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}"
AdvSceneSwitcher.action.media.type.seek="Seek to"
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pause"
AdvSceneSwitcher.action.macro.type.unpause="Unpause"
@@ -418,13 +437,20 @@ AdvSceneSwitcher.action.file="File"
AdvSceneSwitcher.action.file.type.write="Write"
AdvSceneSwitcher.action.file.type.append="Append"
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.studioMode="Studio mode"
AdvSceneSwitcher.action.studioMode.type.swap="Swap preview and program scene"
AdvSceneSwitcher.action.studioMode.type.setScene="Set preview scene to"
AdvSceneSwitcher.action.studioMode.type.enable="Enable studio mode"
AdvSceneSwitcher.action.studioMode.type.disable="Disable studio mode"
AdvSceneSwitcher.action.studioMode.entry="{{actions}}{{scenes}}"
AdvSceneSwitcher.action.transition="Transition"
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Set transition type to {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Set transition duration to {{duration}}seconds"
AdvSceneSwitcher.action.transition.type.scene="scene transition"
AdvSceneSwitcher.action.transition.type.sceneOverride="scene transition override"
AdvSceneSwitcher.action.transition.type.sourceShow="source show transition"
AdvSceneSwitcher.action.transition.type.sourceHide="source hide transition"
AdvSceneSwitcher.action.transition.entry.line1="Modify {{type}}{{scenes}}{{sources}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Set transition type to {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Set transition duration to {{duration}}seconds"
AdvSceneSwitcher.action.timer="Timer"
AdvSceneSwitcher.action.timer.type.pause="Pause"
AdvSceneSwitcher.action.timer.type.continue="Continue"
@@ -515,7 +541,7 @@ AdvSceneSwitcher.mediaTab.states.Paused="Paused"
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
AdvSceneSwitcher.mediaTab.states.ended="Ended"
AdvSceneSwitcher.mediaTab.states.error="Error"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Played to end"
AdvSceneSwitcher.mediaTab.states.playlistEnd="Ended(Playlist)"
AdvSceneSwitcher.mediaTab.states.any="Any"
AdvSceneSwitcher.mediaTab.timeRestriction.none="None"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Time shorter"
@@ -622,7 +648,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to
AdvSceneSwitcher.networkTab.title="Network"
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel."
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
@@ -695,6 +720,8 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the
AdvSceneSwitcher.hotkey.macro.pause="Pause macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Unpause macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Toggle pause of macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Move macro segment selection up"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Move macro segment selection down"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remove selected macro segment"
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
@@ -714,6 +741,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
AdvSceneSwitcher.selectSource="--select source--"
AdvSceneSwitcher.selectAudioSource="--select audio source--"
AdvSceneSwitcher.selectVideoSource="--select video source--"
AdvSceneSwitcher.OBSVideoOutput="OBS video output"
AdvSceneSwitcher.selectMediaSource="--select media source--"
AdvSceneSwitcher.selectProcess="--select process--"
AdvSceneSwitcher.selectFilter="--select filter--"
@@ -741,7 +769,8 @@ AdvSceneSwitcher.unit.milliseconds="milliseconds"
AdvSceneSwitcher.unit.secends="seconds"
AdvSceneSwitcher.unit.minutes="minutes"
AdvSceneSwitcher.unit.hours="hours"
AdvSceneSwitcher.duration.condition.none="No time constraint"
AdvSceneSwitcher.duration.condition.none="No duration modifier"
AdvSceneSwitcher.duration.condition.more="For at least"
AdvSceneSwitcher.duration.condition.equal="For exactly"
AdvSceneSwitcher.duration.condition.less="For at most"
AdvSceneSwitcher.duration.condition.within="Within the last"

766
data/locale/es-ES.ini Normal file
View File

@@ -0,0 +1,766 @@
AdvSceneSwitcher.pluginName="Advanced Scene Switcher"
AdvSceneSwitcher.windowTitle="Advanced Scene Switcher"
; General Tab
AdvSceneSwitcher.generalTab.title="General"
AdvSceneSwitcher.generalTab.status="Estado"
AdvSceneSwitcher.generalTab.status.hotkeytips="Los atajos pueden ser definidos en los ajustes de OBS"
AdvSceneSwitcher.generalTab.status.currentStatus="Advanced Scene Switcher está:"
AdvSceneSwitcher.generalTab.status.onStartup="Al iniciar OBS:"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Empezar el selector de escenas si no estaba iniciado"
AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Siempre iniciar el selector de escenas"
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="No iniciar el selector de escenas"
AdvSceneSwitcher.generalTab.status.start="Iniciar"
AdvSceneSwitcher.generalTab.status.stop="Detener"
AdvSceneSwitcher.generalTab.status.autoStart="Iniciar automáticamente el selector de escenas cuando:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Nunca"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Grabando"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Emitiendo"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Grabando o emitiendo"
AdvSceneSwitcher.generalTab.status.checkInterval="Comprobar las condiciones de cambio cada"
AdvSceneSwitcher.generalTab.generalBehavior="Comportamiento general"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="Si no se cumple ninguna condición por"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Solo será tan preciso como el intervalo de comprobación configurado."
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="No cambiar"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Cambiar a una escena aleatoria"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Cambiar a:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Después de una coincidencia, no cambiar de escena durante"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="¡Durante este tiempo, se ignorarán las posibles coincidencias!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Habilitar el registro detallado"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Guardar la posición y el tamaño de la ventana"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Mostrar notificaciones del sistema"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deshabilitar sugerencias de interfaz de usuario"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Ocultar pestañas que se pueden representar a través de macros"
AdvSceneSwitcher.generalTab.priority="Prioridad"
AdvSceneSwitcher.generalTab.priority.description="Prioridad de métodos de conmutación (la prioridad más alta está en la parte superior)"
AdvSceneSwitcher.generalTab.priority.threadPriority="Usar prioridad de subproceso"
AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(No se recomienda elevar la prioridad por encima de \"Normal\")"
AdvSceneSwitcher.generalTab.saveOrLoadsettings="Guardar / Cargar ajustes"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="Exportar"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="Importar"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="Exportar los ajustes para Advanced Scene Switcher ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="Importar los ajustes para Advanced Scene Switcher ..."
AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="Archivos de texto (*.txt)"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="La importacion la configuracion de escenas fallo"
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="Configuración de escenas importada correctamente"
AdvSceneSwitcher.generalTab.priority.fileContent="Contenido del archivo"
AdvSceneSwitcher.generalTab.priority.sceneSequence="Secuencia de escenas"
AdvSceneSwitcher.generalTab.priority.idleDetection="Detección de inactivos"
AdvSceneSwitcher.generalTab.priority.executable="Ejecutable"
AdvSceneSwitcher.generalTab.priority.screenRegion="Región de pantalla"
AdvSceneSwitcher.generalTab.priority.windowTitle="Título de la ventana"
AdvSceneSwitcher.generalTab.priority.media="Medios"
AdvSceneSwitcher.generalTab.priority.time="Tiempo"
AdvSceneSwitcher.generalTab.priority.audio="Audio"
AdvSceneSwitcher.generalTab.priority.video="Video"
AdvSceneSwitcher.generalTab.priority.macro="Macro"
; Macro Tab
AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.priorityWarning="Nota: Se recomienda configurar las macros para que sean la funcionalidad de máxima prioridad.\nEsta configuración se puede cambiar en la ficha General."
AdvSceneSwitcher.macroTab.help="Las macros le permiten ejecutar una cadena de acciones en función de varias condiciones.\n\nHaga clic en el símbolo más resaltado para agregar una nueva macro."
AdvSceneSwitcher.macroTab.editConditionHelp="Esta sección le permite definir las condiciones de la macro.\n\nSeleccione una macro existente o agregue una nueva a la izquierda.\nLuego haga clic en el botón más a continuación para agregar una nueva condición."
AdvSceneSwitcher.macroTab.editActionHelp="Esta sección le permite definir acciones de la macro.\n\nSeleccione una macro existente o agregue una nueva a la izquierda.\nLuego haga clic en el botón más a continuación para agregar una nueva acción"
AdvSceneSwitcher.macroTab.edit="Editar macro"
AdvSceneSwitcher.macroTab.edit.logic="Tipo de lógica:"
AdvSceneSwitcher.macroTab.edit.condition="Tipo de condición:"
AdvSceneSwitcher.macroTab.edit.action="Tipo de acción:"
AdvSceneSwitcher.macroTab.add="Agregar nueva macro"
AdvSceneSwitcher.macroTab.name="Nombre:"
AdvSceneSwitcher.macroTab.run="Ejecutar macro"
AdvSceneSwitcher.macroTab.runFail="Error al ejecutar \"%1\" si se ha producido un error en una de las acciones o si la macro ya se está ejecutando."
AdvSceneSwitcher.macroTab.runInParallel="Ejecutar macro en paralelo a otras macros"
AdvSceneSwitcher.macroTab.onChange="Realizar acciones solo en el cambio de condición"
AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.exists="El nombre de la macro ya existe"
AdvSceneSwitcher.macroTab.copy="Crear copia"
AdvSceneSwitcher.macroTab.expandAll="Expandir todo"
AdvSceneSwitcher.macroTab.collapseAll="Contraer todo"
AdvSceneSwitcher.macroTab.maximize="Maximizar"
AdvSceneSwitcher.macroTab.minimize="Minimizar"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Resaltar macros ejecutadas recientemente"
AdvSceneSwitcher.macroTab.highlightTrueConditions="Resaltar condiciones de la macro seleccionada, que se evaluaron como verdaderas recientemente"
AdvSceneSwitcher.macroTab.highlightPerformedActions="Resaltar acciones realizadas recientemente de la macro seleccionada actualmente"
; Lógica de macros
AdvSceneSwitcher.logic.none="Omitir entrada"
AdvSceneSwitcher.logic.and="Y"
AdvSceneSwitcher.logic.or="O"
AdvSceneSwitcher.logic.andNot="Y no"
AdvSceneSwitcher.logic.orNot="O no"
AdvSceneSwitcher.logic.rootNone="Si"
AdvSceneSwitcher.logic.not="Si no"
; Macro Conditions
AdvSceneSwitcher.condition.audio="Audio"
AdvSceneSwitcher.condition.audio.state.below="abajo"
AdvSceneSwitcher.condition.audio.state.exact="exactamente"
AdvSceneSwitcher.condition.audio.state.above="arriba"
AdvSceneSwitcher.condition.audio.state.mute="silenciado"
AdvSceneSwitcher.condition.audio.state.unmute="no silenciado"
AdvSceneSwitcher.condition.audio.type.output="Volumen de salida"
AdvSceneSwitcher.condition.audio.type.volume="Nivel de volumen configurado"
AdvSceneSwitcher.condition.audio.entry="{{checkType}} de {{audioSources}} es {{condition}} {{volume}}"
AdvSceneSwitcher.condition.cursor="Cursor"
AdvSceneSwitcher.condition.cursor.type.region="está en la región"
AdvSceneSwitcher.condition.cursor.type.moving="se está moviendo"
AdvSceneSwitcher.condition.cursor.showFrame="Mostrar cuadro"
AdvSceneSwitcher.condition.cursor.hideFrame="Ocultar cuadro"
AdvSceneSwitcher.condition.cursor.entry.line1="El cursor es {{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
AdvSceneSwitcher.condition.cursor.entry.line2="El cursor se encuentra actualmente en {{xPos}} x {{yPos}}"
AdvSceneSwitcher.condition.scene="Escena"
AdvSceneSwitcher.condition.scene.type.current="La escena actual es"
AdvSceneSwitcher.condition.scene.type.previous="La escena anterior es"
AdvSceneSwitcher.condition.scene.type.changed="Escena cambiada"
AdvSceneSwitcher.condition.scene.type.notChanged="La escena no ha cambiado"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Durante la transición, verifique la escena de destino de la transición"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Ventana"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existen y ..."
AdvSceneSwitcher.condition.window.entry.line2="... es {{fullscreen}} pantalla completa {{maximized}} maximizada {{focused}} enfocada {{windowFocusChanged}} ventana de primer plano cambiada"
AdvSceneSwitcher.condition.file="Archivo"
AdvSceneSwitcher.condition.file.entry.line1="Contenido de {{fileType}} {{filePath}} coincidencias:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Medios"
AdvSceneSwitcher.condition.media.anyOnScene="Cualquier fuente multimedia activada"
AdvSceneSwitcher.condition.media.allOnScene="Todas las fuentes de medios activadas"
AdvSceneSwitcher.condition.media.matchOnChange="Solo coincidir con el cambio (Nota: esta opción se eliminará en una versión futura; utilice las restricciones de tiempo en su lugar)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="Desafortunadamente, no todos los tipos de fuentes de medios se comportan de la misma manera (p. ej., fuente de medios frente a estado \"Detenido\" de fuente de video VLC).\n¡Así que experimente lo que funciona para su configuración!"
AdvSceneSwitcher.condition.media.entry="El estado de {{mediaSources}}{{scenes}} es {{states}} y {{timeRestrictions}} {{time}}"
AdvSceneSwitcher.condition.video="Video"
AdvSceneSwitcher.condition.video.condition.match="coincide exactamente"
AdvSceneSwitcher.condition.video.condition.differ="no coincide"
AdvSceneSwitcher.condition.video.condition.hasChanged="ha cambiado"
AdvSceneSwitcher.condition.video.condition.hasNotChanged="no ha cambiado"
AdvSceneSwitcher.condition.video.condition.noImage="no tiene salida"
AdvSceneSwitcher.condition.video.condition.pattern="coincide con el patrón"
AdvSceneSwitcher.condition.video.condition.object="contiene objeto"
AdvSceneSwitcher.condition.video.askFileAction="¿Desea utilizar un archivo existente o crear una captura de pantalla de la fuente seleccionada actualmente?"
AdvSceneSwitcher.condition.video.askFileAction.file="Usar archivo existente"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Crear captura de pantalla"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Usar coincidencia de patrones"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="Esto le permitirá controlar cuánto debe cambiar la imagen para que la condición sea verdadera".
AdvSceneSwitcher.condition.video.patternThreshold="Umbral: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="Un valor de umbral más alto significa que el patrón debe coincidir más estrechamente con la fuente de video".
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Usar canal alfa como máscara para el patrón."
AdvSceneSwitcher.condition.video.objectScaleThreshold="Factor de escala: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="Un factor de escala más bajo generará más coincidencias pero una mayor carga de CPU".
AdvSceneSwitcher.condition.video.minNeighborDescription="Un valor de vecinos mínimo más alto dará como resultado menos coincidencias pero de mayor calidad".
AdvSceneSwitcher.condition.video.showMatch="Mostrar coincidencia"
AdvSceneSwitcher.condition.video.showMatch.loading="Buscando coincidencias"
AdvSceneSwitcher.condition.video.screenshotFail="¡Error al obtener la captura de pantalla de la fuente!"
AdvSceneSwitcher.condition.video.screenshotEmpty="La captura de pantalla está vacía. ¿Está visible la fuente?"
AdvSceneSwitcher.condition.video.patternMatchFail="¡No se encontró el patrón!"
AdvSceneSwitcher.condition.video.patternMatchSuccess="El patrón está resaltado en rojo"
AdvSceneSwitcher.condition.video.objectMatchFail="¡No se encontró el objeto!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="El objeto está resaltado en rojo"
AdvSceneSwitcher.condition.video.modelLoadFail="¡No se pudieron cargar los datos del modelo!"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Mínimo de vecinos: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}"
AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:"
AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:"
AdvSceneSwitcher.condition.video.selectArea="Seleccionar área"
AdvSceneSwitcher.condition.video.selectArea.status="Solo se comprobará el área resaltada"
AdvSceneSwitcher.condition.video.width="Ancho"
AdvSceneSwitcher.condition.video.height="Altura"
AdvSceneSwitcher.condition.stream="Transmisión"
AdvSceneSwitcher.condition.stream.state.start="Transmisión en ejecución"
AdvSceneSwitcher.condition.stream.state.stop="Transmisión detenida"
AdvSceneSwitcher.condition.stream.state.starting="Inicio de transmisión"
AdvSceneSwitcher.condition.stream.state.stopping="Detener transmisión"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}"
AdvSceneSwitcher.condition.record="Grabación"
AdvSceneSwitcher.condition.record.state.start="Grabación en ejecución"
AdvSceneSwitcher.condition.record.state.pause="Grabación en pausa"
AdvSceneSwitcher.condition.record.state.stop="Grabación detenida"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="Proceso"
AdvSceneSwitcher.condition.process.entry="{{processes}} se está ejecutando {{focused}} y está enfocado"
AdvSceneSwitcher.condition.idle="Inactivo"
AdvSceneSwitcher.condition.idle.entry="No hay entradas de teclado o ratón durante {{duration}}"
AdvSceneSwitcher.condition.pluginState="Estado del complemento"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Se activó un cambio de escena automático en este intervalo"
AdvSceneSwitcher.condition.pluginState.state.running="El conmutador de escenas avanzado se está ejecutando"
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS se está cerrando"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
AdvSceneSwitcher.condition.timer="Temporizador"
AdvSceneSwitcher.condition.timer.type.fixed="Fijar"
AdvSceneSwitcher.condition.timer.type.random="Aleatorio"
AdvSceneSwitcher.condition.timer.pause="Pausa"
AdvSceneSwitcher.condition.timer.continue="Continuar"
AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}} la duración de {{duration}} ha pasado"
AdvSceneSwitcher.condition.timer.entry.line1.random="La duración de {{type}} de {{duration}} a {{duration2}} ha pasado"
AdvSceneSwitcher.condition.timer.entry.line2="Tiempo restante: {{remaining}} segundos"
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Ahorra tiempo restante {{autoReset}} Restablecer el temporizador después de alcanzar la duración"
AdvSceneSwitcher.condition.timer.reset="Reiniciar"
AdvSceneSwitcher.condition.macro="Macro"
AdvSceneSwitcher.condition.macro.type.count="Recuento"
AdvSceneSwitcher.condition.macro.type.state="Estado"
AdvSceneSwitcher.condition.macro.type.selection="Tipo de condición: {{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Menor que"
AdvSceneSwitcher.condition.macro.count.type.above="Más que"
AdvSceneSwitcher.condition.macro.count.type.equal="Exactamente"
AdvSceneSwitcher.condition.macro.count.reset="Reiniciar"
AdvSceneSwitcher.condition.macro.pausedWarning="¡La macro seleccionada está actualmente en pausa!"
AdvSceneSwitcher.condition.macro.state.entry="Las condiciones de {{macros}} son verdaderas"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} se ejecutó {{conditions}} {{count}} veces"
AdvSceneSwitcher.condition.macro.count.entry.line2="Recuento actual: {{currentCount}} {{resetCount}}"
AdvSceneSwitcher.condition.source="Fuente"
AdvSceneSwitcher.condition.source.type.active="Está activo"
AdvSceneSwitcher.condition.source.type.showing="Se muestra"
AdvSceneSwitcher.condition.source.type.settings="Coincidencia de configuración"
AdvSceneSwitcher.condition.source.regex="Usar expresiones regulares"
AdvSceneSwitcher.condition.source.getSettings="Obtener la configuración actual"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.source.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.virtualCamera="Cámara virtual"
AdvSceneSwitcher.condition.virtualCamera.state.start="Cámara virtual iniciada"
AdvSceneSwitcher.condition.virtualCamera.state.stop="Cámara virtual detenida"
AdvSceneSwitcher.condition.virtualCamera.entry="{{states}}"
AdvSceneSwitcher.condition.filter="Filtro"
AdvSceneSwitcher.condition.filter.type.active="Está habilitado"
AdvSceneSwitcher.condition.filter.type.showing="Está deshabilitado"
AdvSceneSwitcher.condition.filter.type.settings="Coincidencia de configuración"
AdvSceneSwitcher.condition.filter.regex="Usar expresiones regulares"
AdvSceneSwitcher.condition.filter.getSettings="Obtener la configuración actual"
AdvSceneSwitcher.condition.filter.entry.line1="En {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneOrder="Orden de elementos de escena"
AdvSceneSwitcher.condition.sceneOrder.type.above="Está arriba"
AdvSceneSwitcher.condition.sceneOrder.type.below="Está debajo"
AdvSceneSwitcher.condition.sceneOrder.type.position="Está en la posición"
AdvSceneSwitcher.condition.sceneOrder.positionInfo="El valor de posición comienza en la parte inferior con 0 y aumenta en uno para cada elemento de la escena, incluidos los de los grupos de escenas"
AdvSceneSwitcher.condition.sceneOrder.entry="En{{scenes}}{{sources}}{{conditions}}{{sources2}}{{position}}"
AdvSceneSwitcher.condition.hotkey="Tecla de acceso rápido"
AdvSceneSwitcher.condition.hotkey.name="Tecla de acceso directo de activación de macro"
AdvSceneSwitcher.condition.hotkey.tip="Nota: puede configurar las combinaciones de teclas para esta tecla de acceso rápido en la ventana de configuración de OBS"
AdvSceneSwitcher.condition.hotkey.entry.line1="Se presiona la tecla de acceso rápido"
AdvSceneSwitcher.condition.hotkey.entry.line2="Nombre: {{name}}"
AdvSceneSwitcher.condition.replay="Búfer de reproducción"
AdvSceneSwitcher.condition.replay.state.stopped="Búfer de reproducción detenido"
AdvSceneSwitcher.condition.replay.state.started="Búfer de reproducción iniciado"
AdvSceneSwitcher.condition.replay.state.saved="Búfer de reproducción guardado"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="Fecha"
AdvSceneSwitcher.condition.date.anyDay="Cualquier día"
AdvSceneSwitcher.condition.date.monday="Lunes"
AdvSceneSwitcher.condition.date.tuesday="Martes"
AdvSceneSwitcher.condition.date.wednesday="Miércoles"
AdvSceneSwitcher.condition.date.thursday="Jueves"
AdvSceneSwitcher.condition.date.friday="Viernes"
AdvSceneSwitcher.condition.date.saturday="Sábado"
AdvSceneSwitcher.condition.date.sunday="Domingo"
AdvSceneSwitcher.condition.date.state.at="A las"
AdvSceneSwitcher.condition.date.state.after="Después"
AdvSceneSwitcher.condition.date.state.before="Antes"
AdvSceneSwitcher.condition.date.state.between="Entre"
AdvSceneSwitcher.condition.date.separator="y"
AdvSceneSwitcher.condition.date.ignoreDate="Si no se marca, se ignorará el componente de fecha"
AdvSceneSwitcher.condition.date.ignoreTime="Si no se marca, se ignorará el componente de tiempo"
AdvSceneSwitcher.condition.date.showAdvancedSettings="Mostrar configuración avanzada"
AdvSceneSwitcher.condition.date.showSimpleSettings="Mostrar configuración simple"
AdvSceneSwitcher.condition.date.entry.simple="El {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repetir cada {{duration}} en la coincidencia de fechas"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Próxima coincidencia en: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} Al repetir actualizar la fecha seleccionada para repetir la fecha"
AdvSceneSwitcher.condition.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.condition.sceneTransform.regex="Usar expresiones regulares"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="En{{scenes}}{{sources}}coincide con la transformación"
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.transition="Transición"
AdvSceneSwitcher.condition.transition.type.current="El tipo de transición actual es"
AdvSceneSwitcher.condition.transition.type.duration="La duración de la transición actual es"
AdvSceneSwitcher.condition.transition.type.started="Transición iniciada"
AdvSceneSwitcher.condition.transition.type.ended="Transición finalizada"
AdvSceneSwitcher.condition.transition.type.transitionSource="Transición desde"
AdvSceneSwitcher.condition.transition.type.transitionTarget="Transición a"
AdvSceneSwitcher.condition.transition.durationSuffix="segundos"
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scenes}}{{duration}}{{durationSuffix}}"
AdvSceneSwitcher.condition.sceneVisibility="Visibilidad del elemento de escena"
AdvSceneSwitcher.condition.sceneVisibility.type.shown="Mostrado"
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="Oculto"
AdvSceneSwitcher.condition.sceneVisibility.entry="En{{scenes}}{{sources}}es{{conditions}}"
AdvSceneSwitcher.condition.studioMode="Modo de estudio"
AdvSceneSwitcher.condition.studioMode.state.active="El modo de estudio está activo"
AdvSceneSwitcher.condition.studioMode.state.notActive="El modo de estudio no está activo"
AdvSceneSwitcher.condition.studioMode.state.previewScene="La vista previa de la escena es"
AdvSceneSwitcher.condition.studioMode.entry="{{conditions}}{{scenes}}"
AdvSceneSwitcher.condition.openvr="OpenVR"
AdvSceneSwitcher.condition.errorStatus="Error de OpenVR: "
AdvSceneSwitcher.condition.openvr.entry.line1="HMD está en..."
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
AdvSceneSwitcher.condition.openvr.entry.line3="HMD se encuentra actualmente en {{xPos}} x {{yPos}} x {{zPos}}"
AdvSceneSwitcher.condition.stats="Estadísticas OBS"
AdvSceneSwitcher.condition.stats.type.fps="FPS"
AdvSceneSwitcher.condition.stats.type.CPUUsage="Uso de CPU"
AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable="Espacio en disco disponible"
AdvSceneSwitcher.condition.stats.type.memoryUsage="Uso de memoria"
AdvSceneSwitcher.condition.stats.type.averageTimeToRender="Tiempo medio para renderizar fotograma"
AdvSceneSwitcher.condition.stats.type.skippedFrames="Fotogramas omitidos debido a un retraso en la codificación"
AdvSceneSwitcher.condition.stats.type.missedFrames="Frames perdidos debido al retraso en el procesamiento"
AdvSceneSwitcher.condition.stats.type.droppedFrames.stream="Transmitir fotogramas perdidos"
AdvSceneSwitcher.condition.stats.type.megabytesSent.stream="Transmisión total de salida de datos"
AdvSceneSwitcher.condition.stats.type.bitrate.stream="Tasa de bits de transmisión"
AdvSceneSwitcher.condition.stats.type.droppedFrames.recording="Grabando fotogramas caídos"
AdvSceneSwitcher.condition.stats.type.megabytesSent.recording="Grabando salida de datos total"
AdvSceneSwitcher.condition.stats.type.bitrate.recording="Tasa de bits de grabación"
AdvSceneSwitcher.condition.stats.condition.above="arriba de"
AdvSceneSwitcher.condition.stats.condition.equals="igual a"
AdvSceneSwitcher.condition.stats.condition.below="abajo de"
AdvSceneSwitcher.condition.stats.dockHint="Puede abrir el panel de \"Estadísticas\" para ver el estado actual"
AdvSceneSwitcher.condition.stats.entry="{{stats}} esta {{condition}} {{value}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Cambiar escena"
AdvSceneSwitcher.action.scene.entry="Cambiar a la escena {{scenes}} usando {{transitions}} con una duración de {{duration}} segundos"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Espere hasta que se complete la transición a la escena de destino"
AdvSceneSwitcher.action.wait="Esperar"
AdvSceneSwitcher.action.wait.type.fixed="fijo"
AdvSceneSwitcher.action.wait.type.random="aleatorio"
AdvSceneSwitcher.action.wait.entry.fixed="Espere {{waitType}} de {{duration}}"
AdvSceneSwitcher.action.wait.entry.random="Espere {{waitType}} de {{duration}} a {{duration2}}"
AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Silencio"
AdvSceneSwitcher.action.audio.type.unmute="Activar silencio"
AdvSceneSwitcher.action.audio.type.sourceVolume="Establecer volumen de origen"
AdvSceneSwitcher.action.audio.type.masterVolume="Establecer volumen maestro"
AdvSceneSwitcher.action.audio.fade.type.duration="durante una duración de"
AdvSceneSwitcher.action.audio.fade.type.rate="a una velocidad de"
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade {{fadeTypes}} {{duration}} segundos."
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}por segundo."
AdvSceneSwitcher.action.audio.fade.wait="Espere a que se complete el desvanecimiento".
AdvSceneSwitcher.action.audio.fade.abort="Cancelar atenuación ya activa."
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Grabando"
AdvSceneSwitcher.action.recording.type.stop="Detener grabación"
AdvSceneSwitcher.action.recording.type.start="Iniciar grabación"
AdvSceneSwitcher.action.recording.type.pause="Pausar grabación"
AdvSceneSwitcher.action.recording.type.unpause="Reanudar grabación"
AdvSceneSwitcher.action.recording.pause.hint="Tenga en cuenta que, dependiendo de la configuración de grabación, es posible que no pueda pausar la grabación"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{pauseHint}}"
AdvSceneSwitcher.action.replay="Búfer de reproducción"
AdvSceneSwitcher.action.replay.type.stop="Detener el búfer de reproducción"
AdvSceneSwitcher.action.replay.type.start="Iniciar búfer de reproducción"
AdvSceneSwitcher.action.replay.type.save="Guardar búfer de reproducción"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
AdvSceneSwitcher.action.run="Ejecutar"
AdvSceneSwitcher.action.run.arguments="Argumentos:"
AdvSceneSwitcher.action.run.addArgument="Agregar argumento"
AdvSceneSwitcher.action.run.addArgumentDescription="Añadir nuevo argumento:"
AdvSceneSwitcher.action.run.entry="Ejecutar {{filePath}}"
AdvSceneSwitcher.action.sceneVisibility="Visibilidad del elemento de escena"
AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera"
AdvSceneSwitcher.action.sceneVisibility.entry="En{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"
AdvSceneSwitcher.action.filter.type.settings="Establecer configuración"
AdvSceneSwitcher.action.filter.entry="En {{sources}} {{actions}} {{filters}}"
AdvSceneSwitcher.action.filter.getSettings="Obtener la configuración actual"
AdvSceneSwitcher.action.source="Fuente"
AdvSceneSwitcher.action.source.type.enable="Habilitar"
AdvSceneSwitcher.action.source.type.disable="Deshabilitar"
AdvSceneSwitcher.action.source.type.settings="Establecer configuración"
AdvSceneSwitcher.action.source.entry="{{actions}} {{sources}}"
AdvSceneSwitcher.action.source.warning="Advertencia: la IU de OBS no puede controlar la habilitación y deshabilitación global de fuentes"
AdvSceneSwitcher.action.source.getSettings="Obtener la configuración actual"
AdvSceneSwitcher.action.media="Medios"
AdvSceneSwitcher.action.media.type.play="Reproducir"
AdvSceneSwitcher.action.media.type.pause="Pausa"
AdvSceneSwitcher.action.media.type.stop="Detener"
AdvSceneSwitcher.action.media.type.restart="Reiniciar"
AdvSceneSwitcher.action.media.type.next="Siguiente"
AdvSceneSwitcher.action.media.type.previous="Anterior"
AdvSceneSwitcher.action.media.type.seek="Buscar"
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
AdvSceneSwitcher.action.macro="Macro"
AdvSceneSwitcher.action.macro.type.pause="Pausa"
AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
AdvSceneSwitcher.action.macro.type.run="Ejecutar"
AdvSceneSwitcher.action.macro.type.stop="Detener"
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
AdvSceneSwitcher.action.pluginState="Estado del complemento"
AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher"
AdvSceneSwitcher.action.pluginState.type.noMatch="Cambiar el comportamiento de no coincidencia:"
AdvSceneSwitcher.action.pluginState.type.import="Importar configuración desde"
AdvSceneSwitcher.action.pluginState.importWarning="Nota: la acción se ignorará mientras se abra la ventana de configuración."
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.virtualCamera="Cámara virtual"
AdvSceneSwitcher.action.virtualCamera.type.stop="Detener cámara virtual"
AdvSceneSwitcher.action.virtualCamera.type.start="Iniciar cámara virtual"
AdvSceneSwitcher.action.virtualCamera.entry="{{actions}}"
AdvSceneSwitcher.action.hotkey="Tecla de acceso rápido"
AdvSceneSwitcher.action.hotkey.leftShift="Mayús a la izquierda"
AdvSceneSwitcher.action.hotkey.rightShift="Mayús a la derecha"
AdvSceneSwitcher.action.hotkey.leftCtrl="Ctrl izquierdo"
AdvSceneSwitcher.action.hotkey.rightCtrl="Ctrl derecho"
AdvSceneSwitcher.action.hotkey.leftAlt="Alt izquierdo"
AdvSceneSwitcher.action.hotkey.rightAlt="Alt derecho"
AdvSceneSwitcher.action.hotkey.leftMeta="Meta izquierda"
AdvSceneSwitcher.action.hotkey.rightMeta="Meta derecho"
AdvSceneSwitcher.action.hotkey.onlyOBS="Enviar pulsación de tecla solo a OBS"
AdvSceneSwitcher.action.hotkey.disabled="No se puede simular la pulsación de teclas: ¡funcionalidad desactivada!"
AdvSceneSwitcher.action.hotkey.entry="Presione {{keys}} durante {{duration}} milisegundos"
AdvSceneSwitcher.action.sceneOrder="Orden de elementos de escena"
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Mover hacia arriba"
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Mover hacia abajo"
AdvSceneSwitcher.action.sceneOrder.type.moveTop="Mover al principio"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Mover al final"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Mover a la posición"
AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}transformar{{sources}}"
AdvSceneSwitcher.action.file="Archivo"
AdvSceneSwitcher.action.file.type.write="Escribir"
AdvSceneSwitcher.action.file.type.append="Agregar"
AdvSceneSwitcher.action.file.entry="{{actions}} a {{filePath}}:"
AdvSceneSwitcher.action.previewScene="Cambiar escena de vista previa"
AdvSceneSwitcher.action.previewScene.entry="Cambiar escena de vista previa a {{scenes}}"
AdvSceneSwitcher.action.SceneSwap="Cambiar escena (modo Estudio)"
AdvSceneSwitcher.action.SceneSwap.entry="Intercambiar Vista Previa y Programa en Modo Estudio"
AdvSceneSwitcher.action.transition="Transición"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Establecer el tipo de transición en {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Establecer la duración de la transición en {{duration}}segundos"
AdvSceneSwitcher.action.timer="Temporizador"
AdvSceneSwitcher.action.timer.type.pause="Pausa"
AdvSceneSwitcher.action.timer.type.continue="Continuar"
AdvSceneSwitcher.action.timer.type.reset="Reiniciar"
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Establecer tiempo restante de"
AdvSceneSwitcher.action.timer.entry="{{timerAction}} temporizadores en {{macros}} {{duration}}"
AdvSceneSwitcher.action.random="Aleatorio"
AdvSceneSwitcher.action.random.entry="Ejecute aleatoriamente cualquiera de las siguientes macros (las macros en pausa se ignoran)"
AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
AdvSceneSwitcher.action.systray.entry="Mostrar notificación: {{message}}"
AdvSceneSwitcher.action.screenshot="Captura de pantalla"
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
AdvSceneSwitcher.action.screenshot.entry="Captura de pantalla {{sources}}"
AdvSceneSwitcher.action.profile="Perfil"
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="Colección de escenas"
AdvSceneSwitcher.action.sceneCollection.entry="Cambiar la colección de escenas activa a {{sceneCollections}}"
AdvSceneSwitcher.action.sceneCollection.warning="Nota: Cualquier acción posterior a esta no se ejecutará, ya que la colección de escenas cambiante también volverá a cargar la configuración del conmutador de escenas.\nLa acción de la colección de escenas se ignorará mientras se abra la ventana de configuración".
AdvSceneSwitcher.action.sequence="Secuencia"
AdvSceneSwitcher.action.sequence.entry="Cada vez que se realiza esta acción, ejecute la siguiente macro de la lista (las macros en pausa se ignoran)"
AdvSceneSwitcher.action.sequence.status="Última macro ejecutada: %1 - Siguiente macro a ejecutar: %2"
AdvSceneSwitcher.action.sequence.status.none="ninguno"
AdvSceneSwitcher.action.sequence.restart="Reiniciar desde el principio una vez que se alcance el final de la lista"
AdvSceneSwitcher.action.sequence.continueFrom="Continuar con el elemento seleccionado"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transición"
AdvSceneSwitcher.transitionTab.setTransitionBy="Al cambiar las transiciones:"
AdvSceneSwitcher.transitionTab.transitionOverride="Establecer anulaciones de transición"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Cambiar tipo de transición activa"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Al menos una opción debe estar habilitada:\n\n - Usar anulaciones de transición\n\n - Cambiar el tipo de transición activo"
AdvSceneSwitcher.transitionTab.transitionForAToB="Utiliza la transición para el cambio de escena automatizado de la escena A a la escena B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body> <p> Estos ajustes <span style =\"font-style: italic; \"> solo </span> surgió a las transiciones causadas por el selector de escenas - Echa un vistazo a <a href =\"https://obsproject.com/forum/resources/transition-table.1174/\"> <span style=\" texto-decoración: subrayado; color: # 268bd2; \"> Tabla de transición </span> </a> si desea configurar esto para cambios de escena manuales. <br/> Los definiciones definidos aquí tienen prioridad sobre los ajustes de transición configurados en cualquier otro lugar del selector de escenas. < br/> <br/> Haz clic en el símbolo más a continuación para agregar una nueva entrada. </p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Cambiar transición si la escena está activa"
AdvSceneSwitcher.transitionTab.entry="Cambiar de {{scenes}} a {{scenes2}} usando {{transitions}} con una duración de {{duration}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Cuando la escena {{scenes}} está activa, cambie la transición de escena predeterminada a {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Haga clic en el símbolo más para añadir una entrada."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Cambiar transición {{defTransitionDelay}} después del cambio de escena."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="El retraso se usa para evitar cambios de escena cancelados, lo que puede suceder si se cambia el tipo de transición mientras una transición aún está en curso".
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pausar"
AdvSceneSwitcher.pauseTab.pauseOnScene="Pausar el selector de escenas en la escena"
AdvSceneSwitcher.pauseTab.pauseInFocus1="Pausar el selector de escena cuando "
AdvSceneSwitcher.pauseTab.pauseInFocus2="esté en foco"
AdvSceneSwitcher.pauseTab.pauseTypeScene="la escena esté activa"
AdvSceneSwitcher.pauseTab.pauseTypeWindow="la ventana esté en foco"
AdvSceneSwitcher.pauseTab.pauseTargetAll="Todas"
AdvSceneSwitcher.pauseTab.pauseEntry="Pausar {{pauseTargets}} cuando {{pauseTypes}} {{scenes}} {{windows}}"
AdvSceneSwitcher.pauseTab.help="En esta pestaña puedes configurar la pausa de métodos de cambio individuales si una escena está activa o una ventana está enfocada.\n\nHaz clic en el símbolo más resaltado para continuar."
; Window Title Tab
AdvSceneSwitcher.windowTitleTab.title="Titulo"
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Introduce el título directo de una ventana o un regex válido. Puedes verificar la sintaxis y las coincidencias para las expresiones regulares usando <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span></a></p></body></html>"
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Ignorar nombre de la ventana"
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
AdvSceneSwitcher.windowTitleTab.fullscreen="si está en pantalla completa"
AdvSceneSwitcher.windowTitleTab.maximized="si está maximizada"
AdvSceneSwitcher.windowTitleTab.focused="si está en foco"
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
AdvSceneSwitcher.windowTitleTab.windowsHelp="Cambia de escena según el título de la ventana de las aplicaciones en ejecución.\nSe pueden seleccionar las siguientes condiciones adicionales:\nLa ventana está en Pantalla completa\nLa ventana está maximizada\nLa ventana está enfocada\n\nHaz clic en el símbolo más resaltado para continuar."
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="Si se ignora el título de una ventana, el selector de escena actuará como si la ventana seleccionada anteriormente todavía estuviera enfocada.\nEsto te permitirá evitar cambios de escena, si cambias con frecuencia a una ventana diferente, lo que no provocará un cambio de escena.\n\nElige una ventana o introduce un título de ventana arriba y haz clic en el símbolo más a continuación para agregarlo a la lista."
; Executable Tab
AdvSceneSwitcher.executableTab.title="Ejecutable"
AdvSceneSwitcher.executableTab.implemented="Implementado por dasOven"
AdvSceneSwitcher.executableTab.requiresFocus="sólo si está en foco"
AdvSceneSwitcher.executableTab.entry="Cuando {{processes}} está ejectuándose cambiar a {{scenes}} usando {{transitions}} {{requiresFocus}}"
AdvSceneSwitcher.executableTab.help="Esta pestaña te permitirá cambiar de escena automáticamente si se está ejecutando un proceso.\nEsto puede ser útil en situaciones en las que el nombre de la ventana puede cambiar o no se conoce.\n\nHaz clic en el símbolo más resaltado para continuar."
; Screen Region Tab
AdvSceneSwitcher.screenRegionTab.title="Región"
AdvSceneSwitcher.screenRegionTab.currentPosition="El cursor está actualmente en:"
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Mostrar marcos de guía"
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Ocultar guide frames"
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="Sin selección"
AdvSceneSwitcher.screenRegionTab.entry="Si el cursor está en {{minX}} {{minY}} x {{maxX}} {{maxY}} cambiar a {{scenes}} usando {{transitions}} a menos que esté en {{excludeScenes}}"
AdvSceneSwitcher.screenRegionTab.help="Esta pestaña te permitirá cambiar escenas automáticamente según la posición actual del cursor del ratón.\n\nHaz clic en el símbolo más resaltado para continuar."
; Media Tab
AdvSceneSwitcher.mediaTab.title="Medios"
AdvSceneSwitcher.mediaTab.implemented="Implementado por Exeldro"
AdvSceneSwitcher.mediaTab.states.none="Ninguno"
AdvSceneSwitcher.mediaTab.states.playing="Reproduciéndose"
AdvSceneSwitcher.mediaTab.states.opening="Abriendo"
AdvSceneSwitcher.mediaTab.states.buffering="Almacenando en búfer"
AdvSceneSwitcher.mediaTab.states.Paused="Pausado"
AdvSceneSwitcher.mediaTab.states.stopped="Detenido"
AdvSceneSwitcher.mediaTab.states.ended="Finalizado"
AdvSceneSwitcher.mediaTab.states.error="Error"
AdvSceneSwitcher.mediaTab.states.playlistEnd="Finalizado(lista de reproducción)"
AdvSceneSwitcher.mediaTab.states.any="Cualquiera"
AdvSceneSwitcher.mediaTab.timeRestriction.none="Ninguno"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Tiempo es más corto"
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Tiempo es más largo"
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Tiempo que quede es más corto"
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Tiempo que quede es más largo"
AdvSceneSwitcher.mediaTab.entry="Cuando el estado de {{mediaSources}} es {{states}} y el {{timeRestrictions}} que {{time}} cambiar a {{scenes}} usando {{transitions}}"
AdvSceneSwitcher.mediaTab.help="Esta pestaña te permitirá cambiar de escena según los estados de las fuentes de medios.\nPor ejemplo, puede volver automáticamente a la escena anterior una vez que la fuente de medios seleccionada finalizó su reproducción.\n\nHaz clic en el símbolo más resaltado para continuar."
; File Tab
AdvSceneSwitcher.fileTab.title="Archivo"
AdvSceneSwitcher.fileTab.readWriteSceneFile="Leer / escribir escena desde / a un archivo"
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Escribe el nombre de la escena actual en este archivo:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Habilitar el cambio de escenas según la entrada de archivos"
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Leer el nombre de la escena a la que se cambiará desde este archivo:"
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Cambiar de escena según el contenido del archivo"
AdvSceneSwitcher.fileTab.remoteFileWarning="Ten en cuenta que si eliges la opción remota, el selector de escenas intentará acceder a la ubicación remota cada x ms como se especifica en la pestaña General."
AdvSceneSwitcher.fileTab.remoteFileWarning1="Ten en cuenta que el selector de escenas intentará acceder a la ubicación remota cada "
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
AdvSceneSwitcher.fileTab.libcurlWarning="¡Error al cargar libcurl! ¡No será posible acceder a archivos remotos!"
AdvSceneSwitcher.fileTab.selectWrite="Selecciona un archivo para escribir ..."
AdvSceneSwitcher.fileTab.selectRead="Seleccione un archivo para leer ..."
AdvSceneSwitcher.fileTab.textFileType="Archivos de texto (*.txt)"
AdvSceneSwitcher.fileTab.anyFileType="Cualquier archivo (*.*)"
AdvSceneSwitcher.fileTab.remote="archivo remoto"
AdvSceneSwitcher.fileTab.local="archivo local"
AdvSceneSwitcher.fileTab.useRegExp="usar expresiones regulares (coincidencia de patrones)"
AdvSceneSwitcher.fileTab.checkfileContentTime="si la fecha de modificación cambió"
AdvSceneSwitcher.fileTab.checkfileContent="si el contenido cambia"
AdvSceneSwitcher.fileTab.entry="Cambiar a {{scenes}} usando {{transitions}} si el contenido de {{fileType}} {{filePath}} {{browseButton}} coincide con:"
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.fileTab.help="Esta pestaña te permitirá cambiar escenas automáticamente según el contenido de archivos remotos o locales.\n\nHaz clic en el símbolo más resaltado para continuar."
; Random Tab
AdvSceneSwitcher.randomTab.title="Aleatorio"
AdvSceneSwitcher.randomTab.randomDisabledWarning="Funcionalidad deshabilitada - para activarla, selecciona \"Si no se cumple ninguna condición de cambio, cambiar a cualquier escena en la pestaña Aleatorio\" en la pestaña General"
AdvSceneSwitcher.randomTab.entry="Si no se cumple ninguna condición de cambio, cambiar a {{scenes}} usando {{transitions}} después de {{delay}}"
AdvSceneSwitcher.randomTab.help="El selector de escenas elegirá aleatoriamente una entrada en esta pestaña para cambiar durante el tiempo configurado.\nTen en cuenta que la misma entrada no se elegirá dos veces seguidas.\n\nHaz clic en el símbolo más resaltado para continuar."
; Time Tab
AdvSceneSwitcher.timeTab.title="Tiempo"
AdvSceneSwitcher.timeTab.anyDay="En cualquier día"
AdvSceneSwitcher.timeTab.mondays="Lunes"
AdvSceneSwitcher.timeTab.tuesdays="Martes"
AdvSceneSwitcher.timeTab.wednesdays="Miércoles"
AdvSceneSwitcher.timeTab.thursdays="Jueves"
AdvSceneSwitcher.timeTab.fridays="Viernes"
AdvSceneSwitcher.timeTab.saturdays="Sábados"
AdvSceneSwitcher.timeTab.sundays="Domingos"
AdvSceneSwitcher.timeTab.afterstart="Empezar al iniciar emisión/grabación"
AdvSceneSwitcher.timeTab.afterstart.tip="Se utilizará el tiempo relativo al inicio de la transmisión / grabación"
AdvSceneSwitcher.timeTab.entry="{{triggers}} a las {{time}} cambiar a {{scenes}} usando {{transitions}}"
AdvSceneSwitcher.timeTab.help="Esta pestaña te permitirá cambiar automáticamente a una escena diferente en función de la hora local actual.\n\nTen en cuenta que el selector de escenas solo cambiará de escena a la hora exacta que especificaste.\nAsegúrate de haber configurado los ajustes de prioridad en la pestaña General a tu gusto para que no se pierda el punto de tiempo seleccionado debido a que otros métodos de cambio tengan una prioridad más alta.\n\nHaz clic en el símbolo más resaltado para continuar."
; Idle Tab
AdvSceneSwitcher.idleTab.title="Inactividad"
AdvSceneSwitcher.idleTab.enable="Habilitar la detección de inactividad"
AdvSceneSwitcher.idleTab.idleswitch="Después de {{duration}} sin entradas de teclado o ratón, cambiar a la escena {{scenes}} usando la {{transitions}}"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="No cambiar si"
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="está en foco"
; Scene Sequence Tab
AdvSceneSwitcher.sceneSequenceTab.title="Secuencia"
AdvSceneSwitcher.sceneSequenceTab.description="Se puede cancelar una secuencia de cambios de escena automática pausando / deteniendo el selector de escenas o cambiando manualmente a una escena diferente"
AdvSceneSwitcher.sceneSequenceTab.save="Guardar secuencias de escenas en un archivo"
AdvSceneSwitcher.sceneSequenceTab.load="Cargar secuencias de escenas desde un archivo"
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Guardar la secuencia de escenas en un archivo ..."
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Seleccionar un archivo para leer la secuencia de escenas ..."
AdvSceneSwitcher.sceneSequenceTab.loadFail="¡Advanced Scene Switcher no ha podido cargar los ajustes!"
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="¡Advanced Scene Switcher ha importando los ajustes correctamente!"
AdvSceneSwitcher.sceneSequenceTab.fileType="Archivos de texto (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.interruptible="interrumpible"
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Se permiten otros métodos de cambio para interrumpir esta secuencia de escenas."
AdvSceneSwitcher.sceneSequenceTab.entry="Cuando {{startScenes}} esté activo, cambiar a {{scenes}} después de {{delay}} usando {{transitions}} {{interruptible}}"
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extender secuencia"
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Después de {{delay}} cambiar a {{scenes}} usando {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="Esta pestaña te permitirá cambiar automáticamente a una escena diferente si una escena estuvo activa durante un período de tiempo configurado.\nPor ejemplo, podrías alternar automáticamente entre dos escenas automáticamente.\n\nHaz clic en el símbolo más resaltado para seguir."
; Audio Tab
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="por encima del"
AdvSceneSwitcher.audioTab.condition.below="por debajo del"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="a menos que la fuente esté inactiva"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Si múltiples entradas coinciden ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... durante {{duration}} cambiar a {{scenes}} usando {{transitions}}"
AdvSceneSwitcher.audioTab.help="Esta pestaña te permitirá cambiar de escena según el volumen de las fuentes.\nPor ejemplo, podrías cambiar automáticamente a una escena diferente si el volumen de su micrófono alcanza un cierto umbral.\n\nHaz clic en el símbolo más resaltado para continuar."
; Video Tab
AdvSceneSwitcher.videoTab.title="Vídeo"
AdvSceneSwitcher.videoTab.getScreenshot="Obtener captura de pantalla para la entrada seleccionada"
AdvSceneSwitcher.videoTab.getScreenshotHelp="Obtén una captura de pantalla de la fuente de vídeo de la entrada actualmente seleccionada y configúrala automáticamente como la imagen de destino"
AdvSceneSwitcher.videoTab.condition.match="coincide exactamente"
AdvSceneSwitcher.videoTab.condition.match.tooltip="Una coincidencia exacta requiere que el objetivo y la imagen de origen tengan la misma resolución.\nAdicionalmente, todos los píxeles deben coincidir, por lo que no se recomienda el uso de formatos de imagen que utilizan compresión (por ejemplo, .JPG)."
AdvSceneSwitcher.videoTab.condition.differ="no coincide"
AdvSceneSwitcher.videoTab.condition.hasNotChanged="no ha cambiado"
AdvSceneSwitcher.videoTab.condition.hasChanged="ha cambiado"
AdvSceneSwitcher.videoTab.ignoreInactiveSource="a menos que la fuente esté inactiva"
AdvSceneSwitcher.videoTab.entry="Cuando {{videoSources}} {{condition}} {{filePath}} {{browseButton}} durante {{duration}} cambiar a {{scenes}} usando {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Esta pestaña te permitirá cambiar escenas según la salida de vídeo actual de las fuentes seleccionadas.<br/>Asegúrate de revisar <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> para una implementación aún mejor de esta funcionalidad.<br/><br/> Haz clic en el símbolo más resaltado para continuar.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Red"
AdvSceneSwitcher.networkTab.description="Esta pestaña le permitirá controlar de forma remota la escena activa de otra instancia de OBS.\nTenga en cuenta que los nombres de las escenas deben coincidir exactamente en todas las instancias de OBS".
AdvSceneSwitcher.networkTab.warning="Ejecutar el servidor fuera de una red local permitirá que terceros lean la escena activa".
AdvSceneSwitcher.networkTab.server="Iniciar servidor (envía mensajes de cambio de escena a todos los clientes conectados)"
AdvSceneSwitcher.networkTab.server.port="Puerto"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Bloquear servidor para usar solo IPv4"
AdvSceneSwitcher.networkTab.server.sendSceneChange="Enviar mensajes para cambios de escena"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Solo enviar mensajes para cambios de escena automatizados"
AdvSceneSwitcher.networkTab.server.sendPreview="Enviar mensajes para obtener una vista previa del cambio de escena cuando se ejecuta en Modo Estudio"
AdvSceneSwitcher.networkTab.startFailed.message="El servidor WebSockets no pudo iniciarse, tal vez porque:\n - El puerto TCP %1 puede estar actualmente en uso en otro lugar de este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en el WebSocket configuración del servidor o detenga cualquier aplicación que pueda estar usando este puerto.\n - Mensaje de error: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Estado actual"
AdvSceneSwitcher.networkTab.server.status.notRunning="Desconectado"
AdvSceneSwitcher.networkTab.server.status.starting="Iniciando"
AdvSceneSwitcher.networkTab.server.status.running="En ejecución"
AdvSceneSwitcher.networkTab.server.restart="Reiniciar servidor"
AdvSceneSwitcher.networkTab.client="Iniciar cliente (Recibe mensajes de cambios de escena)"
AdvSceneSwitcher.networkTab.client.address="Nombre de host o dirección IP"
AdvSceneSwitcher.networkTab.client.port="Puerto"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Estado actual"
AdvSceneSwitcher.networkTab.client.status.disconnected="Desconectado"
AdvSceneSwitcher.networkTab.client.status.connecting="Conectando"
AdvSceneSwitcher.networkTab.client.status.connected="Conectado"
AdvSceneSwitcher.networkTab.client.reconnect="Forzar reconexión"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Grupo de escenas"
AdvSceneSwitcher.sceneGroupTab.list="Grupos de escenas"
AdvSceneSwitcher.sceneGroupTab.edit="Editar grupos de escenas"
AdvSceneSwitcher.sceneGroupTab.edit.name="Nombre:"
AdvSceneSwitcher.sceneGroupTab.edit.type="Tipo: {{type}}"
AdvSceneSwitcher.sceneGroupTab.type.count="Contar"
AdvSceneSwitcher.sceneGroupTab.type.time="Tiempo"
AdvSceneSwitcher.sceneGroupTab.type.random="Al azar"
AdvSceneSwitcher.sceneGroupTab.edit.count="Avanzar a la siguiente escena en la lista después de {{count}} coincidencias"
AdvSceneSwitcher.sceneGroupTab.edit.time="Avanzar a la siguiente escena de la lista después de que haya pasado {{time}}"
AdvSceneSwitcher.sceneGroupTab.edit.random="Elige la siguiente escena en la lista al azar"
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Empezar desde el principio si se llega al final de la lista de escenas"
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Agregar escena"
AdvSceneSwitcher.sceneGroupTab.add="Agregar grupo de escenas"
AdvSceneSwitcher.sceneGroupTab.defaultname="Grupo de escenas %1"
AdvSceneSwitcher.sceneGroupTab.exists="El grupo de escenas o el nombre de la escena ya existe"
AdvSceneSwitcher.sceneGroupTab.help="Los grupos de escenas se pueden seleccionar como un objetivo al igual que una escena normal.\n\nComo sugiere el nombre, un grupo de escenas es una colección de varias escenas.\nEl grupo de escenas avanzará a través de la lista de sus escenas asignadas según los ajustes configurados, que se puede encontrar en el lado derecho.\n\nPuedes configurar el grupo de escenas para avanzar a la siguiente escena en la lista:\nDespués de varias veces, el grupo de escenas se selecciona como objetivo.\nDespués de que un cierto período de tiempo haya pasado.\nO al azar.\n\nPor ejemplo, un grupo de escenas que contiene las escenas ... \nEscena 1 \nEscena 2 \nEscena 3 \n ... activará la \"Escena 1 \" la primera vez que se seleccione como objetivo. \nLa segunda vez se activará \"Escena 2 \". \nLas veces restantes \"Escena 3 \" se activarán. \n\nHaz clic en el símbolo más resaltado a continuación para agregar un nuevo grupo de escenas."
AdvSceneSwitcher.sceneGroupTab.scenes.help="Selecciona el grupo de escenas que deseas modificar a la izquierda. \n\nSelecciona una escena para agregar a este grupo de escenas seleccionando la escena de arriba y haciendo clic en el símbolo más a continuación. \n\nSe puede agregar una escena varias veces al mismo grupo de escenas."
; Scene Trigger Tab
AdvSceneSwitcher.sceneTriggerTab.title="Activadores de escena"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--selecciona el activador--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="esté activo"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="no esté activo"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="cambie a otra escena"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--Selecciona la acción--"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="empezar a grabar"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="pausar la grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="reanudar grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="detener grabación"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="detener transmisión"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="iniciar transmisión"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="iniciar búfer de repetición"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="detener búfer de repetición"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="silenciar fuente"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="dejar de silenciar fuente"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="iniciar el selector de escenas"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="detener el selector de escenas"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="Iniciar Camara Virtual"
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="Detener Camara Virtual"
AdvSceneSwitcher.sceneTriggerTab.entry="Cuando {{scenes}} {{triggers}} {{actions}} {{audioSources}} después de {{duration}}"
AdvSceneSwitcher.sceneTriggerTab.help="Esta pestaña te permite activar acciones sobre cambios de escena, como detener la grabación o la transmisión."
; Hotkey
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Iniciar Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Detener Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Alternar inicio / detención para Advanced Scene Switcher"
AdvSceneSwitcher.hotkey.macro.pause="Pausar macro %1"
AdvSceneSwitcher.hotkey.macro.unpause="Despausar macro %1"
AdvSceneSwitcher.hotkey.macro.togglePause="Alternar pausa de macro %1"
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Mover selección de segmento de macro hacia arriba"
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Mover selección de segmento de macro hacia abajo"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Eliminar segmento de macro seleccionado"
AdvSceneSwitcher.askBackup="Se detectó una nueva versión de Advanced Scene Switcher.\n¿Crear una copia de seguridad de la configuración anterior?"
AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
AdvSceneSwitcher.close="Cerrar"
AdvSceneSwitcher.browse="Explorar"
AdvSceneSwitcher.selectScene="--seleccionar escena--"
AdvSceneSwitcher.selectPreviousScene="Escena anterior"
AdvSceneSwitcher.selectCurrentScene="Escena actual"
AdvSceneSwitcher.selectAnyScene="Cualquier escena"
AdvSceneSwitcher.currentTransition="Transición actual"
AdvSceneSwitcher.anyTransition="Cualquier transición"
AdvSceneSwitcher.selectTransition="--seleccionar transición--"
AdvSceneSwitcher.selectWindow="--seleccionar ventana--"
AdvSceneSwitcher.selectSource="--seleccionar fuente--"
AdvSceneSwitcher.selectAudioSource="--seleccione la fuente de audio--"
AdvSceneSwitcher.selectVideoSource="--seleccione la fuente de video--"
AdvSceneSwitcher.OBSVideoOutput="Salida de vídeo OBS"
AdvSceneSwitcher.selectMediaSource="--seleccione la fuente de medios--"
AdvSceneSwitcher.selectProcess="--seleccionar proceso--"
AdvSceneSwitcher.selectFilter="--seleccionar filtro--"
AdvSceneSwitcher.selectMacro="--seleccionar macro--"
AdvSceneSwitcher.selectItem="--seleccionar elemento--"
AdvSceneSwitcher.selectProfile="--seleccionar perfil--"
AdvSceneSwitcher.selectSceneCollection="--seleccionar colección de escenas--"
AdvSceneSwitcher.enterPath="--ingrese la ruta--"
AdvSceneSwitcher.enterText="--ingresar texto--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="las entradas no válidas no se guardarán"
AdvSceneSwitcher.selectWindowTip="Usa \"OBS \" para especificar la ventana de OBS\nUsa \"Cambiar de tarea \" para especificar ALT + TAB"
AdvSceneSwitcher.sceneItemSelection.all="Todos"
AdvSceneSwitcher.sceneItemSelection.any="Cualquiera"
AdvSceneSwitcher.status.active="Activo"
AdvSceneSwitcher.status.inactive="Inactivo"
AdvSceneSwitcher.running="Iniciar complemento"
AdvSceneSwitcher.stopped="Complemento de detención"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Esta parece ser la primera vez que se inicia el conmutador de escena avanzado.<br>Por favor, eche un vistazo a <a href=\"https:/ /github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> para obtener una lista de guías y ejemplos.<br>No dude en hacer preguntas en el complemento <a href=\"https://obsproject.com /forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">hilo</span></a> en los foros de OBS!</p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="¡Se detuvo el desarrollo de esta pestaña!\nConsidere cambiar a macros en su lugar.\nEsta sugerencia se puede desactivar en la pestaña General".
AdvSceneSwitcher.unit.milliseconds="milisegundos"
AdvSceneSwitcher.unit.secends="segundos"
AdvSceneSwitcher.unit.minutes="minutos"
AdvSceneSwitcher.unit.hours="horas"
AdvSceneSwitcher.duration.condition.none="Sin límite de tiempo"
AdvSceneSwitcher.duration.condition.more="Al menos durante"
AdvSceneSwitcher.duration.condition.equal="Para exactamente"
AdvSceneSwitcher.duration.condition.less="Para como máximo"
;Traducido por: @EliasDipa

View File

@@ -78,23 +78,16 @@ AdvSceneSwitcher.logic.rootNone="Если"
AdvSceneSwitcher.logic.not="Если нет"
; Macro Conditions
AdvSceneSwitcher.condition.audio="Аудио"
AdvSceneSwitcher.ondition.audio.state.below="Ниже"
AdvSceneSwitcher.ondition.audio.state.above="Выше"
AdvSceneSwitcher.condition.audio.entry="Громкость {{audioSources}} равна {{condition}} {{volume}} в течении {{duration}} секунд"
AdvSceneSwitcher.condition.cursor="Область экрана"
AdvSceneSwitcher.condition.scene="Сцена"
AdvSceneSwitcher.condition.scene.type.current="Текущий"
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} сцена является {{scenes}} на {{duration}}"
AdvSceneSwitcher.condition.window="Окно"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..."
AdvSceneSwitcher.condition.window.entry.line2="... это {{fullscreen}} полноэкранный {{maximized}} максимизированный {{focused}} сфокусированный"
AdvSceneSwitcher.condition.file="Файл"
AdvSceneSwitcher.condition.file.entry.line1="Содержимое {{fileType}} {{filePath}} {{browseButton}} соответствует:"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="Медиа"
AdvSceneSwitcher.condition.media.entry="{{mediaSources}} состояние {{states}} и {{timeRestrictions}} {{time}}"
AdvSceneSwitcher.condition.video="Видео"
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
AdvSceneSwitcher.condition.video.condition.differ="не совпадает"
@@ -104,16 +97,13 @@ AdvSceneSwitcher.condition.video.condition.noImage="не имеет вывода
AdvSceneSwitcher.condition.video.askFileAction="Вы хотите использовать существующий файл или создать скриншот текущего выбранного источника?"
AdvSceneSwitcher.condition.video.askFileAction.file="Использовать существующий файл"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Создать скриншот"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}}"
AdvSceneSwitcher.condition.stream="Потоковое вещание"
AdvSceneSwitcher.condition.stream.state.start="Поток запущен"
AdvSceneSwitcher.condition.stream.state.stop="Поток остановлен"
AdvSceneSwitcher.condition.stream.entry="{{streamState}} для {{duration}}"
AdvSceneSwitcher.condition.record="Запись"
AdvSceneSwitcher.condition.record.state.start="Запись запущена"
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
AdvSceneSwitcher.condition.record.entry="{{recordState}} для {{duration}}"
AdvSceneSwitcher.condition.process="Процесс"
AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован"
AdvSceneSwitcher.condition.idle="Простой"
@@ -153,7 +143,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Остановить потоков
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
AdvSceneSwitcher.action.run="Запустить"
AdvSceneSwitcher.action.run.entry="Запустить {{filePath}} {{browseButton}}"
; Transition Tab
@@ -221,7 +210,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="Приостановлено"
AdvSceneSwitcher.mediaTab.states.stopped="Остановлено"
AdvSceneSwitcher.mediaTab.states.ended="Закончилось"
AdvSceneSwitcher.mediaTab.states.error="Ошибка"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Воспроизведено до конца"
AdvSceneSwitcher.mediaTab.states.any="Любой"
AdvSceneSwitcher.mediaTab.timeRestriction.none="Нет"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Время короче"
@@ -327,7 +315,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Эта вкладка по
; Network Tab
AdvSceneSwitcher.networkTab.title="Сеть"
AdvSceneSwitcher.networkTab.warning="Запуск сервера вне локальной сети позволит третьим лицам читать активную сцену."
AdvSceneSwitcher.networkTab.DisabledWarning="ту функциональность, к сожалению, пришлось отключить на macOS из-за несовместимости библиотек при параллельном запуске плагина obs-websocket."
AdvSceneSwitcher.networkTab.server="Запустить сервер (отправляет сообщения о переключении сцены всем подключенным клиентам)"
AdvSceneSwitcher.networkTab.server.port="Порт"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Заблокировать сервер на использование только IPv4"

View File

@@ -92,7 +92,6 @@ AdvSceneSwitcher.condition.audio.state.below="altında"
AdvSceneSwitcher.condition.audio.state.above="üstünde"
AdvSceneSwitcher.condition.audio.state.mute="sessiz"
AdvSceneSwitcher.condition.audio.state.unmute="ses açıldı"
AdvSceneSwitcher.condition.audio.entry="Hacim{{audioSources}} gibi {{condition}} {{volume}}"
AdvSceneSwitcher.condition.cursor="İmleç"
AdvSceneSwitcher.condition.cursor.type.region="alanında"
AdvSceneSwitcher.condition.cursor.type.moving="hareketlidir"
@@ -147,8 +146,6 @@ AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum komşular: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.minSize="Minimum boyut: {{minSizeX}} x {{minSizeY}}"
AdvSceneSwitcher.condition.video.entry.maxSize="Maximum boyut: {{maxSizeX}} x {{maxSizeY}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler"
AdvSceneSwitcher.condition.stream="Yayınlama"
AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor"
@@ -222,7 +219,6 @@ AdvSceneSwitcher.condition.sceneOrder.entry="Açık{{scenes}}{{sources}}{{condit
AdvSceneSwitcher.condition.hotkey="Kısayol tuşu"
AdvSceneSwitcher.condition.hotkey.name="Makro tetik kısayol tuşu"
AdvSceneSwitcher.condition.hotkey.tip="Not: Bu kısayol tuşu için tuş atamalarını OBS ayarları penceresinde yapılandırabilirsiniz."
AdvSceneSwitcher.condition.hotkey.entry="İsim: {{name}}"
AdvSceneSwitcher.condition.replay="Tekrar oynatma arabelleği"
AdvSceneSwitcher.condition.replay.state.stopped="Tekrar oynatma arabelleği durdu"
AdvSceneSwitcher.condition.replay.state.started="Tekrar oynatma arabelleği başladı"
@@ -233,8 +229,6 @@ AdvSceneSwitcher.condition.date.state.at="de"
AdvSceneSwitcher.condition.date.state.after="Sonra"
AdvSceneSwitcher.condition.date.state.before="Önce"
AdvSceneSwitcher.condition.date.state.between="Arasında"
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Tarih bileşenini yoksay {{ignoreTime}} Zaman bileşenini yoksay"
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Tekrar her {{duration}} tarih eşleşmesinde"
AdvSceneSwitcher.condition.sceneTransform="Sahne öğesi dönüşümü"
AdvSceneSwitcher.condition.sceneTransform.getTransform="Dönüşümü al"
AdvSceneSwitcher.condition.sceneTransform.regex="Normal ifadeler kullanın"
@@ -279,7 +273,6 @@ AdvSceneSwitcher.action.audio.type.mute="Sessiz"
AdvSceneSwitcher.action.audio.type.unmute="Ses açmak"
AdvSceneSwitcher.action.audio.type.sourceVolume="Kaynak ses seviyesini ayarla"
AdvSceneSwitcher.action.audio.type.masterVolume="Ana ses seviyesini ayarla"
AdvSceneSwitcher.action.audio.fade="{{fade}}karartmak {{duration}} saniye"
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="Kayıt"
AdvSceneSwitcher.action.recording.type.stop="Kayıt Durdur"
@@ -328,7 +321,7 @@ AdvSceneSwitcher.action.media.type.stop="Dur"
AdvSceneSwitcher.action.media.type.restart="Yeniden Başlat"
AdvSceneSwitcher.action.media.type.next="Sonraki"
AdvSceneSwitcher.action.media.type.previous="Önceki"
AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}"
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
AdvSceneSwitcher.action.macro="Makro"
AdvSceneSwitcher.action.macro.type.pause="Duraklat"
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
@@ -376,8 +369,8 @@ AdvSceneSwitcher.action.previewScene.entry="Önizleme sahnesini şu şekilde de
AdvSceneSwitcher.action.SceneSwap="Sahneyi değiştir (Studyo modu)"
AdvSceneSwitcher.action.SceneSwap.entry="Stüdyo modunda önizleme ve program sahnesini değiştir"
AdvSceneSwitcher.action.transition="Geçiş"
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Geçiş türünü ayarla {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}Geçiş türünü ayarla {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
AdvSceneSwitcher.action.timer="Zamanlayıcı"
AdvSceneSwitcher.action.timer.type.pause="Duraklat"
AdvSceneSwitcher.action.timer.type.continue="Devam et"
@@ -462,7 +455,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="Duraklatıldı"
AdvSceneSwitcher.mediaTab.states.stopped="Durduruldu"
AdvSceneSwitcher.mediaTab.states.ended="Bitti"
AdvSceneSwitcher.mediaTab.states.error="Hata"
AdvSceneSwitcher.mediaTab.states.playedToEnd="Sonuna kadar oynandı"
AdvSceneSwitcher.mediaTab.states.any="Herhangi"
AdvSceneSwitcher.mediaTab.timeRestriction.none="Yok"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Zaman daha kısa"
@@ -569,7 +561,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Bu sekme, seçilen kaynakl
AdvSceneSwitcher.networkTab.title="Ağ"
AdvSceneSwitcher.networkTab.description="Bu sekme, başka bir OBS örneğinin etkin sahnesini uzaktan kontrol etmenizi sağlar.\nSahne adlarının tüm OBS örneklerinde tam olarak eşleşmesi gerektiğini lütfen unutmayın."
AdvSceneSwitcher.networkTab.warning="Sunucuyu yerel bir ağın dışında çalıştırmak, üçüncü tarafların aktif sahneyi okumasına izin verecektir."
AdvSceneSwitcher.networkTab.DisabledWarning="Bu işlevsellik, obs-websocket eklentisini paralel olarak çalıştırırken kitaplık uyumsuzlukları nedeniyle ne yazık ki macOS'ta devre dışı bırakılmak zorunda kaldı."
AdvSceneSwitcher.networkTab.server="Sunucuyu başlat (Bağlı tüm istemcilere sahne değiştirme mesajları gönderir)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Sunucuyu yalnızca IPv4 kullanacak şekilde kilitleyin"

View File

@@ -30,6 +30,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="详细日志输出"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="保存窗口位置和大小"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="显示系统托盘通知"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="禁用UI提示"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="隐藏可通过宏操作的分组栏"
AdvSceneSwitcher.generalTab.priority="优先级"
AdvSceneSwitcher.generalTab.priority.description="切换场景优先级 (最上方的项优先级最高)"
AdvSceneSwitcher.generalTab.priority.threadPriority="使用线程优先级"
@@ -57,6 +58,7 @@ AdvSceneSwitcher.generalTab.priority.macro="宏"
; Macro Tab
AdvSceneSwitcher.macroTab.title="宏"
AdvSceneSwitcher.macroTab.macros="宏文件"
AdvSceneSwitcher.macroTab.priorityWarning="注意:建议将宏配置为最高优先级的功能.\n可以在“常规”选项卡上更改此设置."
AdvSceneSwitcher.macroTab.help="宏允许您根据多种条件执行一系列操作。\n\n单击突出显示的加号可添加新宏."
AdvSceneSwitcher.macroTab.editConditionHelp="此部分允许您定义宏条件。\n\n在左侧选择现有宏或添加新宏。\n然后单击下面的加号按钮添加新条件。"
AdvSceneSwitcher.macroTab.editActionHelp="此部分允许您定义宏操作。\n\n在左侧选择现有宏或添加新宏。\n然后单击下面的加号按钮添加新操作."
@@ -66,11 +68,17 @@ AdvSceneSwitcher.macroTab.edit.condition="条件类型:"
AdvSceneSwitcher.macroTab.edit.action="动作类型:"
AdvSceneSwitcher.macroTab.add="添加新宏"
AdvSceneSwitcher.macroTab.name="名称:"
AdvSceneSwitcher.macroTab.run="运行宏"
AdvSceneSwitcher.macroTab.runFail="运行 \"%1\" 失败!\n其中一个操作失败或者宏已在运行."
AdvSceneSwitcher.macroTab.runInParallel="与其他宏并行运行宏"
AdvSceneSwitcher.macroTab.onChange="仅在条件更改时执行操作"
AdvSceneSwitcher.macroTab.defaultname="宏 %1"
AdvSceneSwitcher.macroTab.exists="宏名称已存在"
AdvSceneSwitcher.macroTab.copy="创建副本"
AdvSceneSwitcher.macroTab.expandAll="全部展开"
AdvSceneSwitcher.macroTab.collapseAll="全部收回"
AdvSceneSwitcher.macroTab.maximize="最大化"
AdvSceneSwitcher.macroTab.minimize="最小化"
; Macro Logic
AdvSceneSwitcher.logic.none="忽略条目"
@@ -83,23 +91,27 @@ AdvSceneSwitcher.logic.not="如果 不"
; Macro Conditions
AdvSceneSwitcher.condition.audio="音频"
AdvSceneSwitcher.condition.audio.state.below="于"
AdvSceneSwitcher.condition.audio.state.below="于"
AdvSceneSwitcher.condition.audio.state.exact="相等"
AdvSceneSwitcher.condition.audio.state.above="大于"
AdvSceneSwitcher.condition.audio.entry="音量 来源 {{audioSources}} 是 {{condition}} {{volume}}"
AdvSceneSwitcher.condition.cursor="光标"
AdvSceneSwitcher.condition.audio.state.mute="静音"
AdvSceneSwitcher.condition.audio.state.unmute="取消静音"
AdvSceneSwitcher.condition.audio.type.output="输出音频"
AdvSceneSwitcher.condition.audio.type.volume="配置音量级别"
AdvSceneSwitcher.condition.cursor="屏幕区域"
AdvSceneSwitcher.condition.cursor.type.region="当前位置"
AdvSceneSwitcher.condition.cursor.type.moving="正在移动"
AdvSceneSwitcher.condition.cursor.showFrame="显示帧率"
AdvSceneSwitcher.condition.cursor.hideFrame="隐藏帧率"
AdvSceneSwitcher.condition.cursor.entry.line1="标 处于{{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
AdvSceneSwitcher.condition.cursor.entry.line2="标当前位于 {{xPos}} x {{yPos}}"
AdvSceneSwitcher.condition.cursor.entry.line1="标 处于{{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
AdvSceneSwitcher.condition.cursor.entry.line2="标当前位于 {{xPos}} x {{yPos}}"
AdvSceneSwitcher.condition.scene="场景"
AdvSceneSwitcher.condition.scene.type.current="当前场景是"
AdvSceneSwitcher.condition.scene.type.previous="上一个场景是"
AdvSceneSwitcher.condition.scene.type.changed="场景改变了"
AdvSceneSwitcher.condition.scene.type.notChanged="场景没有改变"
AdvSceneSwitcher.condition.scene.waitForTransition="等待过渡完成"
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}} {{waitForTransition}}"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="窗口"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
@@ -108,7 +120,9 @@ AdvSceneSwitcher.condition.file.entry.line1="内容 {{fileType}} {{filePath}} (
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="媒体"
AdvSceneSwitcher.condition.media.entry="{{mediaSources}} 状态是 {{states}} 和 {{timeRestrictions}} {{time}}"
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体来源"
AdvSceneSwitcher.condition.media.allOnScene="所有媒体来源"
AdvSceneSwitcher.condition.media.matchOnChange="仅在更改时匹配(注意:此选项将在未来版本中删除-请改用时间限制)"
AdvSceneSwitcher.condition.video="视频"
AdvSceneSwitcher.condition.video.condition.match="完全匹配"
AdvSceneSwitcher.condition.video.condition.differ="不匹配"
@@ -129,15 +143,17 @@ AdvSceneSwitcher.condition.video.objectScaleThreshold="模型比例: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="较低的模比例将导致更多的匹配但会导致更高的CPU负载."
AdvSceneSwitcher.condition.video.minNeighborDescription="较高的“最小区域”值将导致较少但质量较高的匹配."
AdvSceneSwitcher.condition.video.showMatch="显示模式"
AdvSceneSwitcher.condition.video.showMatch.loading="Checking for match"
AdvSceneSwitcher.condition.video.screenshotFail="无法获取源的屏幕截图!"
AdvSceneSwitcher.condition.video.screenshotEmpty="Screenshot is empty - Is the source visible?"
AdvSceneSwitcher.condition.video.patternMatchFail="没有找到模式!"
AdvSceneSwitcher.condition.video.patternMatchSuccess="Pattern is highlighted in red"
AdvSceneSwitcher.condition.video.objectMatchFail="找不到对象!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
AdvSceneSwitcher.condition.video.modelLoadFail="无法加载模型数据!"
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.minSize="最小尺寸: {{minSizeX}} x {{minSizeY}}"
AdvSceneSwitcher.condition.video.entry.maxSize="最大尺寸: {{maxSizeX}} x {{maxSizeY}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}通过只执行每一次检查来减少CPU负载 {{throttleCount}} 毫秒"
AdvSceneSwitcher.condition.stream="推流"
AdvSceneSwitcher.condition.stream.state.start="推流启动"
@@ -156,11 +172,14 @@ AdvSceneSwitcher.condition.idle="闲置检测"
AdvSceneSwitcher.condition.idle.entry="没有键盘或鼠标输入{{duration}}"
AdvSceneSwitcher.condition.pluginState="插件状态"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="在此时间间隔内触发了自动场景更改"
AdvSceneSwitcher.condition.pluginState.state.running="高级场景切换程序正在运行"
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS正在关闭"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
AdvSceneSwitcher.condition.timer="计时器"
AdvSceneSwitcher.condition.timer.type.fixed="固定"
AdvSceneSwitcher.condition.timer.type.random="随机"
AdvSceneSwitcher.condition.timer.pause="暂停"
AdvSceneSwitcher.condition.timer.continue="继续"
AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} 时间倒计时"
AdvSceneSwitcher.condition.timer.entry.line2="剩余时间: {{remaining}} 秒"
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} 保存剩余时间 {{autoReset}} 达到时间后自动重置计时器"
AdvSceneSwitcher.condition.timer.reset="重置"
@@ -177,8 +196,8 @@ AdvSceneSwitcher.condition.macro.state.entry="条件 {{macros}} 是真的"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} 运行 {{conditions}} {{count}} 次(相对于下面计数匹配)"
AdvSceneSwitcher.condition.macro.count.entry.line2="当前计数: {{currentCount}} {{resetCount}}"
AdvSceneSwitcher.condition.source="源"
AdvSceneSwitcher.condition.source.type.active="激活状态"
AdvSceneSwitcher.condition.source.type.showing="显示状态"
AdvSceneSwitcher.condition.source.type.active="激活状态"
AdvSceneSwitcher.condition.source.type.showing="显示状态"
AdvSceneSwitcher.condition.source.type.settings="设置完全匹配"
AdvSceneSwitcher.condition.source.regex="使用正则表达式"
AdvSceneSwitcher.condition.source.getSettings="获取当前设置"
@@ -207,19 +226,33 @@ AdvSceneSwitcher.condition.sceneOrder.entry="在 {{scenes}} {{sources}} {{condit
AdvSceneSwitcher.condition.hotkey="热键"
AdvSceneSwitcher.condition.hotkey.name="宏触发热键"
AdvSceneSwitcher.condition.hotkey.tip="注意您可以在OBS设置窗口中为此热键配置按键绑定"
AdvSceneSwitcher.condition.hotkey.entry="名称: {{name}}"
AdvSceneSwitcher.condition.hotkey.entry.line1="热键被按下"
AdvSceneSwitcher.condition.hotkey.entry.line2="名称: {{name}}"
AdvSceneSwitcher.condition.replay="回放缓存"
AdvSceneSwitcher.condition.replay.state.stopped="回放缓存已停止"
AdvSceneSwitcher.condition.replay.state.started="回放缓存已启动"
AdvSceneSwitcher.condition.replay.state.saved="已保存回放缓存"
AdvSceneSwitcher.condition.replay.entry="{{state}}"
AdvSceneSwitcher.condition.date="日期"
AdvSceneSwitcher.condition.date.anyDay="每一天"
AdvSceneSwitcher.condition.date.monday="周一"
AdvSceneSwitcher.condition.date.tuesday="周二"
AdvSceneSwitcher.condition.date.wednesday="周三"
AdvSceneSwitcher.condition.date.thursday="周四"
AdvSceneSwitcher.condition.date.friday="周五"
AdvSceneSwitcher.condition.date.saturday="周六"
AdvSceneSwitcher.condition.date.sunday="周日"
AdvSceneSwitcher.condition.date.state.at="现在"
AdvSceneSwitcher.condition.date.state.after="之后"
AdvSceneSwitcher.condition.date.state.before="之前"
AdvSceneSwitcher.condition.date.state.between="之间"
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} 忽略日期 {{ignoreTime}} 忽略时间"
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} 重复的每一次 {{duration}} 匹配的时间"
AdvSceneSwitcher.condition.date.separator="和"
AdvSceneSwitcher.condition.date.ignoreDate="如果未选中,日期组件将被忽略"
AdvSceneSwitcher.condition.date.ignoreTime="如果未选中,时间组件将被忽略"
AdvSceneSwitcher.condition.date.showAdvancedSettings="显示高级设置"
AdvSceneSwitcher.condition.date.showSimpleSettings="显示简单设置"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} 在匹配到日期时间后,每隔 {{duration}} 重复一次"
AdvSceneSwitcher.condition.sceneTransform="场景项目被改变"
AdvSceneSwitcher.condition.sceneTransform.getTransform="内容或者设置被改变了"
AdvSceneSwitcher.condition.sceneTransform.regex="使用正则表达式"
@@ -231,16 +264,48 @@ AdvSceneSwitcher.condition.transition.type.current="当前转场特效类型为"
AdvSceneSwitcher.condition.transition.type.duration="当前转场特效持续时间为"
AdvSceneSwitcher.condition.transition.type.started="转场特效开始"
AdvSceneSwitcher.condition.transition.type.ended="转场特效结束"
AdvSceneSwitcher.condition.transition.type.transitionSource="转场特效来自"
AdvSceneSwitcher.condition.transition.type.transitionTarget="转场特效到"
AdvSceneSwitcher.condition.transition.durationSuffix="秒"
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}"
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scenes}}{{duration}}{{durationSuffix}}"
AdvSceneSwitcher.condition.sceneVisibility="场景项目可见"
AdvSceneSwitcher.condition.sceneVisibility.type.shown="显示的"
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="隐藏的"
AdvSceneSwitcher.condition.sceneVisibility.entry="在 {{scenes}} {{sources}} 是 {{conditions}} "
AdvSceneSwitcher.condition.studioMode="工作室模式"
AdvSceneSwitcher.condition.studioMode.state.active="工作室模式处于活动状态"
AdvSceneSwitcher.condition.studioMode.state.notActive="工作室模式未处于活动状态"
AdvSceneSwitcher.condition.studioMode.state.previewScene="预览场景是"
AdvSceneSwitcher.condition.studioMode.entry="{{conditions}}{{scenes}}"
AdvSceneSwitcher.condition.openvr="OpenVR"
AdvSceneSwitcher.condition.errorStatus="OpenVR 错误: "
AdvSceneSwitcher.condition.openvr.entry.line1="HMD在 ..."
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
AdvSceneSwitcher.condition.openvr.entry.line3="HMD目前处于 {{xPos}} x {{yPos}} x {{zPos}}"
AdvSceneSwitcher.condition.stats="OBS统计数据"
AdvSceneSwitcher.condition.stats.type.fps="FPS"
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU使用率"
AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable="可用磁盘空间"
AdvSceneSwitcher.condition.stats.type.memoryUsage="内存使用"
AdvSceneSwitcher.condition.stats.type.averageTimeToRender="渲染帧的平均时间"
AdvSceneSwitcher.condition.stats.type.skippedFrames="由于编码延迟跳过的帧"
AdvSceneSwitcher.condition.stats.type.missedFrames="由于渲染延迟错过的帧"
AdvSceneSwitcher.condition.stats.type.droppedFrames.stream="串流丢弃的帧"
AdvSceneSwitcher.condition.stats.type.megabytesSent.stream="串流总数据输出"
AdvSceneSwitcher.condition.stats.type.bitrate.stream="串流比特率"
AdvSceneSwitcher.condition.stats.type.droppedFrames.recording="录制丢弃的帧"
AdvSceneSwitcher.condition.stats.type.megabytesSent.recording="录制总数据输出"
AdvSceneSwitcher.condition.stats.type.bitrate.recording="录制比特率"
AdvSceneSwitcher.condition.stats.condition.above="大于"
AdvSceneSwitcher.condition.stats.condition.equals="等于"
AdvSceneSwitcher.condition.stats.condition.below="少于"
AdvSceneSwitcher.condition.stats.dockHint="你可以打开“统计”窗口查看当前状态"
AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="切换场景"
AdvSceneSwitcher.action.scene.entry="切换场景 {{scenes}} 使用 {{transitions}} 时长 {{duration}} 秒"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待目标场景的过渡完成"
AdvSceneSwitcher.action.wait="等待"
AdvSceneSwitcher.action.wait.type.fixed="固定数值"
AdvSceneSwitcher.action.wait.type.random="随机数值"
@@ -251,7 +316,6 @@ AdvSceneSwitcher.action.audio.type.mute="静音"
AdvSceneSwitcher.action.audio.type.unmute="取消静音"
AdvSceneSwitcher.action.audio.type.sourceVolume="来源音量"
AdvSceneSwitcher.action.audio.type.masterVolume="主音量"
AdvSceneSwitcher.action.audio.fade="{{fade}}淡出 {{duration}} 秒"
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
AdvSceneSwitcher.action.recording="录制"
AdvSceneSwitcher.action.recording.type.stop="停止录制"
@@ -279,7 +343,6 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="显示"
AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
AdvSceneSwitcher.action.sceneVisibility.type.source="来源"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="全部"
AdvSceneSwitcher.action.sceneVisibility.entry="在 {{scenes}} {{actions}} {{sourceTypes}} {{sources}}"
AdvSceneSwitcher.action.filter="滤镜"
AdvSceneSwitcher.action.filter.type.enable="开启"
AdvSceneSwitcher.action.filter.type.disable="关闭"
@@ -300,16 +363,19 @@ AdvSceneSwitcher.action.media.type.stop="停止"
AdvSceneSwitcher.action.media.type.restart="重新启动"
AdvSceneSwitcher.action.media.type.next="下一个"
AdvSceneSwitcher.action.media.type.previous="上一个"
AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}"
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
AdvSceneSwitcher.action.macro="宏"
AdvSceneSwitcher.action.macro.type.pause="暂停"
AdvSceneSwitcher.action.macro.type.unpause="取消暂停"
AdvSceneSwitcher.action.macro.type.resetCounter="复位计数器"
AdvSceneSwitcher.action.macro.type.run="运行"
AdvSceneSwitcher.action.macro.type.stop="停止"
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
AdvSceneSwitcher.action.pluginState="插件状态"
AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件"
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项:"
AdvSceneSwitcher.action.pluginState.entry="{{actions}} {{values}} {{scenes}}"
AdvSceneSwitcher.action.pluginState.type.import="从设置导入"
AdvSceneSwitcher.action.pluginState.importWarning="注意:当“设置”窗口打开时,操作将被忽略。"
AdvSceneSwitcher.action.virtualCamera="虚拟摄像机"
AdvSceneSwitcher.action.virtualCamera.type.stop="停止虚拟摄像机"
AdvSceneSwitcher.action.virtualCamera.type.start="启动虚拟摄像机"
@@ -333,20 +399,20 @@ AdvSceneSwitcher.action.sceneOrder.type.moveTop="顶部"
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="底部"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="移动到数值位置"
AdvSceneSwitcher.action.sceneOrder.entry="在 {{scenes}} {{actions}} {{sources}} {{position}}"
AdvSceneSwitcher.action.sceneTransform="场景项变换"
AdvSceneSwitcher.action.sceneTransform="场景项变换"
AdvSceneSwitcher.action.sceneTransform.getTransform="得到变换"
AdvSceneSwitcher.action.sceneTransform.entry="On {{scenes}} transform {{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry=" {{scenes}} 变换 {{sources}}"
AdvSceneSwitcher.action.file="文件"
AdvSceneSwitcher.action.file.type.write="写入"
AdvSceneSwitcher.action.file.type.append="追加写入"
AdvSceneSwitcher.action.file.entry="{{actions}} to {{filePath}}:"
AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:"
AdvSceneSwitcher.action.previewScene="切换预览场景"
AdvSceneSwitcher.action.previewScene.entry="将预览场景切换到 {{scenes}}"
AdvSceneSwitcher.action.SceneSwap="交换场景 (Studio mode)"
AdvSceneSwitcher.action.SceneSwap.entry="在studio模式下交换预览和编程场景"
AdvSceneSwitcher.action.transition="过场特效"
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}将过场特效类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}将过场特效持续时间设置为 {{duration}}seconds"
AdvSceneSwitcher.action.transition.entry.line2="{{setTransition}}将过场特效类型设置为 {{transitions}}"
AdvSceneSwitcher.action.transition.entry.line3="{{setDuration}}将过场特效持续时间设置为 {{duration}}seconds"
AdvSceneSwitcher.action.timer="计时器"
AdvSceneSwitcher.action.timer.type.pause="暂停"
AdvSceneSwitcher.action.timer.type.continue="继续"
@@ -354,9 +420,22 @@ AdvSceneSwitcher.action.timer.type.reset="重置"
AdvSceneSwitcher.action.timer.type.setTimeRemaining="设置剩余时间"
AdvSceneSwitcher.action.timer.entry="{{timerAction}} 定时在 {{macros}} {{duration}}"
AdvSceneSwitcher.action.random="随机"
AdvSceneSwitcher.action.random.arguments="宏:"
AdvSceneSwitcher.action.random.addArgument="增加 宏"
AdvSceneSwitcher.action.random.entry="运行 {{macroSelection}}"
AdvSceneSwitcher.action.systray="系统托盘通知"
AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}"
AdvSceneSwitcher.action.screenshot="截图"
AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面"
AdvSceneSwitcher.action.screenshot.entry="截图 {{sources}}"
AdvSceneSwitcher.action.profile="配置文件"
AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}"
AdvSceneSwitcher.action.sceneCollection="场景集合"
AdvSceneSwitcher.action.sceneCollection.entry="将活动场景集合切换到 {{sceneCollections}}"
AdvSceneSwitcher.action.sceneCollection.warning="注意:在此之后的任何操作都不会执行,因为更改场景集合也会重新加载场景切换器设置。\n打开“设置”窗口时场景采集操作将被忽略."
AdvSceneSwitcher.action.sequence="场景序列"
AdvSceneSwitcher.action.sequence.entry="每次执行此操作时,运行列表中的下一个宏(暂停的宏将被忽略)"
AdvSceneSwitcher.action.sequence.status="上次执行的宏:%1 - 要执行的下一个宏: %2"
AdvSceneSwitcher.action.sequence.status.none="无"
AdvSceneSwitcher.action.sequence.restart="到达列表末尾后,从开头重新启动"
AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="转场特效"
@@ -366,8 +445,6 @@ AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="更改激活转场特
AdvSceneSwitcher.transitionTab.transitionForAToB="当自动从场景A切换到场景B时使用的转场特效"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>这里的设定<span style=\"font-style:bold;\">只影响由场景切换器引起的转场有效</span>,不影响你手动的引起的转场。<br/>在这里设定的转场特效优先级高于场景切换器其他地方设置的<br/><br/>单击加号添加项目.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="当切换到这个场景时修改默认转场特效"
AdvSceneSwitcher.transitionTab.switchDefaultLabel="活跃时切换默认转场特效为"
AdvSceneSwitcher.transitionTab.entry="从场景 {{scenes}} 切换到场景 {{scenes2}} 时使用转场特效 {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="当场景 {{scenes}} 被激活时更改默认转场特效为 {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="单击加号添加项目."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="场景更改 {{defTransitionDelay}} 后切换转换特效."
@@ -423,7 +500,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="暂停"
AdvSceneSwitcher.mediaTab.states.stopped="停止"
AdvSceneSwitcher.mediaTab.states.ended="结束"
AdvSceneSwitcher.mediaTab.states.error="错误"
AdvSceneSwitcher.mediaTab.states.playedToEnd="播放结束"
AdvSceneSwitcher.mediaTab.states.any="任意"
AdvSceneSwitcher.mediaTab.timeRestriction.none="无"
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="播放时间小于"
@@ -475,7 +551,7 @@ AdvSceneSwitcher.timeTab.fridays="每周五"
AdvSceneSwitcher.timeTab.saturdays="每周六"
AdvSceneSwitcher.timeTab.sundays="每周日"
AdvSceneSwitcher.timeTab.afterstart="推流或录制开始后"
AdvSceneSwitcher.timeTab.afterstart.tip="相对于直播或录制开始之后的时间点"
AdvSceneSwitcher.timeTab.afterstart.tip="相对于串流或录制开始之后的时间点"
AdvSceneSwitcher.timeTab.entry="{{triggers}} 的 {{time}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
AdvSceneSwitcher.timeTab.help="此选项卡将允许您根据当前本地时间自动切换到其他场景。\n\n请注意场景切换程序仅在您指定的确切时间切换场景。\n请确保您已根据自己的喜好在“常规”选项卡上配置了优先级设置以便不会因其他切换而错过选定的时间点具有较高优先级的方法。\n\n单击突出显示的加号继续."
@@ -508,9 +584,8 @@ AdvSceneSwitcher.audioTab.title="音频"
AdvSceneSwitcher.audioTab.condition.above="高于"
AdvSceneSwitcher.audioTab.condition.below="低于"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="除非源处于非活动状态"
AdvSceneSwitcher.audioTab.entry="当{{audioSources}} 在 {{condition}} {{volumeWidget}} for {{duration}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="如果多个条目匹配 ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} 秒切换到 {{scenes}} 使用 {{transitions}}"
AdvSceneSwitcher.audioTab.multiMatchfallback="... {{duration}} 秒切换到 {{scenes}} 使用 {{transitions}}"
AdvSceneSwitcher.audioTab.help="此选项卡允许您根据源音量切换场景。\n例如如果麦克风音量达到某个阈值您可以自动切换到其他场景。\n\n单击高亮显示的加号继续."
; Video Tab
@@ -530,7 +605,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>此选项卡将允许您
AdvSceneSwitcher.networkTab.title="网络"
AdvSceneSwitcher.networkTab.description="此选项卡将允许您远程控制另一个OBS实例的活动场景。\n请注意所有OBS实例上的场景名称必须完全匹配."
AdvSceneSwitcher.networkTab.warning="在本地网络之外运行服务器将允许第三方读取活动场景."
AdvSceneSwitcher.networkTab.DisabledWarning="不幸的是在并行运行obs websocket插件时由于库不兼容不得不在macOS上禁用此功能."
AdvSceneSwitcher.networkTab.server="启动服务器(向所有连接的客户端发送场景切换消息)"
AdvSceneSwitcher.networkTab.server.port="端口"
AdvSceneSwitcher.networkTab.server.lockToIPv4="锁定为仅使用IPv4将服务器"
@@ -602,8 +676,11 @@ AdvSceneSwitcher.hotkey.stopSwitcherHotkey="停止高级场景切换器"
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="切换(启动/停止)高级场景切换器"
AdvSceneSwitcher.hotkey.macro.pause="暂停宏 %1"
AdvSceneSwitcher.hotkey.macro.unpause="取消暂停宏 %1"
AdvSceneSwitcher.hotkey.macro.togglePause="切换暂停宏 %1"
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="删除选定的宏"
AdvSceneSwitcher.askBackup="检测到新版本的高级场景切换程序。\n是否要创建旧设置的备份?"
AdvSceneSwitcher.askForMacro="选择宏 {{macroSelection}}"
AdvSceneSwitcher.close="关闭"
AdvSceneSwitcher.browse="浏览文件"
@@ -621,19 +698,27 @@ AdvSceneSwitcher.selectAudioSource="--选择音频源--"
AdvSceneSwitcher.selectVideoSource="--选择视频源--"
AdvSceneSwitcher.selectMediaSource="--选择媒体源--"
AdvSceneSwitcher.selectProcess="--选择进程--"
AdvSceneSwitcher.selectFilter="--select filter--"
AdvSceneSwitcher.selectFilter="--选择滤镜--"
AdvSceneSwitcher.selectMacro="--选择宏--"
AdvSceneSwitcher.selectItem="--选择项目--"
AdvSceneSwitcher.selectProfile="--选择配置文件--"
AdvSceneSwitcher.selectSceneCollection="--选择场景集合--"
AdvSceneSwitcher.enterPath="--输入路径--"
AdvSceneSwitcher.enterText="--输入文本--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="无效项将不会被保存"
AdvSceneSwitcher.selectWindowTip="使用 \"OBS\" 来指定OBS窗口\n使用 \"任务切换\" 来指定 Alt + Tab"
AdvSceneSwitcher.sceneItemSelection.all="全部"
AdvSceneSwitcher.sceneItemSelection.any="任何"
AdvSceneSwitcher.status.active="运行中"
AdvSceneSwitcher.status.inactive="已停止"
AdvSceneSwitcher.running="插件运行"
AdvSceneSwitcher.stopped="插件停止"
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>这似乎是第一次启动高级场景切换程序.<br>请看一下 <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> 查看指南和示例列表.<br>如果有问题在在OBS论坛插件帖子内提问 <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a></p></body></html>"
AdvSceneSwitcher.deprecatedTabWarning="此选项卡的开发已停止!请考虑转换为使用宏来代替。\n可以在“常规”选项卡上禁用此提示."
AdvSceneSwitcher.unit.milliseconds="毫秒"
AdvSceneSwitcher.unit.secends="秒"
AdvSceneSwitcher.unit.minutes="分钟"

File diff suppressed because it is too large Load Diff

View File

@@ -437,13 +437,6 @@ void SwitcherData::Stop()
server.stop();
client.disconnect();
for (auto &t : audioHelperThreads) {
if (t.joinable()) {
t.join();
}
}
audioHelperThreads.clear();
if (showSystemTrayNotifications) {
DisplayTrayMessage(
obs_module_text("AdvSceneSwitcher.pluginName"),
@@ -467,6 +460,19 @@ bool SwitcherData::sceneChangedDuringWait()
return (waitScene && currentSource != waitScene);
}
// 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 SwitcherData::anySceneTransitionStarted()
{
auto currentSceneSrouce = obs_frontend_get_current_scene();
auto currentScene = obs_source_get_weak_source(currentSceneSrouce);
bool ret = switcher->currentScene != currentScene;
obs_weak_source_release(currentScene);
obs_source_release(currentSceneSrouce);
return ret;
}
/******************************************************************************
* OBS module setup
******************************************************************************/

View File

@@ -23,7 +23,7 @@ void Duration::Load(obs_data_t *obj, const char *secondsName,
bool Duration::DurationReached()
{
if (_startTime.time_since_epoch().count() == 0) {
if (IsReset()) {
_startTime = std::chrono::high_resolution_clock::now();
}
@@ -32,9 +32,14 @@ bool Duration::DurationReached()
return runTime.count() >= seconds * 1000;
}
bool Duration::IsReset()
{
return _startTime.time_since_epoch().count() == 0;
}
double Duration::TimeRemaining()
{
if (_startTime.time_since_epoch().count() == 0) {
if (IsReset()) {
return seconds;
}
auto runTime = std::chrono::duration_cast<std::chrono::milliseconds>(
@@ -161,131 +166,3 @@ void DurationSelection::_UnitChanged(int idx)
emit UnitChanged(unit);
}
void DurationConstraint::Save(obs_data_t *obj, const char *condName,
const char *secondsName, const char *unitName)
{
obs_data_set_int(obj, condName, static_cast<int>(_type));
_dur.Save(obj, secondsName, unitName);
}
void DurationConstraint::Load(obs_data_t *obj, const char *condName,
const char *secondsName, const char *unitName)
{
// For backwards compatability check if duration value exist without
// time constraint condition - if so assume DurationCondition::MORE
if (!obs_data_has_user_value(obj, condName) &&
obs_data_has_user_value(obj, secondsName)) {
obs_data_set_int(obj, condName,
static_cast<int>(DurationCondition::MORE));
}
_type = static_cast<DurationCondition>(obs_data_get_int(obj, condName));
_dur.Load(obj, secondsName, unitName);
}
bool DurationConstraint::DurationReached()
{
switch (_type) {
case DurationCondition::NONE:
return true;
break;
case DurationCondition::MORE:
return _dur.DurationReached();
break;
case DurationCondition::EQUAL:
if (_dur.DurationReached() && !_timeReached) {
_timeReached = true;
return true;
}
break;
case DurationCondition::LESS:
return !_dur.DurationReached();
break;
default:
break;
}
return false;
}
void DurationConstraint::Reset()
{
_timeReached = false;
_dur.Reset();
}
static void populateConditions(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.none"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.more"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.equal"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.less"));
}
DurationConstraintEdit::DurationConstraintEdit(QWidget *parent)
{
_condition = new QComboBox(parent);
_duration = new DurationSelection(parent);
_toggle = new QPushButton(parent);
_toggle->setMaximumSize(22, 22);
_toggle->setIcon(
QIcon(QString::fromStdString(getDataFilePath("res/time.svg"))));
populateConditions(_condition);
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(_ConditionChanged(int)));
QObject::connect(_duration, &DurationSelection::DurationChanged, this,
&DurationConstraintEdit::DurationChanged);
QObject::connect(_duration, &DurationSelection::UnitChanged, this,
&DurationConstraintEdit::UnitChanged);
QWidget::connect(_toggle, SIGNAL(clicked()), this,
SLOT(ToggleClicked()));
QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(11);
layout->addWidget(_toggle);
layout->addWidget(_condition);
layout->addWidget(_duration);
setLayout(layout);
Collapse(true);
}
void DurationConstraintEdit::SetValue(DurationConstraint &value)
{
_duration->SetDuration(value.GetDuration());
_condition->setCurrentIndex(static_cast<int>(value.GetCondition()));
_duration->setVisible(value.GetCondition() != DurationCondition::NONE);
}
void DurationConstraintEdit::SetUnit(DurationUnit u)
{
_duration->SetUnit(u);
}
void DurationConstraintEdit::SetDuration(const Duration &d)
{
_duration->SetDuration(d);
}
void DurationConstraintEdit::_ConditionChanged(int value)
{
auto cond = static_cast<DurationCondition>(value);
Collapse(cond == DurationCondition::NONE);
emit ConditionChanged(cond);
}
void DurationConstraintEdit::ToggleClicked()
{
Collapse(false);
}
void DurationConstraintEdit::Collapse(bool collapse)
{
_toggle->setVisible(collapse);
_duration->setVisible(!collapse);
_condition->setVisible(!collapse);
}

View File

@@ -17,14 +17,18 @@ endif()
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../headers")
set(module_SOURCES
area-selection.cpp
area-selection.hpp
macro-condition-video.cpp
macro-condition-video.hpp
video-match-dialog.cpp
video-match-dialog.hpp
opencv-helpers.cpp
opencv-helpers.hpp
preview-dialog.cpp
preview-dialog.hpp
threshold-slider.cpp
threshold-slider.hpp
opencv-helpers.cpp
opencv-helpers.hpp)
video-selection.cpp
video-selection.hpp)
add_library(advanced-scene-switcher-opencv MODULE ${module_SOURCES})
if(BUILD_OUT_OF_TREE)

View File

@@ -0,0 +1,130 @@
#include "area-selection.hpp"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <obs-module.h>
void advss::Size::Save(obs_data_t *obj, const char *name)
{
auto data = obs_data_create();
obs_data_set_int(data, "width", width);
obs_data_set_int(data, "height", height);
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
void advss::Size::Load(obs_data_t *obj, const char *name)
{
auto data = obs_data_get_obj(obj, name);
width = obs_data_get_int(data, "width");
height = obs_data_get_int(data, "height");
obs_data_release(data);
}
cv::Size advss::Size::CV()
{
return {width, height};
}
void advss::Area::Save(obs_data_t *obj, const char *name)
{
auto data = obs_data_create();
obs_data_set_int(data, "x", x);
obs_data_set_int(data, "y", y);
obs_data_set_int(data, "width", width);
obs_data_set_int(data, "height", height);
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
void advss::Area::Load(obs_data_t *obj, const char *name)
{
auto data = obs_data_get_obj(obj, name);
x = obs_data_get_int(data, "x");
y = obs_data_get_int(data, "y");
width = obs_data_get_int(data, "width");
height = obs_data_get_int(data, "height");
obs_data_release(data);
}
SizeSelection::SizeSelection(int min, int max, QWidget *parent)
: QWidget(parent), _x(new QSpinBox), _y(new QSpinBox)
{
_x->setMinimum(min);
_y->setMinimum(min);
_x->setMaximum(max);
_y->setMaximum(max);
connect(_x, SIGNAL(valueChanged(int)), this, SLOT(XChanged(int)));
connect(_y, SIGNAL(valueChanged(int)), this, SLOT(YChanged(int)));
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_x);
layout->addWidget(new QLabel(" x "));
layout->addWidget(_y);
setLayout(layout);
}
void SizeSelection::SetSize(const advss::Size &s)
{
_x->setValue(s.width);
_y->setValue(s.height);
}
advss::Size SizeSelection::Size()
{
return advss::Size{_x->value(), _y->value()};
}
void SizeSelection::XChanged(int value)
{
emit SizeChanged(advss::Size{value, _y->value()});
}
void SizeSelection::YChanged(int value)
{
emit SizeChanged(advss::Size{_x->value(), value});
}
AreaSelection::AreaSelection(int min, int max, QWidget *parent)
: QWidget(parent),
_x(new SizeSelection(min, max)),
_y(new SizeSelection(min, max))
{
_x->_x->setToolTip("X");
_x->_y->setToolTip("Y");
_y->_x->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.video.width"));
_y->_y->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.video.height"));
connect(_x, SIGNAL(SizeChanged(advss::Size)), this,
SLOT(XSizeChanged(advss::Size)));
connect(_y, SIGNAL(SizeChanged(advss::Size)), this,
SLOT(YSizeChanged(advss::Size)));
auto layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_x);
layout->addWidget(_y);
setLayout(layout);
}
void AreaSelection::SetArea(const advss::Area &value)
{
_x->SetSize({value.x, value.y});
_y->SetSize({value.width, value.height});
}
void AreaSelection::XSizeChanged(advss::Size value)
{
emit AreaChanged(advss::Area{value.width, value.height,
_y->Size().width, _y->Size().height});
}
void AreaSelection::YSizeChanged(advss::Size value)
{
emit AreaChanged(advss::Area{_x->Size().width, _x->Size().height,
value.width, value.height});
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <QWidget>
#include <QSpinBox>
#include <obs-data.h>
#include <opencv2/opencv.hpp>
namespace advss {
struct Size {
void Save(obs_data_t *obj, const char *name);
void Load(obs_data_t *obj, const char *name);
cv::Size CV();
int width;
int height;
};
struct Area {
void Save(obs_data_t *obj, const char *name);
void Load(obs_data_t *obj, const char *name);
int x;
int y;
int width;
int height;
};
}
class SizeSelection : public QWidget {
Q_OBJECT
public:
SizeSelection(int min, int max, QWidget *parent = 0);
void SetSize(const advss::Size &);
advss::Size Size();
private slots:
void XChanged(int);
void YChanged(int);
signals:
void SizeChanged(advss::Size value);
private:
QSpinBox *_x;
QSpinBox *_y;
friend class AreaSelection;
};
class AreaSelection : public QWidget {
Q_OBJECT
public:
AreaSelection(int min, int max, QWidget *parent = 0);
void SetArea(const advss::Area &);
private slots:
void XSizeChanged(advss::Size value);
void YSizeChanged(advss::Size value);
signals:
void AreaChanged(advss::Area value);
private:
SizeSelection *_x;
SizeSelection *_y;
};

View File

@@ -72,8 +72,11 @@ bool MacroConditionVideo::CheckShouldBeSkipped()
bool MacroConditionVideo::CheckCondition()
{
bool match = false;
if (!_video.ValidSelection()) {
return false;
}
bool match = false;
if (CheckShouldBeSkipped()) {
return _lastMatchResult;
}
@@ -99,8 +102,7 @@ bool MacroConditionVideo::CheckCondition()
bool MacroConditionVideo::Save(obs_data_t *obj)
{
MacroCondition::Save(obj);
obs_data_set_string(obj, "videoSource",
GetWeakSourceName(_videoSource).c_str());
_video.Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
obs_data_set_string(obj, "filePath", _file.c_str());
obs_data_set_bool(obj, "usePatternForChangedCheck",
@@ -110,12 +112,12 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
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);
_minSize.Save(obj, "minSize");
_maxSize.Save(obj, "maxSize");
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
obs_data_set_int(obj, "throttleCount", _throttleCount);
obs_data_set_bool(obj, "checkAreaEnabled", _checkAreaEnable);
_checkArea.Save(obj, "checkArea");
return true;
}
@@ -133,8 +135,10 @@ bool isMinNeighborsValid(int minNeighbors)
bool MacroConditionVideo::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
_videoSource = GetWeakSourceByName(videoSourceName);
_video.Load(obj);
if (obs_data_has_user_value(obj, "videoSource")) {
_video.Load(obj, "videoSource");
}
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
_file = obs_data_get_string(obj, "filePath");
@@ -151,13 +155,20 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
if (!isMinNeighborsValid(_minNeighbors)) {
_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");
// TODO: Remove this fallback in future version
if (obs_data_has_user_value(obj, "minSizeX")) {
_minSize.width = obs_data_get_int(obj, "minSizeX");
_minSize.height = obs_data_get_int(obj, "minSizeY");
_maxSize.width = obs_data_get_int(obj, "maxSizeX");
_maxSize.height = obs_data_get_int(obj, "maxSizeY");
} else {
_minSize.Load(obj, "minSize");
_maxSize.Load(obj, "maxSize");
}
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
_throttleCount = obs_data_get_int(obj, "throttleCount");
_checkAreaEnable = obs_data_get_bool(obj, "checkAreaEnabled");
_checkArea.Load(obj, "checkArea");
if (requiresFileInput(_condition)) {
(void)LoadImageFromFile();
}
@@ -171,15 +182,12 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
std::string MacroConditionVideo::GetShortDesc()
{
if (_videoSource) {
return GetWeakSourceName(_videoSource);
}
return "";
return _video.ToString();
}
void MacroConditionVideo::GetScreenshot()
{
auto source = obs_weak_source_get_source(_videoSource);
auto source = obs_weak_source_get_source(_video.GetVideo());
_screenshotData.~ScreenshotHelper();
new (&_screenshotData) ScreenshotHelper(source);
obs_source_release(source);
@@ -230,14 +238,19 @@ bool MacroConditionVideo::OutputChanged()
bool MacroConditionVideo::ScreenshotContainsObject()
{
auto objects = matchObject(_screenshotData.image, _objectCascade,
_scaleFactor, _minNeighbors,
{_minSizeX, _minSizeY},
{_maxSizeX, _maxSizeY});
_scaleFactor, _minNeighbors, _minSize.CV(),
_maxSize.CV());
return objects.size() > 0;
}
bool MacroConditionVideo::Compare()
{
if (_checkAreaEnable && _condition != VideoCondition::NO_IMAGE) {
_screenshotData.image = _screenshotData.image.copy(
_checkArea.x, _checkArea.y, _checkArea.width,
_checkArea.height);
}
switch (_condition) {
case VideoCondition::MATCH:
return _screenshotData.image == _matchImage;
@@ -269,58 +282,59 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent),
_matchDialog(this, entryData.get(), &GetSwitcher()->m)
_videoSelection(new VideoSelectionWidget(this)),
_condition(new QComboBox()),
_usePatternForChangedCheck(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
_imagePath(new FileSelection()),
_patternThreshold(new ThresholdSlider(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"))),
_useAlphaAsMask(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"))),
_modelDataPath(new FileSelection()),
_modelPathLayout(new QHBoxLayout),
_objectScaleThreshold(new ThresholdSlider(
1.1, 5.,
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"))),
_neighborsControlLayout(new QHBoxLayout),
_minNeighbors(new QSpinBox()),
_minNeighborsDescription(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minNeighborDescription"))),
_sizeLayout(new QHBoxLayout()),
_minSize(new SizeSelection(0, 1024)),
_maxSize(new SizeSelection(0, 4096)),
_checkAreaControlLayout(new QHBoxLayout),
_checkAreaEnable(new QCheckBox()),
_checkArea(new AreaSelection(0, 99999)),
_selectArea(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea"))),
_throttleControlLayout(new QHBoxLayout),
_throttleEnable(new QCheckBox()),
_throttleCount(new QSpinBox()),
_showMatch(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.showMatch"))),
_previewDialog(this, entryData.get(), &GetSwitcher()->m)
{
_videoSelection = new QComboBox();
_condition = new QComboBox();
_imagePath = new FileSelection();
_imagePath->Button()->disconnect();
_usePatternForChangedCheck = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"));
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternThreshold = new ThresholdSlider(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"));
_useAlphaAsMask = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"));
_modelDataPath = new FileSelection();
_objectScaleThreshold = new ThresholdSlider(
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);
_minNeighborsDescription = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minNeighborDescription"));
_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 * GetSwitcher()->interval);
_throttleCount->setMaximum(10 * GetSwitcher()->interval);
_throttleCount->setSingleStep(GetSwitcher()->interval);
_showMatch = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.video.showMatch"));
QWidget::connect(_videoSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
SIGNAL(VideoSelectionChange(const VideoSelection &)),
this,
SLOT(VideoSelectionChanged(const VideoSelection &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
@@ -338,14 +352,14 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
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(_minSize, SIGNAL(SizeChanged(advss::Size)), this,
SLOT(MinSizeChanged(advss::Size)));
QWidget::connect(_maxSize, SIGNAL(SizeChanged(advss::Size)), this,
SLOT(MaxSizeChanged(advss::Size)));
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
SLOT(CheckAreaEnableChanged(int)));
QWidget::connect(_checkArea, SIGNAL(AreaChanged(advss::Area)), this,
SLOT(CheckAreaChanged(advss::Area)));
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
this, SLOT(ModelPathChanged(const QString &)));
QWidget::connect(_throttleEnable, SIGNAL(stateChanged(int)), this,
@@ -354,8 +368,11 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
SLOT(ThrottleCountChanged(int)));
QWidget::connect(_showMatch, SIGNAL(clicked()), this,
SLOT(ShowMatchClicked()));
QWidget::connect(&_previewDialog, SIGNAL(SelectionAreaChanged(QRect)),
this, SLOT(CheckAreaChanged(QRect)));
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
SLOT(SelectAreaClicked()));
populateVideoSelection(_videoSelection);
populateConditionSelection(_condition);
QHBoxLayout *entryLine1Layout = new QHBoxLayout;
@@ -364,43 +381,47 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
{"{{condition}}", _condition},
{"{{imagePath}}", _imagePath},
{"{{minNeighbors}}", _minNeighbors},
{"{{minSizeX}}", _minSizeX},
{"{{minSizeY}}", _minSizeY},
{"{{maxSizeX}}", _maxSizeX},
{"{{maxSizeY}}", _maxSizeY},
{"{{minSize}}", _minSize},
{"{{maxSize}}", _maxSize},
{"{{modelDataPath}}", _modelDataPath},
{"{{throttleEnable}}", _throttleEnable},
{"{{throttleCount}}", _throttleCount},
{"{{checkAreaEnable}}", _checkAreaEnable},
{"{{checkArea}}", _checkArea},
{"{{selectArea}}", _selectArea},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
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);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.checkArea"),
_checkAreaControlLayout, widgetPlaceholders);
QGridLayout *sizeGrid = new QGridLayout;
sizeGrid->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minSize")),
0, 0);
sizeGrid->addWidget(_minSize, 0, 1);
sizeGrid->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.maxSize")),
1, 0);
sizeGrid->addWidget(_maxSize, 1, 1);
_sizeLayout->addLayout(sizeGrid);
_sizeLayout->addStretch();
_sizeLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *showMatchLayout = new QHBoxLayout;
showMatchLayout->addWidget(_showMatch);
@@ -414,10 +435,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout);
mainLayout->addWidget(_minNeighborsDescription);
mainLayout->addLayout(_minSizeControlLayout);
mainLayout->addLayout(_maxSizeControlLayout);
mainLayout->addLayout(_sizeLayout);
mainLayout->addLayout(showMatchLayout);
mainLayout->addLayout(_throttleControlLayout);
mainLayout->addLayout(_checkAreaControlLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -451,14 +472,14 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
this->setToolTip(html);
}
void MacroConditionVideoEdit::SourceChanged(const QString &text)
void MacroConditionVideoEdit::VideoSelectionChanged(const VideoSelection &v)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_videoSource = GetWeakSourceByQString(text);
_entryData->_video = v;
_entryData->ResetLastMatch();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -513,7 +534,7 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
QString path;
bool useExistingFile = false;
// Ask whether to create screenshot or to select existing file
if (_entryData->_videoSource) {
if (_entryData->_video.ValidSelection()) {
QMessageBox msgBox(
QMessageBox::Question,
obs_module_text("AdvSceneSwitcher.windowTitle"),
@@ -540,8 +561,8 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
}
} else {
auto source =
obs_weak_source_get_source(_entryData->_videoSource);
auto source = obs_weak_source_get_source(
_entryData->_video.GetVideo());
ScreenshotHelper screenshot(source);
obs_source_release(source);
@@ -561,6 +582,13 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
"AdvSceneSwitcher.condition.video.screenshotFail"));
return;
}
if (_entryData->_checkAreaEnable) {
screenshot.image = screenshot.image.copy(
_entryData->_checkArea.x,
_entryData->_checkArea.y,
_entryData->_checkArea.width,
_entryData->_checkArea.height);
}
screenshot.image.save(path);
}
_imagePath->SetPath(path);
@@ -620,44 +648,53 @@ void MacroConditionVideoEdit::MinNeighborsChanged(int value)
_entryData->_minNeighbors = value;
}
void MacroConditionVideoEdit::MinSizeXChanged(int value)
void MacroConditionVideoEdit::MinSizeChanged(advss::Size value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_minSizeX = value;
_entryData->_minSize = value;
}
void MacroConditionVideoEdit::MinSizeYChanged(int value)
void MacroConditionVideoEdit::MaxSizeChanged(advss::Size value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_minSizeY = value;
_entryData->_maxSize = value;
}
void MacroConditionVideoEdit::MaxSizeXChanged(int value)
void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_maxSizeX = value;
_entryData->_checkAreaEnable = value;
_checkArea->setEnabled(value);
_selectArea->setEnabled(value);
}
void MacroConditionVideoEdit::MaxSizeYChanged(int value)
void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_maxSizeY = value;
_entryData->_checkArea = value;
}
void MacroConditionVideoEdit::CheckAreaChanged(QRect rect)
{
advss::Area area{rect.topLeft().x(), rect.y(), rect.width(),
rect.height()};
_checkArea->SetArea(area);
}
void MacroConditionVideoEdit::ThrottleEnableChanged(int value)
@@ -683,10 +720,18 @@ void MacroConditionVideoEdit::ThrottleCountChanged(int value)
void MacroConditionVideoEdit::ShowMatchClicked()
{
_matchDialog.show();
_matchDialog.raise();
_matchDialog.activateWindow();
_matchDialog.ShowMatch();
_previewDialog.show();
_previewDialog.raise();
_previewDialog.activateWindow();
_previewDialog.ShowMatch();
}
void MacroConditionVideoEdit::SelectAreaClicked()
{
_previewDialog.show();
_previewDialog.raise();
_previewDialog.activateWindow();
_previewDialog.SelectArea();
}
void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
@@ -737,6 +782,11 @@ bool patternControlIsOptional(VideoCondition cond)
cond == VideoCondition::HAS_NOT_CHANGED;
}
bool needsAreaControls(VideoCondition cond)
{
return cond != VideoCondition::NO_IMAGE;
}
void MacroConditionVideoEdit::SetWidgetVisibility()
{
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
@@ -752,14 +802,14 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsObjectControls(_entryData->_condition));
_minNeighborsDescription->setVisible(
needsObjectControls(_entryData->_condition));
setLayoutVisible(_minSizeControlLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_maxSizeControlLayout,
setLayoutVisible(_sizeLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_modelPathLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->_condition));
setLayoutVisible(_checkAreaControlLayout,
needsAreaControls(_entryData->_condition));
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
@@ -776,8 +826,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
return;
}
_videoSelection->setCurrentText(
GetWeakSourceName(_entryData->_videoSource).c_str());
_videoSelection->SetVideoSelection(_entryData->_video);
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
@@ -787,12 +836,13 @@ void MacroConditionVideoEdit::UpdateEntryData()
_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);
_minSize->SetSize(_entryData->_minSize);
_maxSize->SetSize(_entryData->_maxSize);
_throttleEnable->setChecked(_entryData->_throttleEnabled);
_throttleCount->setValue(_entryData->_throttleCount *
GetSwitcher()->interval);
_checkAreaEnable->setChecked(_entryData->_checkAreaEnable);
_checkArea->SetArea(_entryData->_checkArea);
UpdatePreviewTooltip();
SetWidgetVisibility();
}

View File

@@ -1,16 +1,21 @@
#pragma once
#include "opencv-helpers.hpp"
#include "threshold-slider.hpp"
#include "preview-dialog.hpp"
#include "area-selection.hpp"
#include "video-selection.hpp"
#include <macro.hpp>
#include <screenshot-helper.hpp>
#include <opencv-helpers.hpp>
#include <file-selection.hpp>
#include <threshold-slider.hpp>
#include <video-match-dialog.hpp>
#include <screenshot-helper.hpp>
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QRect>
enum class VideoCondition {
MATCH,
@@ -41,7 +46,7 @@ public:
std::string GetModelDataPath() { return _modelDataPath; }
void ResetLastMatch() { _lastMatchResult = false; }
OBSWeakSource _videoSource;
VideoSelection _video;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
bool _useAlphaAsMask = false;
@@ -51,10 +56,11 @@ public:
cv::CascadeClassifier _objectCascade;
double _scaleFactor = 1.1;
int _minNeighbors = minMinNeighbors;
int _minSizeX = 0;
int _minSizeY = 0;
int _maxSizeX = 0;
int _maxSizeY = 0;
advss::Size _minSize{0, 0};
advss::Size _maxSize{0, 0};
bool _checkAreaEnable = false;
advss::Area _checkArea{0, 0, 0, 0};
bool _throttleEnabled = false;
int _throttleCount = 3;
@@ -99,7 +105,7 @@ public:
void UpdatePreviewTooltip();
private slots:
void SourceChanged(const QString &text);
void VideoSelectionChanged(const VideoSelection &);
void ConditionChanged(int cond);
void ImagePathChanged(const QString &text);
void ImageBrowseButtonClicked();
@@ -110,10 +116,13 @@ private slots:
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 MinSizeChanged(advss::Size value);
void MaxSizeChanged(advss::Size value);
void CheckAreaEnableChanged(int value);
void CheckAreaChanged(advss::Area);
void CheckAreaChanged(QRect area);
void SelectAreaClicked();
void ThrottleEnableChanged(int value);
void ThrottleCountChanged(int value);
@@ -122,7 +131,7 @@ signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_videoSelection;
VideoSelectionWidget *_videoSelection;
QComboBox *_condition;
QCheckBox *_usePatternForChangedCheck;
@@ -136,18 +145,21 @@ protected:
QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
QHBoxLayout *_minSizeControlLayout;
QSpinBox *_minSizeX;
QSpinBox *_minSizeY;
QHBoxLayout *_maxSizeControlLayout;
QSpinBox *_maxSizeX;
QSpinBox *_maxSizeY;
QHBoxLayout *_sizeLayout;
SizeSelection *_minSize;
SizeSelection *_maxSize;
QHBoxLayout *_checkAreaControlLayout;
QCheckBox *_checkAreaEnable;
AreaSelection *_checkArea;
QPushButton *_selectArea;
QHBoxLayout *_throttleControlLayout;
QCheckBox *_throttleEnable;
QSpinBox *_throttleCount;
QPushButton *_showMatch;
ShowMatchDialog _matchDialog;
PreviewDialog _previewDialog;
std::shared_ptr<MacroConditionVideo> _entryData;

View File

@@ -0,0 +1,249 @@
#include "preview-dialog.hpp"
#include "macro-condition-video.hpp"
#include "opencv-helpers.hpp"
#include "utility.hpp"
#include <condition_variable>
PreviewDialog::PreviewDialog(QWidget *parent,
MacroConditionVideo *conditionData,
std::mutex *mutex)
: QDialog(parent),
_conditionData(conditionData),
_scrollArea(new QScrollArea),
_imageLabel(new QLabel(this)),
_rubberBand(new QRubberBand(QRubberBand::Rectangle, this)),
_mtx(mutex)
{
setWindowTitle("Advanced Scene Switcher");
_statusLabel = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.showMatch.loading"));
resize(640, 480);
_scrollArea->setBackgroundRole(QPalette::Dark);
_scrollArea->setWidget(_imageLabel);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_statusLabel);
layout->addWidget(_scrollArea);
setLayout(layout);
// This is a workaround to handle random segfaults triggered when using:
// QMetaObject::invokeMethod(this, "RedrawImage", Qt::QueuedConnection,
// Q_ARG(QImage, image));
// from within CheckForMatchLoop().
// Even using BlockingQueuedConnection causes deadlocks
_timer.setInterval(500);
QWidget::connect(&_timer, &QTimer::timeout, this,
&PreviewDialog::Resize);
_timer.start();
}
void PreviewDialog::mousePressEvent(QMouseEvent *event)
{
_selectingArea = true;
if (_type == Type::SHOW_MATCH) {
return;
}
_origin = event->pos();
_rubberBand->setGeometry(QRect(_origin, QSize()));
_rubberBand->show();
}
void PreviewDialog::mouseMoveEvent(QMouseEvent *event)
{
if (_type == Type::SHOW_MATCH) {
return;
}
_rubberBand->setGeometry(QRect(_origin, event->pos()).normalized());
}
void PreviewDialog::mouseReleaseEvent(QMouseEvent *)
{
if (_type == Type::SHOW_MATCH) {
return;
}
auto selectionStart =
_rubberBand->mapToGlobal(_rubberBand->rect().topLeft());
QRect selectionArea(selectionStart, _rubberBand->size());
auto imageStart =
_imageLabel->mapToGlobal(_imageLabel->rect().topLeft());
QRect imageArea(imageStart, _imageLabel->size());
auto intersected = imageArea.intersected(selectionArea);
QRect checksize(QPoint(intersected.topLeft() - imageStart),
intersected.size());
if (checksize.x() >= 0 && checksize.y() >= 0 && checksize.width() > 0 &&
checksize.height() > 0) {
emit SelectionAreaChanged(checksize);
}
_selectingArea = false;
}
PreviewDialog::~PreviewDialog()
{
_stop = true;
if (_thread.joinable()) {
_thread.join();
}
}
void PreviewDialog::ShowMatch()
{
Start();
_rubberBand->hide();
_type = Type::SHOW_MATCH;
}
void PreviewDialog::SelectArea()
{
_selectingArea = false;
Start();
_type = Type::SELECT_AREA;
DrawFrame();
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea.status"));
}
void PreviewDialog::Resize()
{
_imageLabel->adjustSize();
if (_type == Type::SELECT_AREA && !_selectingArea) {
DrawFrame();
}
}
void PreviewDialog::Start()
{
if (_thread.joinable()) {
return;
}
if (!_conditionData) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
return;
}
_thread = std::thread(&PreviewDialog::CheckForMatchLoop, this);
}
void PreviewDialog::CheckForMatchLoop()
{
std::condition_variable cv;
while (!_stop) {
std::unique_lock<std::mutex> lock(*_mtx);
auto source = obs_weak_source_get_source(
_conditionData->_video.GetVideo());
ScreenshotHelper screenshot(source);
obs_source_release(source);
cv.wait_for(lock, std::chrono::seconds(1));
if (_stop) {
return;
}
if (isHidden()) {
continue;
}
if (!screenshot.done ||
!_conditionData->_video.ValidSelection()) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
_imageLabel->setPixmap(QPixmap());
continue;
}
if (screenshot.image.width() == 0 ||
screenshot.image.height() == 0) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotEmpty"));
_imageLabel->setPixmap(QPixmap());
continue;
}
if (_type == Type::SHOW_MATCH) {
if (_conditionData->_checkAreaEnable) {
screenshot.image = screenshot.image.copy(
_conditionData->_checkArea.x,
_conditionData->_checkArea.y,
_conditionData->_checkArea.width,
_conditionData->_checkArea.height);
}
MarkMatch(screenshot.image);
}
_imageLabel->setPixmap(QPixmap::fromImage(screenshot.image));
}
}
void 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, 255), 2, 8, 0);
}
}
}
}
void 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, 255), 2, 8, 0);
}
}
void PreviewDialog::MarkMatch(QImage &screenshot)
{
if (_conditionData->_condition == VideoCondition::PATTERN) {
cv::Mat result;
QImage pattern = _conditionData->GetMatchImage();
matchPattern(screenshot, pattern,
_conditionData->_patternThreshold, result,
_conditionData->_useAlphaAsMask);
if (countNonZero(result) == 0) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
} else {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
markPatterns(result, screenshot, pattern);
}
} else if (_conditionData->_condition == VideoCondition::OBJECT) {
auto objects = matchObject(screenshot,
_conditionData->_objectCascade,
_conditionData->_scaleFactor,
_conditionData->_minNeighbors,
_conditionData->_minSize.CV(),
_conditionData->_maxSize.CV());
if (objects.empty()) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
} else {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
markObjects(screenshot, objects);
}
}
}
void PreviewDialog::DrawFrame()
{
if (!_conditionData) {
return;
}
auto imageStart =
_imageLabel->mapToGlobal(_imageLabel->rect().topLeft());
auto windowStart = mapToGlobal(rect().topLeft());
_rubberBand->resize(_conditionData->_checkArea.width,
_conditionData->_checkArea.height);
_rubberBand->move(_conditionData->_checkArea.x +
(imageStart.x() - windowStart.x()),
_conditionData->_checkArea.y +
(imageStart.y() - windowStart.y()));
_rubberBand->show();
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <QDialog>
#include <QLabel>
#include <QScrollArea>
#include <QTimer>
#include <QMouseEvent>
#include <QRubberBand>
#include <QPoint>
#include <thread>
#include <mutex>
class MacroConditionVideo;
class PreviewDialog : public QDialog {
Q_OBJECT
public:
PreviewDialog(QWidget *parent, MacroConditionVideo *_conditionData,
std::mutex *mutex);
virtual ~PreviewDialog();
void ShowMatch();
void SelectArea();
private slots:
void Resize();
signals:
void SelectionAreaChanged(QRect area);
private:
void Start();
void CheckForMatchLoop();
void MarkMatch(QImage &screenshot);
void DrawFrame();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
MacroConditionVideo *_conditionData;
QScrollArea *_scrollArea;
QLabel *_statusLabel;
QLabel *_imageLabel;
QTimer _timer;
QPoint _origin;
QRubberBand *_rubberBand = nullptr;
std::atomic_bool _selectingArea = {false};
std::mutex *_mtx;
std::thread _thread;
std::atomic_bool _stop = {false};
enum class Type {
SHOW_MATCH,
SELECT_AREA,
};
Type _type = Type::SHOW_MATCH;
};

View File

@@ -3,22 +3,26 @@
ThresholdSlider::ThresholdSlider(double min, double max, const QString &label,
const QString &description, QWidget *parent)
: QWidget(parent)
: QWidget(parent),
_spinBox(new QDoubleSpinBox()),
_slider(new QSlider())
{
_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);
_spinBox->setMinimum(min);
_spinBox->setMaximum(max);
_spinBox->setDecimals(5);
connect(_slider, SIGNAL(valueChanged(int)), this,
SLOT(NotifyValueChanged(int)));
SLOT(SliderValueChanged(int)));
connect(_spinBox, SIGNAL(valueChanged(double)), this,
SLOT(SpinBoxValueChanged(double)));
QVBoxLayout *mainLayout = new QVBoxLayout();
QHBoxLayout *sliderLayout = new QHBoxLayout();
sliderLayout->addWidget(_value);
if (!label.isEmpty()) {
sliderLayout->addWidget(new QLabel(label));
}
sliderLayout->addWidget(_spinBox);
sliderLayout->addWidget(_slider);
mainLayout->addLayout(sliderLayout);
if (!description.isEmpty()) {
@@ -30,21 +34,21 @@ ThresholdSlider::ThresholdSlider(double min, double max, const QString &label,
void ThresholdSlider::SetDoubleValue(double value)
{
const QSignalBlocker b1(_slider);
const QSignalBlocker b2(_spinBox);
_slider->setValue(value * _scale);
SetDoubleValueText(value);
_spinBox->setValue(value);
}
void ThresholdSlider::NotifyValueChanged(int value)
void ThresholdSlider::SpinBoxValueChanged(double value)
{
int sliderPos = value * _scale;
_slider->setValue(sliderPos);
emit DoubleValueChanged(value);
}
void ThresholdSlider::SliderValueChanged(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);
_spinBox->setValue(doubleValue);
}

View File

@@ -2,6 +2,7 @@
#include <QWidget>
#include <QSlider>
#include <QLabel>
#include <QDoubleSpinBox>
class ThresholdSlider : public QWidget {
Q_OBJECT
@@ -12,14 +13,13 @@ public:
const QString &description = "", QWidget *parent = 0);
void SetDoubleValue(double);
public slots:
void NotifyValueChanged(int value);
void SliderValueChanged(int value);
void SpinBoxValueChanged(double value);
signals:
void DoubleValueChanged(double value);
private:
void SetDoubleValueText(double);
QLabel *_value;
QDoubleSpinBox *_spinBox;
QSlider *_slider;
double _scale = 100.0;
int _precision = 2;
};

View File

@@ -1,160 +0,0 @@
#include "video-match-dialog.hpp"
#include "macro-condition-video.hpp"
#include "opencv-helpers.hpp"
#include "utility.hpp"
#include <condition_variable>
ShowMatchDialog::ShowMatchDialog(QWidget *parent,
MacroConditionVideo *conditionData,
std::mutex *mutex)
: QDialog(parent),
_conditionData(conditionData),
_imageLabel(new QLabel),
_scrollArea(new QScrollArea),
_mtx(mutex)
{
setWindowTitle("Advanced Scene Switcher");
_statusLabel = new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.showMatch.loading"));
_scrollArea->setBackgroundRole(QPalette::Dark);
_scrollArea->setWidget(_imageLabel);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_statusLabel);
layout->addWidget(_scrollArea);
setLayout(layout);
// This is a workaround to handle random segfaults triggered when using:
// QMetaObject::invokeMethod(this, "RedrawImage", -Qt::QueuedConnection,
// Q_ARG(QImage, image));
// from within CheckForMatchLoop().
// Even using BlockingQueuedConnection causes deadlocks
_timer.setInterval(500);
QWidget::connect(&_timer, &QTimer::timeout, this,
&ShowMatchDialog::Resize);
_timer.start();
}
ShowMatchDialog::~ShowMatchDialog()
{
_stop = true;
if (_thread.joinable()) {
_thread.join();
}
}
void ShowMatchDialog::ShowMatch()
{
if (_thread.joinable()) {
return;
}
if (!_conditionData) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
return;
}
_thread = std::thread(&ShowMatchDialog::CheckForMatchLoop, this);
}
void ShowMatchDialog::Resize()
{
_imageLabel->adjustSize();
}
void ShowMatchDialog::CheckForMatchLoop()
{
std::condition_variable cv;
while (!_stop) {
std::unique_lock<std::mutex> lock(*_mtx);
auto source = obs_weak_source_get_source(
_conditionData->_videoSource);
ScreenshotHelper screenshot(source);
obs_source_release(source);
cv.wait_for(lock, std::chrono::seconds(1));
if (_stop) {
return;
}
if (!screenshot.done) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotFail"));
_imageLabel->setPixmap(QPixmap());
continue;
}
if (screenshot.image.width() == 0 ||
screenshot.image.height() == 0) {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.screenshotEmpty"));
_imageLabel->setPixmap(QPixmap());
continue;
}
auto image = MarkMatch(screenshot.image);
_imageLabel->setPixmap(QPixmap::fromImage(screenshot.image));
}
}
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, 255), 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, 255), 2, 8, 0);
}
return MatToQImage(frame);
}
QImage ShowMatchDialog::MarkMatch(QImage &screenshot)
{
QImage resultIamge;
if (_conditionData->_condition == VideoCondition::PATTERN) {
cv::Mat result;
QImage pattern = _conditionData->GetMatchImage();
matchPattern(screenshot, pattern,
_conditionData->_patternThreshold, result,
_conditionData->_useAlphaAsMask);
if (countNonZero(result) == 0) {
resultIamge = screenshot;
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
} else {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
resultIamge = markPatterns(result, screenshot, pattern);
}
} else if (_conditionData->_condition == VideoCondition::OBJECT) {
auto objects = matchObject(
screenshot, _conditionData->_objectCascade,
_conditionData->_scaleFactor,
_conditionData->_minNeighbors,
{_conditionData->_minSizeX, _conditionData->_minSizeY},
{_conditionData->_maxSizeX, _conditionData->_maxSizeY});
if (objects.empty()) {
resultIamge = screenshot;
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchFail"));
} else {
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
resultIamge = markObjects(screenshot, objects);
}
}
return resultIamge;
}

View File

@@ -1,36 +0,0 @@
#pragma once
#include <QDialog>
#include <QLabel>
#include <QScrollArea>
#include <QTimer>
#include <thread>
#include <mutex>
class MacroConditionVideo;
class ShowMatchDialog : public QDialog {
Q_OBJECT
public:
ShowMatchDialog(QWidget *parent, MacroConditionVideo *_conditionData,
std::mutex *mutex);
virtual ~ShowMatchDialog();
void ShowMatch();
private slots:
void Resize();
private:
void CheckForMatchLoop();
QImage MarkMatch(QImage &screenshot);
MacroConditionVideo *_conditionData;
QScrollArea *_scrollArea;
QLabel *_statusLabel;
QLabel *_imageLabel;
QTimer _timer;
std::mutex *_mtx;
std::thread _thread;
std::atomic_bool _stop = {false};
};

View File

@@ -0,0 +1,122 @@
#include "video-selection.hpp"
#include "utility.hpp"
void VideoSelection::Save(obs_data_t *obj, const char *name,
const char *typeName)
{
obs_data_set_int(obj, typeName, static_cast<int>(_type));
switch (_type) {
case VideoSelectionType::SOURCE:
obs_data_set_string(obj, name,
GetWeakSourceName(_source).c_str());
break;
default:
break;
}
}
void VideoSelection::Load(obs_data_t *obj, const char *name,
const char *typeName)
{
_type = static_cast<VideoSelectionType>(
obs_data_get_int(obj, typeName));
auto target = obs_data_get_string(obj, name);
switch (_type) {
case VideoSelectionType::SOURCE:
_source = GetWeakSourceByName(target);
break;
case VideoSelectionType::OBS_MAIN:
_source = nullptr;
break;
default:
break;
}
}
OBSWeakSource VideoSelection::GetVideo()
{
if (_type == VideoSelectionType::SOURCE) {
return _source;
}
return nullptr;
}
std::string VideoSelection::ToString()
{
switch (_type) {
case VideoSelectionType::SOURCE:
return GetWeakSourceName(_source);
case VideoSelectionType::OBS_MAIN:
return obs_module_text("AdvSceneSwitcher.OBSVideoOutput");
default:
break;
}
return "";
}
bool VideoSelection::ValidSelection()
{
return _type == VideoSelectionType::OBS_MAIN || !!_source;
}
VideoSelectionWidget::VideoSelectionWidget(QWidget *parent, bool addOBSVideoOut)
: QComboBox(parent)
{
setDuplicatesEnabled(true);
populateVideoSelection(this, addOBSVideoOut);
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(SelectionChanged(const QString &)));
}
void VideoSelectionWidget::SetVideoSelection(VideoSelection &t)
{
int idx;
switch (t.GetType()) {
case VideoSelectionType::SOURCE:
setCurrentText(QString::fromStdString(t.ToString()));
break;
case VideoSelectionType::OBS_MAIN:
idx = findText(QString::fromStdString(obs_module_text(
obs_module_text("AdvSceneSwitcher.OBSVideoOutput"))));
if (idx != -1) {
setCurrentIndex(idx);
}
break;
default:
setCurrentIndex(0);
break;
}
}
static bool isFirstEntry(QComboBox *l, QString name, int idx)
{
for (int i = 0; i < l->count(); i++) {
if (l->itemText(i) == name) {
return idx == i;
}
}
return false;
}
bool VideoSelectionWidget::IsOBSVideoOutSelected(const QString &name)
{
if (name == QString::fromStdString(obs_module_text(
"AdvSceneSwitcher.OBSVideoOutput"))) {
return isFirstEntry(this, name, currentIndex());
}
return false;
}
void VideoSelectionWidget::SelectionChanged(const QString &name)
{
VideoSelection t;
if (IsOBSVideoOutSelected(name)) {
t._type = VideoSelectionType::OBS_MAIN;
} else {
auto source = GetWeakSourceByQString(name);
t._type = VideoSelectionType::SOURCE;
t._source = source;
}
emit VideoSelectionChange(t);
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include <QComboBox>
#include <obs-module.h>
#include <obs.hpp>
enum class VideoSelectionType {
SOURCE,
OBS_MAIN,
};
class VideoSelection {
public:
void Save(obs_data_t *obj, const char *name = "video",
const char *typeName = "videoType");
void Load(obs_data_t *obj, const char *name = "video",
const char *typeName = "videoType");
VideoSelectionType GetType() { return _type; }
OBSWeakSource GetVideo();
std::string ToString();
bool ValidSelection();
private:
OBSWeakSource _source;
VideoSelectionType _type = VideoSelectionType::SOURCE;
friend class VideoSelectionWidget;
};
class VideoSelectionWidget : public QComboBox {
Q_OBJECT
public:
VideoSelectionWidget(QWidget *parent, bool addOBSVideoOut = true);
void SetVideoSelection(VideoSelection &);
signals:
void VideoSelectionChange(const VideoSelection &);
private slots:
void SelectionChanged(const QString &name);
private:
bool IsOBSVideoOutSelected(const QString &name);
};

View File

@@ -166,6 +166,9 @@ MacroConditionOpenVREdit::MacroConditionOpenVREdit(
controlsLayout->addWidget(_maxY, 1, 1);
controlsLayout->addWidget(_maxZ, 1, 2);
QWidget *controls = new QWidget;
controls->setObjectName("openVRControls");
controls->setStyleSheet(
"#openVRControls { background-color: rgba(0,0,0,0); }");
controls->setLayout(controlsLayout);
controls->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

View File

@@ -192,15 +192,6 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
switcher->disableHints = state;
}
void AdvSceneSwitcher::on_highlightExecutedMacros_stateChanged(int state)
{
if (loading) {
return;
}
switcher->highlightExecutedMacros = state;
}
bool isLegacyTab(const QString &name)
{
return name == obs_module_text(
@@ -482,6 +473,7 @@ void AdvSceneSwitcher::restoreWindowGeo()
void AdvSceneSwitcher::checkFirstTimeSetup()
{
if (switcher->firstBoot && !switcher->disableHints) {
switcher->firstBoot = false;
DisplayMessage(
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
}
@@ -586,8 +578,6 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "showSystemTrayNotifications",
showSystemTrayNotifications);
obs_data_set_bool(obj, "disableHints", disableHints);
obs_data_set_bool(obj, "highlightExecutedMacros",
highlightExecutedMacros);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
@@ -662,8 +652,7 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
showSystemTrayNotifications =
obs_data_get_bool(obj, "showSystemTrayNotifications");
disableHints = obs_data_get_bool(obj, "disableHints");
highlightExecutedMacros =
obs_data_get_bool(obj, "highlightExecutedMacros");
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
obs_data_set_default_int(obj, "priority0", default_priority_0);
@@ -707,21 +696,21 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_set_default_int(obj, "generalTabPos", 0);
obs_data_set_default_int(obj, "macroTabPos", 1);
obs_data_set_default_int(obj, "transitionTabPos", 2);
obs_data_set_default_int(obj, "pauseTabPos", 3);
obs_data_set_default_int(obj, "titleTabPos", 4);
obs_data_set_default_int(obj, "exeTabPos", 5);
obs_data_set_default_int(obj, "regionTabPos", 6);
obs_data_set_default_int(obj, "mediaTabPos", 7);
obs_data_set_default_int(obj, "fileTabPos", 8);
obs_data_set_default_int(obj, "randomTabPos", 9);
obs_data_set_default_int(obj, "timeTabPos", 10);
obs_data_set_default_int(obj, "idleTabPos", 11);
obs_data_set_default_int(obj, "sequenceTabPos", 12);
obs_data_set_default_int(obj, "audioTabPos", 13);
obs_data_set_default_int(obj, "videoTabPos", 14);
obs_data_set_default_int(obj, "networkTabPos", 15);
obs_data_set_default_int(obj, "sceneGroupTabPos", 16);
obs_data_set_default_int(obj, "networkTabPos", 13);
obs_data_set_default_int(obj, "sceneGroupTabPos", 14);
obs_data_set_default_int(obj, "transitionTabPos", 15);
obs_data_set_default_int(obj, "pauseTabPos", 16);
obs_data_set_default_int(obj, "titleTabPos", 2);
obs_data_set_default_int(obj, "exeTabPos", 3);
obs_data_set_default_int(obj, "regionTabPos", 4);
obs_data_set_default_int(obj, "mediaTabPos", 5);
obs_data_set_default_int(obj, "fileTabPos", 6);
obs_data_set_default_int(obj, "randomTabPos", 7);
obs_data_set_default_int(obj, "timeTabPos", 8);
obs_data_set_default_int(obj, "idleTabPos", 9);
obs_data_set_default_int(obj, "sequenceTabPos", 10);
obs_data_set_default_int(obj, "audioTabPos", 11);
obs_data_set_default_int(obj, "videoTabPos", 12);
obs_data_set_default_int(obj, "triggerTabPos", 17);
tabOrder.clear();
@@ -878,8 +867,6 @@ void AdvSceneSwitcher::setupGeneralTab()
ui->showTrayNotifications->setChecked(
switcher->showSystemTrayNotifications);
ui->uiHintsDisable->setChecked(switcher->disableHints);
ui->highlightExecutedMacros->setChecked(
switcher->highlightExecutedMacros);
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
for (int p : switcher->functionNamesByPriority) {

View File

@@ -52,14 +52,13 @@ public:
bool addNewMacro(std::string &name, std::string format = "");
Macro *getSelectedMacro();
void SetEditMacro(Macro &m);
void SetMacroEditAreaDisabled(bool);
void HighlightAction(int idx);
void HighlightCondition(int idx);
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void SetActionData(Macro &m);
void SetConditionData(Macro &m);
void ConnectControlSignals(MacroActionEdit *);
void ConnectControlSignals(MacroConditionEdit *);
void SwapActions(Macro *m, int pos1, int pos2);
void SwapConditions(Macro *m, int pos1, int pos2);
@@ -92,6 +91,9 @@ signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
void MacroRenamed(const QString &oldName, const QString newName);
void HighlightMacrosChanged(bool value);
void HighlightActionsChanged(bool value);
void HighlightConditionsChanged(bool value);
void SceneGroupAdded(const QString &name);
void SceneGroupRemoved(const QString &name);
void SceneGroupRenamed(const QString &oldName, const QString newName);
@@ -132,6 +134,8 @@ public slots:
void on_actionRemove_clicked();
void on_actionUp_clicked();
void on_actionDown_clicked();
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
void DeleteMacroSegementHotkey();
void ShowMacroContextMenu(const QPoint &);
void ShowMacroActionsContextMenu(const QPoint &);
@@ -144,11 +148,13 @@ public slots:
void MinimizeActions();
void MinimizeConditions();
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(int idx);
void RemoveMacroAction(int idx);
void MoveMacroActionUp(int idx);
void MoveMacroActionDown(int idx);
void MacroConditionSelectionChanged(int idx);
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
@@ -158,8 +164,9 @@ public slots:
void ResetOpacityActionControls();
void ResetOpacityConditionControls();
void HighlightControls();
void HighlightMatchedMacros();
void MacroDragDropReorder(QModelIndex, int, int, QModelIndex, int);
void HighlightOnChange();
void on_macroProperties_clicked();
void on_screenRegionSwitches_currentRowChanged(int idx);
void on_showFrame_clicked();
@@ -190,7 +197,6 @@ public slots:
void on_saveWindowGeo_stateChanged(int state);
void on_showTrayNotifications_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_highlightExecutedMacros_stateChanged(int state);
void on_hideLegacyTabs_stateChanged(int state);
void on_exportSettings_clicked();
@@ -295,11 +301,16 @@ public slots:
void on_close_clicked();
private:
void SetSelection(MacroSegmentList *, int);
bool MacroTabIsInFocus();
MacroSegmentList *conditionsList = nullptr;
MacroSegmentList *actionsList = nullptr;
enum class MacroSection {
CONDITIONS,
ACTIONS,
};
MacroSection lastInteracted = MacroSection::CONDITIONS;
int currentConditionIdx = -1;
int currentActionIdx = -1;
};

View File

@@ -21,6 +21,7 @@ public:
const char *unitName = "displayUnit");
bool DurationReached();
bool IsReset();
double TimeRemaining();
void SetTimeRemaining(double);
void Reset();
@@ -57,57 +58,3 @@ private:
double _unitMultiplier;
};
enum class DurationCondition {
NONE,
MORE,
EQUAL,
LESS,
};
class DurationConstraint {
public:
void Save(obs_data_t *obj, const char *condName = "time_constraint",
const char *secondsName = "seconds",
const char *unitName = "displayUnit");
void Load(obs_data_t *obj, const char *condName = "time_constraint",
const char *secondsName = "seconds",
const char *unitName = "displayUnit");
void SetCondition(DurationCondition cond) { _type = cond; }
void SetDuration(const Duration &dur) { _dur = dur; }
void SetValue(double value) { _dur.seconds = value; }
void SetUnit(DurationUnit u) { _dur.displayUnit = u; }
DurationCondition GetCondition() { return _type; }
Duration GetDuration() { return _dur; }
bool DurationReached();
void Reset();
private:
DurationCondition _type = DurationCondition::NONE;
Duration _dur;
bool _timeReached = false;
};
class DurationConstraintEdit : public QWidget {
Q_OBJECT
public:
DurationConstraintEdit(QWidget *parent = nullptr);
void SetValue(DurationConstraint &value);
void SetUnit(DurationUnit u);
void SetDuration(const Duration &d);
private slots:
void _ConditionChanged(int value);
void ToggleClicked();
signals:
void DurationChanged(double value);
void UnitChanged(DurationUnit u);
void ConditionChanged(DurationCondition value);
private:
void Collapse(bool collapse);
DurationSelection *_duration;
QComboBox *_condition;
QPushButton *_toggle;
};

View File

@@ -3,6 +3,7 @@
#include "duration-control.hpp"
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QCheckBox>
#include <QHBoxLayout>
@@ -13,6 +14,11 @@ enum class AudioAction {
MASTER_VOLUME,
};
enum class FadeType {
DURATION,
RATE,
};
class MacroActionAudio : public MacroAction {
public:
MacroActionAudio(Macro *m) : MacroAction(m) {}
@@ -29,11 +35,23 @@ public:
OBSWeakSource _audioSource;
AudioAction _action = AudioAction::MUTE;
FadeType _fadeType = FadeType::DURATION;
int _volume = 0;
bool _fade = false;
Duration _duration;
double _rate = 100.;
bool _wait = false;
bool _abortActiveFade = false;
private:
void StartFade();
void FadeVolume();
void SetVolume(float vol);
float GetVolume();
void SetFadeActive(bool value);
bool FadeActive();
std::atomic_int *GetFadeIdPtr();
static bool _registered;
static const std::string id;
};
@@ -45,7 +63,6 @@ public:
MacroActionAudioEdit(
QWidget *parent,
std::shared_ptr<MacroActionAudio> entryData = nullptr);
void SetWidgetVisibility();
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
@@ -61,18 +78,29 @@ private slots:
void VolumeChanged(int value);
void FadeChanged(int value);
void DurationChanged(double seconds);
void RateChanged(double value);
void WaitChanged(int value);
void AbortActiveFadeChanged(int value);
void FadeTypeChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_audioSources;
QComboBox *_actions;
QComboBox *_fadeTypes;
QSpinBox *_volumePercent;
QCheckBox *_fade;
DurationSelection *_duration;
QHBoxLayout *_fadeLayout;
QDoubleSpinBox *_rate;
QCheckBox *_wait;
QCheckBox *_abortActiveFade;
QHBoxLayout *_fadeTypeLayout;
QVBoxLayout *_fadeOptionsLayout;
std::shared_ptr<MacroActionAudio> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -1,5 +1,8 @@
#pragma once
#include "macro.hpp"
#include "macro-action.hpp"
#include <QComboBox>
#include <memory>
struct MacroActionInfo {
using TCreateMethod = std::shared_ptr<MacroAction> (*)(Macro *m);

View File

@@ -2,7 +2,9 @@
#include "macro-action-edit.hpp"
#include "resizing-text-edit.hpp"
#include <QComboBox>
#include <QSpinBox>
#include <QPushButton>
enum class FilterAction {
ENABLE,

View File

@@ -2,6 +2,7 @@
#include "macro-action-edit.hpp"
#include "hotkey.hpp"
#include <QComboBox>
#include <QCheckBox>
#include <QSpinBox>
#include <QHBoxLayout>

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include <QSpinBox>
#include <QHBoxLayout>
@@ -11,6 +12,7 @@ enum class MediaAction {
RESTART,
NEXT,
PREVIOUS,
SEEK,
};
class MacroActionMedia : public MacroAction {
@@ -29,6 +31,7 @@ public:
OBSWeakSource _mediaSource;
MediaAction _action = MediaAction::PLAY;
Duration _seek;
private:
static bool _registered;
@@ -54,15 +57,20 @@ public:
private slots:
void SourceChanged(const QString &text);
void ActionChanged(int value);
void DurationChanged(double value);
void DurationUnitChanged(DurationUnit unit);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_mediaSources;
QComboBox *_actions;
DurationSelection *_seek;
std::shared_ptr<MacroActionMedia> _entryData;
private:
void SetWidgetVisibility();
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -1,6 +1,8 @@
#pragma once
#include "macro-action-edit.hpp"
#include <QComboBox>
class MacroActionProfile : public MacroAction {
public:
MacroActionProfile(Macro *m) : MacroAction(m) {}

View File

@@ -28,14 +28,6 @@ public:
ReplayBufferAction _action = ReplayBufferAction::STOP;
private:
// Add artifical delay before trying to save the replay buffer again.
//
// Continiously calling obs_frontend_replay_buffer_save() does not
// result in any output actually being written.
// OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED also seems to be sent before
// any data is written.
Duration _duration;
static bool _registered;
static const std::string id;
};
@@ -62,10 +54,10 @@ private slots:
protected:
QComboBox *_actions;
QLabel *_saveWarning;
std::shared_ptr<MacroActionReplayBuffer> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -1,6 +1,8 @@
#pragma once
#include "macro-action-edit.hpp"
#include <QComboBox>
class MacroActionSceneCollection : public MacroAction {
public:
MacroActionSceneCollection(Macro *m) : MacroAction(m) {}

View File

@@ -1,39 +0,0 @@
#pragma once
#include "macro-action-edit.hpp"
class MacroActionSceneSwap : public MacroAction {
public:
MacroActionSceneSwap(Macro *m) : MacroAction(m) {}
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(Macro *m)
{
return std::make_shared<MacroActionSceneSwap>(m);
}
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

@@ -1,6 +1,8 @@
#pragma once
#include "macro-action-edit.hpp"
#include <QComboBox>
class MacroActionScreenshot : public MacroAction {
public:
MacroActionScreenshot(Macro *m) : MacroAction(m) {}

View File

@@ -5,6 +5,7 @@
#include <QSpinBox>
#include <QLabel>
#include <QPushButton>
#include <QComboBox>
enum class SourceAction {
ENABLE,

View File

@@ -1,10 +1,10 @@
#pragma once
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <chrono>
enum class StreamAction {
STOP,
@@ -27,9 +27,8 @@ public:
StreamAction _action = StreamAction::STOP;
private:
// Acts as a safeguard for misconfigured streaming setups leading to an
// endless error spam.
Duration _retryCooldown;
bool CooldownDurationReached();
static std::chrono::high_resolution_clock::time_point s_lastAttempt;
static bool _registered;
static const std::string id;

View File

@@ -2,9 +2,16 @@
#include "macro-action-edit.hpp"
#include "scene-selection.hpp"
class MacroActionPreviewScene : public MacroAction {
enum class StudioModeAction {
SWAP_SCENE,
SET_SCENE,
ENABLE_STUDIO_MODE,
DISABLE_STUDIO_MODE,
};
class MacroActionSudioMode : public MacroAction {
public:
MacroActionPreviewScene(Macro *m) : MacroAction(m) {}
MacroActionSudioMode(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction();
bool Save(obs_data_t *obj);
@@ -13,9 +20,10 @@ public:
std::string GetId() { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionPreviewScene>(m);
return std::make_shared<MacroActionSudioMode>(m);
}
StudioModeAction _action = StudioModeAction::SWAP_SCENE;
SceneSelection _scene;
private:
@@ -23,33 +31,33 @@ private:
static const std::string id;
};
class MacroActionPreviewSceneEdit : public QWidget {
class MacroActionSudioModeEdit : public QWidget {
Q_OBJECT
public:
MacroActionPreviewSceneEdit(
MacroActionSudioModeEdit(
QWidget *parent,
std::shared_ptr<MacroActionPreviewScene> entryData = nullptr);
std::shared_ptr<MacroActionSudioMode> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionPreviewSceneEdit(
parent,
std::dynamic_pointer_cast<MacroActionPreviewScene>(
action));
return new MacroActionSudioModeEdit(
parent, std::dynamic_pointer_cast<MacroActionSudioMode>(
action));
}
private slots:
void ActionChanged(int value);
void SceneChanged(const SceneSelection &);
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_actions;
SceneSelectionWidget *_scenes;
std::shared_ptr<MacroActionPreviewScene> _entryData;
std::shared_ptr<MacroActionSudioMode> _entryData;
private:
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -2,6 +2,8 @@
#include "macro-action-edit.hpp"
#include "duration-control.hpp"
#include "transition-selection.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
@@ -20,12 +22,26 @@ public:
return std::make_shared<MacroActionTransition>(m);
}
enum class Type {
SCENE,
SCENE_OVERRIDE,
SOURCE_SHOW,
SOURCE_HIDE,
};
Type _type = Type::SCENE;
SceneItemSelection _source;
SceneSelection _scene;
bool _setDuration = true;
bool _setType = true;
bool _setTransitionType = true;
TransitionSelection _transition;
Duration _duration;
private:
void SetSceneTransition();
void SetTransitionOverride();
void SetSourceTransition(bool);
static bool _registered;
static const std::string id;
};
@@ -48,7 +64,10 @@ public:
}
private slots:
void SetTypeChanged(int state);
void ActionChanged(int value);
void SourceChanged(const SceneItemSelection &);
void SceneChanged(const SceneSelection &);
void SetTransitionChanged(int state);
void SetDurationChanged(int state);
void TransitionChanged(const TransitionSelection &);
void DurationChanged(double seconds);
@@ -56,14 +75,19 @@ signals:
void HeaderInfoChanged(const QString &);
protected:
QCheckBox *_setType;
QComboBox *_actions;
SceneItemSelectionWidget *_sources;
SceneSelectionWidget *_scenes;
QCheckBox *_setTransition;
QCheckBox *_setDuration;
TransitionSelectionWidget *_transitions;
DurationSelection *_duration;
QHBoxLayout *_typeLayout;
QHBoxLayout *_transitionLayout;
QHBoxLayout *_durationLayout;
std::shared_ptr<MacroActionTransition> _entryData;
private:
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -0,0 +1,26 @@
#pragma once
#include "macro-segment.hpp"
#include "macro-ref.hpp"
class MacroAction : public MacroSegment {
public:
MacroAction(Macro *m) : MacroSegment(m) {}
virtual bool PerformAction() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual void LogAction();
};
class MacroRefAction : public MacroAction {
public:
MacroRefAction(Macro *m) : MacroAction(m) {}
void ResolveMacroRef();
MacroRef _macro;
};
class MultiMacroRefAction : public MacroAction {
public:
MultiMacroRefAction(Macro *m) : MacroAction(m) {}
void ResolveMacroRef();
std::vector<MacroRef> _macros;
};

View File

@@ -5,6 +5,7 @@
#include <QCheckBox>
#include <QDateTimeEdit>
#include <QComboBox>
#include <QTimer>
enum class DateCondition {
AT,
@@ -37,19 +38,32 @@ public:
return std::make_shared<MacroConditionDate>(m);
}
void SetDate1(const QDate &date);
void SetDate2(const QDate &date);
void SetTime1(const QTime &time);
void SetTime2(const QTime &time);
QDateTime GetDateTime1();
QDateTime GetDateTime2();
QDateTime GetNextMatchDateTime();
DayOfWeekSelection _dayOfWeek = DayOfWeekSelection::ANY;
QDateTime _dateTime = QDateTime::currentDateTime();
QDateTime _dateTime2 = QDateTime::currentDateTime();
bool _ignoreDate = false;
bool _ignoreTime = false;
bool _repeat = false;
bool _updateOnRepeat = true;
Duration _duration;
DateCondition _condition = DateCondition::AT;
bool _dayOfWeekCheck = false;
private:
bool checkDayOfWeek();
bool checkRegularDate();
bool CheckDayOfWeek(int64_t);
bool CheckRegularDate(int64_t);
QDateTime _dateTime = QDateTime::currentDateTime();
QDateTime _dateTime2 = QDateTime::currentDateTime();
// Used depending whether or not _updateOnRepeat is set
QDateTime _origDateTime = QDateTime::currentDateTime();
QDateTime _origDateTime2 = QDateTime::currentDateTime();
static bool _registered;
static const std::string id;
@@ -81,14 +95,18 @@ private slots:
void IgnoreDateChanged(int state);
void IgnoreTimeChanged(int state);
void RepeatChanged(int state);
void UpdateOnRepeatChanged(int state);
void DurationChanged(double seconds);
void DurationUnitChanged(DurationUnit unit);
void AdvancedSettingsToggleClicked();
void ShowNextMatch();
signals:
void HeaderInfoChanged(const QString &);
protected:
QComboBox *_weekCondition;
QComboBox *_dayOfWeek;
QCheckBox *_ignoreWeekTime;
QTimeEdit *_weekTime;
QComboBox *_condition;
@@ -100,17 +118,21 @@ protected:
QCheckBox *_ignoreDate;
QCheckBox *_ignoreTime;
QCheckBox *_repeat;
QLabel *_nextMatchDate;
QCheckBox *_updateOnRepeat;
DurationSelection *_duration;
QPushButton *_advancedSettingsTooggle;
QHBoxLayout *_simpleLayout;
QHBoxLayout *_advancedLayout;
QHBoxLayout *_repeatLayout;
QVBoxLayout *_repeatLayout;
QHBoxLayout *_repeatUpdateLayout;
std::shared_ptr<MacroConditionDate> _entryData;
private:
void SetWidgetStatus();
void ShowSecondDateSelection(bool visible);
QTimer _timer;
bool _loading = true;
};

View File

@@ -1,5 +1,7 @@
#pragma once
#include "macro.hpp"
#include "macro-condition.hpp"
#include <memory>
struct MacroConditionInfo {
using TCreateMethod = std::shared_ptr<MacroCondition> (*)(Macro *m);
@@ -8,7 +10,7 @@ struct MacroConditionInfo {
TCreateMethod _createFunc = nullptr;
TCreateWidgetMethod _createWidgetFunc = nullptr;
std::string _name;
bool _useDurationConstraint = true;
bool _useDurationModifier = true;
};
class MacroConditionFactory {
@@ -22,12 +24,36 @@ public:
static auto GetConditionTypes() { return _methods; }
static std::string GetConditionName(const std::string &);
static std::string GetIdByName(const QString &name);
static bool UsesDurationConstraint(const std::string &id);
static bool UsesDurationModifier(const std::string &id);
private:
static std::map<std::string, MacroConditionInfo> _methods;
};
class DurationModifierEdit : public QWidget {
Q_OBJECT
public:
DurationModifierEdit(QWidget *parent = nullptr);
void SetValue(DurationModifier &value);
void SetUnit(DurationUnit u);
void SetDuration(const Duration &d);
private slots:
void _ModifierChanged(int value);
void ToggleClicked();
signals:
void DurationChanged(double value);
void UnitChanged(DurationUnit u);
void ModifierChanged(DurationModifier::Type value);
private:
void Collapse(bool collapse);
DurationSelection *_duration;
QComboBox *_condition;
QPushButton *_toggle;
};
class MacroConditionEdit : public MacroSegmentEdit {
Q_OBJECT
@@ -44,15 +70,16 @@ private slots:
void LogicSelectionChanged(int idx);
void ConditionSelectionChanged(const QString &text);
void DurationChanged(double seconds);
void DurationConditionChanged(DurationCondition cond);
void DurationModifierChanged(DurationModifier::Type m);
void DurationUnitChanged(DurationUnit unit);
private:
void SetLogicSelection();
MacroSegment *Data();
QComboBox *_logicSelection;
QComboBox *_conditionSelection;
DurationConstraintEdit *_dur;
DurationModifierEdit *_dur;
std::shared_ptr<MacroCondition> *_entryData;
bool _isRoot = true;

View File

@@ -31,7 +31,7 @@ enum class MediaState {
// Just a marker
LAST_OBS_MEDIA_STATE,
// states added for use in the plugin
PLAYED_TO_END = custom_media_states_offset,
PLAYLIST_ENDED = custom_media_states_offset,
ANY,
};
@@ -58,6 +58,7 @@ public:
void ResetSignalHandler();
static void MediaStopped(void *data, calldata_t *);
static void MediaEnded(void *data, calldata_t *);
static void MediaNext(void *data, calldata_t *);
MediaSourceType _sourceType = MediaSourceType::SOURCE;
SceneSelection _scene;
@@ -70,17 +71,19 @@ public:
bool _onlyMatchonChagne = false;
private:
bool CheckTime();
bool CheckState();
bool CheckPlaylistEnd(const obs_media_state);
bool CheckMediaMatch();
bool _stopped = false;
bool _ended = false;
bool _next = false;
// TODO: Remove _alreadyMatched as it does not make much sense when
// time restrictions for macro conditions are available.
// Trigger scene change only once even if media state might trigger repeatedly
bool _alreadyMatched = false;
// Workaround to enable use of "ended" to specify end of VLC playlist
bool _previousStateEnded = false;
bool _playedToEnd = false;
static bool _registered;
static const std::string id;

View File

@@ -0,0 +1,88 @@
#pragma once
#include "macro-segment.hpp"
#include "macro-ref.hpp"
#include "duration-control.hpp"
constexpr auto logic_root_offset = 100;
enum class LogicType {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = 100,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
static inline bool isRootLogicType(LogicType l)
{
return static_cast<int>(l) < logic_root_offset;
}
struct LogicTypeInfo {
std::string _name;
};
class DurationModifier {
public:
enum class Type {
NONE,
MORE,
EQUAL,
LESS,
WITHIN, // Condition will remain true for a set a amount of time
// regardless of current state
};
void Save(obs_data_t *obj, const char *condName = "time_constraint",
const char *secondsName = "seconds",
const char *unitName = "displayUnit");
void Load(obs_data_t *obj, const char *condName = "time_constraint",
const char *secondsName = "seconds",
const char *unitName = "displayUnit");
void SetModifier(Type cond) { _type = cond; }
void SetTimeRemaining(const double &val) { _dur.SetTimeRemaining(val); }
void SetValue(double value) { _dur.seconds = value; }
void SetUnit(DurationUnit u) { _dur.displayUnit = u; }
Type GetType() { return _type; }
Duration GetDuration() { return _dur; }
bool DurationReached();
void Reset();
private:
Type _type = Type::NONE;
Duration _dur;
bool _timeReached = false;
};
class MacroCondition : public MacroSegment {
public:
MacroCondition(Macro *m) : MacroSegment(m) {}
virtual bool CheckCondition() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
LogicType GetLogicType() { return _logic; }
void SetLogicType(LogicType logic) { _logic = logic; }
static const std::map<LogicType, LogicTypeInfo> logicTypes;
void ResetDuration();
void CheckDurationModifier(bool &val);
DurationModifier GetDurationModifier() { return _duration; }
void SetDurationModifier(DurationModifier::Type m);
void SetDurationUnit(DurationUnit u);
void SetDuration(double seconds);
private:
LogicType _logic = LogicType::ROOT_NONE;
DurationModifier _duration;
};
class MacroRefCondition : public MacroCondition {
public:
MacroRefCondition(Macro *m) : MacroCondition(m) {}
void ResolveMacroRef();
MacroRef _macro;
};

View File

@@ -2,6 +2,7 @@
#include <QLabel>
#include <QCheckBox>
#include <QTimer>
#include <memory>
class Macro;
@@ -10,15 +11,22 @@ class MacroListEntryWidget : public QWidget {
Q_OBJECT
public:
MacroListEntryWidget(std::shared_ptr<Macro>, QWidget *parent);
MacroListEntryWidget(std::shared_ptr<Macro>, bool highlight,
QWidget *parent);
void SetName(const QString &);
void SetMacro(std::shared_ptr<Macro> &);
private slots:
void PauseChanged(int);
void HighlightExecuted();
void UpdatePaused();
void EnableHighlight(bool);
private:
QTimer _timer;
QLabel *_name;
QCheckBox *_running;
std::shared_ptr<Macro> _macro;
bool _highlightExecutedMacros = false;
};

View File

@@ -0,0 +1,35 @@
#pragma once
#include "macro.hpp"
#include <QWidget>
#include <QDialog>
#include <QCheckBox>
#include <obs-data.h>
// Global macro properties
class MacroProperties {
public:
void Save(obs_data_t *obj);
void Load(obs_data_t *obj);
bool _highlightExecuted = false;
bool _highlightConditions = false;
bool _highlightActions = false;
};
// Dialog for configuring global and macro specific settings
class MacroPropertiesDialog : public QDialog {
Q_OBJECT
public:
MacroPropertiesDialog(QWidget *parent, const MacroProperties &,
Macro *macro);
static bool AskForSettings(QWidget *parent, MacroProperties &userInput,
Macro *macro);
private:
QCheckBox *_executed;
QCheckBox *_conditions;
QCheckBox *_actions;
QCheckBox *_hotkeys;
};

23
src/headers/macro-ref.hpp Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <QString>
#include <obs-module.h>
class Macro;
class MacroRef {
public:
MacroRef(){};
MacroRef(std::string name);
void UpdateRef();
void UpdateRef(std::string name);
void UpdateRef(QString name);
void Save(obs_data_t *obj);
void Load(obs_data_t *obj);
Macro *get();
Macro *operator->();
private:
std::string _name = "";
Macro *_ref = nullptr;
};

View File

@@ -1,25 +1,61 @@
#pragma once
#include "macro-segment.hpp"
#include <QWidget>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QLabel>
#include <thread>
class MacroSegmentList : public QScrollArea {
Q_OBJECT
public:
MacroSegmentList(QWidget *parent = nullptr);
virtual ~MacroSegmentList();
void SetHelpMsg(const QString &msg);
void SetHelpMsgVisible(bool visible);
MacroSegmentEdit *WidgetAt(int idx);
void Insert(int idx, QWidget *widget);
void Add(QWidget *widget);
void Remove(int idx);
void Clear(int idx = 0); // Clear all elements >= idx
void Highlight(int idx);
void SetCollapsed(bool);
void SetSelection(int idx);
QVBoxLayout *ContentLayout() { return _contentLayout; }
signals:
void SelectionChagned(int idx);
void Reorder(int source, int target);
protected:
bool eventFilter(QObject *object, QEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
private:
int GetDragIndex(const QPoint &);
int GetDropIndex(const QPoint &);
int GetWidgetIdx(const QPoint &);
void CheckScroll();
void CheckDropLine(const QPoint &);
bool IsInListArea(const QPoint &);
QRect GetContentItemRectWithPadding(int idx);
void HideLastDropLine();
int _dragPosition = -1;
int _dropLineIdx = -1;
QPoint _dragCursorPos;
std::thread _autoScrollThread;
std::atomic_bool _autoScroll{false};
QVBoxLayout *_layout;
QVBoxLayout *_contentLayout;
QLabel *_helpMsg;
};

View File

@@ -2,6 +2,7 @@
#include <QWidget>
#include <QFrame>
#include <QVBoxLayout>
#include <QTimer>
#include <obs.hpp>
class Macro;
@@ -18,10 +19,14 @@ public:
virtual bool Load(obs_data_t *obj) = 0;
virtual std::string GetShortDesc();
virtual std::string GetId() = 0;
void SetHighlight();
bool Highlight();
protected:
int _idx = 0;
bool _collapsed = false;
// UI helper
bool _highlight = false;
private:
Macro *_macro = nullptr;
@@ -34,7 +39,7 @@ class MacroSegmentEdit : public QWidget {
Q_OBJECT
public:
MacroSegmentEdit(QWidget *parent = nullptr);
MacroSegmentEdit(bool highlight, QWidget *parent = nullptr);
// Use this function to avoid accidental edits when scrolling through
// list of actions and conditions
void SetFocusPolicyOfWidgets();
@@ -44,6 +49,8 @@ public:
protected slots:
void HeaderInfoChanged(const QString &);
void Collapsed(bool);
void Highlight();
void EnableHighlight(bool);
signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
@@ -51,18 +58,43 @@ signals:
void SceneGroupAdded(const QString &name);
void SceneGroupRemoved(const QString &name);
void SceneGroupRenamed(const QString &oldName, const QString newName);
void SelectionChagned(int idx);
protected:
void mousePressEvent(QMouseEvent *event);
Section *_section;
QLabel *_headerInfo;
QFrame *_frame;
QVBoxLayout *_highLightFrameLayout;
QWidget *_frame;
QVBoxLayout *_contentLayout;
private:
enum class DropLineState {
NONE,
ABOVE,
BELOW,
};
virtual MacroSegment *Data() = 0;
void ShowDropLine(DropLineState);
// The reason for using two separate frame widget each with their own
// stylesheet and changing their visibility vs. using a single frame
// and changing the stylesheet at runtime is that the operation of
// adjusting the stylesheet is very expensive and can take multiple
// hundred milliseconds per widget.
// This performance impact would hurt in areas like drag and drop or
// emitting the "SelectionChanged" signal.
QFrame *_noBorderframe;
QFrame *_borderFrame;
// In most cases the line above the widget will be used.
// The lower one will only be used if the segment is the last one in
// the list.
QFrame *_dropLineAbove;
QFrame *_dropLineBelow;
bool _showHighlight;
QTimer _timer;
friend class MacroSegmentList;
};
class MouseWheelWidgetAdjustmentGuard : public QObject {

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro-segment.hpp"
#include "duration-control.hpp"
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include "macro-ref.hpp"
#include <QString>
#include <string>
@@ -13,62 +14,6 @@
constexpr auto macro_func = 10;
constexpr auto logic_root_offset = 100;
enum class LogicType {
ROOT_NONE = 0,
ROOT_NOT,
ROOT_LAST,
// leave some space for potential expansion
NONE = 100,
AND,
OR,
AND_NOT,
OR_NOT,
LAST,
};
static inline bool isRootLogicType(LogicType l)
{
return static_cast<int>(l) < logic_root_offset;
}
struct LogicTypeInfo {
std::string _name;
};
class MacroCondition : public MacroSegment {
public:
MacroCondition(Macro *m) : MacroSegment(m) {}
virtual bool CheckCondition() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
LogicType GetLogicType() { return _logic; }
void SetLogicType(LogicType logic) { _logic = logic; }
static const std::map<LogicType, LogicTypeInfo> logicTypes;
bool DurationReached() { return _duration.DurationReached(); }
void ResetDuration() { _duration.Reset(); }
DurationConstraint GetDurationConstraint() { return _duration; }
void SetDurationConstraint(const DurationConstraint &dur);
void SetDurationCondition(DurationCondition cond);
void SetDurationUnit(DurationUnit u);
void SetDuration(double seconds);
private:
LogicType _logic = LogicType::ROOT_NONE;
DurationConstraint _duration;
};
class MacroAction : public MacroSegment {
public:
MacroAction(Macro *m) : MacroSegment(m) {}
virtual bool PerformAction() = 0;
virtual bool Save(obs_data_t *obj) = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual void LogAction();
};
class Macro {
public:
Macro(const std::string &name = "");
@@ -78,6 +23,7 @@ public:
bool PerformActions(bool forceParallel = false,
bool ignorePause = false);
bool Matched() { return _matched; }
int64_t MsSinceLastCheck();
std::string Name() { return _name; }
void SetName(const std::string &name);
void SetRunInParallel(bool parallel) { _runInParallel = parallel; }
@@ -88,7 +34,9 @@ public:
bool MatchOnChange() { return _matchOnChange; }
int GetCount() { return _count; };
void ResetCount() { _count = 0; };
void Stop() { _stop = true; }
void AddHelperThread(std::thread &&);
bool GetStop() { return _stop; }
void Stop();
std::deque<std::shared_ptr<MacroCondition>> &Conditions()
{
return _conditions;
@@ -108,6 +56,11 @@ public:
// UI helpers for the macro tab
bool WasExecutedRecently();
bool OnChangePreventedActionsRecently();
void ResetUIHelpers();
void EnablePauseHotkeys(bool);
bool PauseHotkeysEnabled();
private:
void SetupHotkeys();
@@ -116,6 +69,7 @@ private:
void ResetTimers();
void RunActions(bool &ret, bool ignorePause);
void RunActions(bool ignorePause);
void SetOnChangeHighlight();
std::string _name = "";
std::deque<std::shared_ptr<MacroCondition>> _conditions;
@@ -126,55 +80,23 @@ private:
bool _matchOnChange = false;
bool _paused = false;
int _count = 0;
bool _registerHotkeys = true;
obs_hotkey_id _pauseHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
// UI helpers for the macro tab
bool _wasExecutedRecently = false;
bool _onChangeTriggered = false;
std::chrono::high_resolution_clock::time_point _lastCheckTime{};
bool _die = false;
bool _stop = false;
bool _done = true;
std::thread _thread;
std::thread _backgroundThread;
std::vector<std::thread> _helperThreads;
};
Macro *GetMacroByName(const char *name);
Macro *GetMacroByQString(const QString &name);
class MacroRef {
public:
MacroRef(){};
MacroRef(std::string name);
void UpdateRef();
void UpdateRef(std::string name);
void UpdateRef(QString name);
void Save(obs_data_t *obj);
void Load(obs_data_t *obj);
Macro *get();
Macro *operator->();
private:
std::string _name = "";
Macro *_ref = nullptr;
};
class MacroRefCondition : public MacroCondition {
public:
MacroRefCondition(Macro *m) : MacroCondition(m) {}
void ResolveMacroRef();
MacroRef _macro;
};
class MacroRefAction : public MacroAction {
public:
MacroRefAction(Macro *m) : MacroAction(m) {}
void ResolveMacroRef();
MacroRef _macro;
};
class MultiMacroRefAction : public MacroAction {
public:
MultiMacroRefAction(Macro *m) : MacroAction(m) {}
void ResolveMacroRef();
std::vector<MacroRef> _macros;
};

View File

@@ -26,6 +26,7 @@
#include "switch-network.hpp"
#include "macro.hpp"
#include "macro-properties.hpp"
#include "duration-control.hpp"
constexpr auto default_interval = 300;
@@ -87,8 +88,7 @@ struct SwitcherData {
bool stop = false;
bool verbose = false;
bool disableHints = false;
bool hideLegacyTabs = false;
bool highlightExecutedMacros = false;
bool hideLegacyTabs = true;
bool showSystemTrayNotifications = false;
bool showFrame = false;
bool transitionOverrideOverride = false;
@@ -111,13 +111,17 @@ struct SwitcherData {
StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
std::vector<std::thread> audioHelperThreads;
std::atomic_bool masterAudioFadeActive = {false};
std::unordered_map<std::string, std::atomic_bool> activeAudioFades;
struct AudioFadeInfo {
std::atomic_bool active = {false};
std::atomic_int id = {0};
};
AudioFadeInfo masterAudioFade;
std::unordered_map<std::string, AudioFadeInfo> activeAudioFades;
Duration cooldown;
std::chrono::high_resolution_clock::time_point lastMatchTime;
MacroProperties macroProperties;
std::deque<std::shared_ptr<Macro>> macros;
std::condition_variable macroWaitCv;
std::atomic_bool abortMacroWait = {false};
@@ -215,6 +219,8 @@ struct SwitcherData {
obs_hotkey_id startHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id stopHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id toggleHotkey = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id upMacroSegment = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id downMacroSegment = OBS_INVALID_HOTKEY_ID;
obs_hotkey_id removeMacroSegment = OBS_INVALID_HOTKEY_ID;
bool saveWindowGeo = false;
@@ -229,6 +235,7 @@ struct SwitcherData {
void setWaitScene();
bool sceneChangedDuringWait();
bool anySceneTransitionStarted();
bool prioFuncsValid();

View File

@@ -43,7 +43,7 @@ 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 deleteLayoutItemWidget(QLayoutItem *item);
void clearLayout(QLayout *layout, int afterIdx = 0);
void setLayoutVisible(QLayout *layout, bool visible);
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
@@ -63,8 +63,8 @@ void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
bool addAny = false);
void populateWindowSelection(QComboBox *sel, bool addSelect = true);
void populateAudioSelection(QComboBox *sel, bool addSelect = true);
void populateVideoSelection(QComboBox *sel, bool addScenes = false,
bool addSelect = true);
void populateVideoSelection(QComboBox *sel, bool addMainOutput = false,
bool addScenes = false, bool addSelect = true);
void populateMediaSelection(QComboBox *sel, bool addSelect = true);
void populateProcessSelection(QComboBox *sel, bool addSelect = true);
void populateSourceSelection(QComboBox *list, bool addSelect = true);
@@ -82,3 +82,4 @@ void populateSceneItemSelection(QComboBox *list,
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
void populateSourceGroupSelection(QComboBox *list);
bool windowPosValid(QPoint pos);
bool doubleEquals(double left, double right, double epsilon);

View File

@@ -34,6 +34,28 @@ void startStopToggleHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
}
}
void upMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
bool pressed)
{
if (pressed && switcher->settingsWindowOpened &&
AdvSceneSwitcher::window) {
QMetaObject::invokeMethod(AdvSceneSwitcher::window,
"UpMacroSegementHotkey",
Qt::QueuedConnection);
}
}
void downMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
bool pressed)
{
if (pressed && switcher->settingsWindowOpened &&
AdvSceneSwitcher::window) {
QMetaObject::invokeMethod(AdvSceneSwitcher::window,
"DownMacroSegementHotkey",
Qt::QueuedConnection);
}
}
void removeMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
bool pressed)
{
@@ -60,6 +82,15 @@ void registerHotkeys()
obs_module_text(
"AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey"),
startStopToggleHotkeyFunc, NULL);
switcher->upMacroSegment = obs_hotkey_register_frontend(
"upMacroSegmentSwitcherHotkey",
obs_module_text("AdvSceneSwitcher.hotkey.upMacroSegmentHotkey"),
upMacroSegmentHotkeyFunc, NULL);
switcher->downMacroSegment = obs_hotkey_register_frontend(
"downMacroSegmentSwitcherHotkey",
obs_module_text(
"AdvSceneSwitcher.hotkey.downMacroSegmentHotkey"),
downMacroSegmentHotkeyFunc, NULL);
switcher->removeMacroSegment = obs_hotkey_register_frontend(
"removeMacroSegmentSwitcherHotkey",
obs_module_text(
@@ -69,26 +100,28 @@ void registerHotkeys()
switcher->hotkeysRegistered = true;
}
void saveHotkey(obs_data_t *obj, obs_hotkey_id id, const char *name)
{
obs_data_array_t *a = obs_hotkey_save(id);
obs_data_set_array(obj, name, a);
obs_data_array_release(a);
}
void SwitcherData::saveHotkeys(obs_data_t *obj)
{
obs_data_array_t *startHotkeyArrray = obs_hotkey_save(startHotkey);
obs_data_set_array(obj, "startHotkey", startHotkeyArrray);
obs_data_array_release(startHotkeyArrray);
saveHotkey(obj, startHotkey, "startHotkey");
saveHotkey(obj, stopHotkey, "stopHotkey");
saveHotkey(obj, toggleHotkey, "toggleHotkey");
saveHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
saveHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
saveHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");
}
obs_data_array_t *stopHotkeyArrray = obs_hotkey_save(stopHotkey);
obs_data_set_array(obj, "stopHotkey", stopHotkeyArrray);
obs_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray = obs_hotkey_save(toggleHotkey);
obs_data_set_array(obj, "toggleHotkey", toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
obs_data_array_t *removeSegmentArrray =
obs_hotkey_save(removeMacroSegment);
obs_data_set_array(obj, "removeMacroSegmentHotkey",
removeSegmentArrray);
obs_data_array_release(removeSegmentArrray);
void loadHotkey(obs_data_t *obj, obs_hotkey_id id, const char *name)
{
obs_data_array_t *a = obs_data_get_array(obj, name);
obs_hotkey_load(id, a);
obs_data_array_release(a);
}
void SwitcherData::loadHotkeys(obs_data_t *obj)
@@ -96,24 +129,10 @@ void SwitcherData::loadHotkeys(obs_data_t *obj)
if (!hotkeysRegistered) {
registerHotkeys();
}
obs_data_array_t *startHotkeyArrray =
obs_data_get_array(obj, "startHotkey");
obs_hotkey_load(startHotkey, startHotkeyArrray);
obs_data_array_release(startHotkeyArrray);
obs_data_array_t *stopHotkeyArrray =
obs_data_get_array(obj, "stopHotkey");
obs_hotkey_load(stopHotkey, stopHotkeyArrray);
obs_data_array_release(stopHotkeyArrray);
obs_data_array_t *toggleHotkeyArrray =
obs_data_get_array(obj, "toggleHotkey");
obs_hotkey_load(toggleHotkey, toggleHotkeyArrray);
obs_data_array_release(toggleHotkeyArrray);
obs_data_array_t *removeSegmentArrray =
obs_data_get_array(obj, "removeMacroSegmentHotkey");
obs_hotkey_load(removeMacroSegment, removeSegmentArrray);
obs_data_array_release(removeSegmentArrray);
loadHotkey(obj, startHotkey, "startHotkey");
loadHotkey(obj, stopHotkey, "stopHotkey");
loadHotkey(obj, toggleHotkey, "toggleHotkey");
loadHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
loadHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
loadHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");
}

View File

@@ -18,92 +18,147 @@ const static std::map<AudioAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.audio.type.masterVolume"},
};
const static std::map<FadeType, std::string> fadeTypes = {
{FadeType::DURATION,
"AdvSceneSwitcher.action.audio.fade.type.duration"},
{FadeType::RATE, "AdvSceneSwitcher.action.audio.fade.type.rate"},
};
constexpr auto fadeInterval = std::chrono::milliseconds(100);
constexpr float minFade = 0.000001f;
void fadeSourceVolume(Duration duration, float vol, OBSWeakSource audioSource)
void MacroActionAudio::SetFadeActive(bool value)
{
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
if (_action == AudioAction::SOURCE_VOLUME) {
switcher->activeAudioFades[GetWeakSourceName(_audioSource)]
.active = value;
} else {
switcher->masterAudioFade.active = value;
}
float curVol = obs_source_get_volume(s);
obs_source_release(s);
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
}
if (volStep < minFade) {
switcher->activeAudioFades[GetWeakSourceName(audioSource)] =
false;
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
auto s = obs_weak_source_get_source(audioSource);
if (!s) {
return;
bool MacroActionAudio::FadeActive()
{
bool active = true;
if (_action == AudioAction::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find(
GetWeakSourceName(_audioSource));
if (it == switcher->activeAudioFades.end()) {
return false;
}
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_source_set_volume(s, curVol);
std::this_thread::sleep_for(fadeInterval);
active = it->second.active;
} else {
active = switcher->masterAudioFade.active;
}
return active;
}
std::atomic_int *MacroActionAudio::GetFadeIdPtr()
{
if (_action == AudioAction::SOURCE_VOLUME) {
auto it = switcher->activeAudioFades.find(
GetWeakSourceName(_audioSource));
if (it == switcher->activeAudioFades.end()) {
return nullptr;
}
return &it->second.id;
}
return &switcher->masterAudioFade.id;
}
void MacroActionAudio::SetVolume(float vol)
{
if (_action == AudioAction::SOURCE_VOLUME) {
auto s = obs_weak_source_get_source(_audioSource);
obs_source_set_volume(s, vol);
obs_source_release(s);
} else {
obs_set_master_volume(vol);
}
}
float MacroActionAudio::GetVolume()
{
float curVol;
if (_action == AudioAction::SOURCE_VOLUME) {
auto s = obs_weak_source_get_source(_audioSource);
if (!s) {
return 0.;
}
curVol = obs_source_get_volume(s);
obs_source_release(s);
} else {
curVol = obs_get_master_volume();
}
return curVol;
}
void MacroActionAudio::FadeVolume()
{
float vol = (float)_volume / 100.0f;
float curVol = GetVolume();
bool volIncrease = curVol <= vol;
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
int nrSteps = 0;
float volStep = 0.;
if (_fadeType == FadeType::DURATION) {
nrSteps = _duration.seconds * 1000 / fadeInterval.count();
volStep = volDiff / nrSteps;
} else {
volStep = _rate / 1000.0f;
nrSteps = volDiff / volStep;
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = false;
}
void fadeMasterVolume(Duration duration, float vol)
{
float curVol = obs_get_master_volume();
bool volIncrease = curVol <= vol;
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
float volStep = volDiff / nrSteps;
if (volStep < minFade) {
switcher->masterAudioFadeActive = false;
if (volStep < minFade || nrSteps <= 1) {
SetVolume(vol);
SetFadeActive(false);
return;
}
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
auto macro = GetMacro();
int step = 0;
auto fadeId = GetFadeIdPtr();
int expectedFadeId = ++(*fadeId);
for (; step < nrSteps && !macro->GetStop() && expectedFadeId == *fadeId;
++step) {
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
obs_set_master_volume(curVol);
SetVolume(curVol);
std::this_thread::sleep_for(fadeInterval);
}
switcher->masterAudioFadeActive = false;
// As a final step set desired volume once again in case floating-point
// precision errors compounded to a noticeable error
if (step == nrSteps) {
SetVolume(vol);
}
SetFadeActive(false);
}
void startSourceFade(Duration &duration, float vol, OBSWeakSource audioSource)
void MacroActionAudio::StartFade()
{
if (!audioSource) {
if (_action == AudioAction::SOURCE_VOLUME && !_audioSource) {
return;
}
auto it =
switcher->activeAudioFades.find(GetWeakSourceName(audioSource));
if (it != switcher->activeAudioFades.end() && it->second == true) {
if (FadeActive() && !_abortActiveFade) {
blog(LOG_WARNING,
"Audio fade for volume of %s already active! New fade request will be ignored!",
GetWeakSourceName(audioSource).c_str());
(_action == AudioAction::SOURCE_VOLUME)
? GetWeakSourceName(_audioSource).c_str()
: "master volume");
return;
}
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = true;
switcher->audioHelperThreads.emplace_back(fadeSourceVolume, duration,
vol, audioSource);
}
SetFadeActive(true);
void startMasterFade(Duration &duration, float vol)
{
if (switcher->masterAudioFadeActive) {
blog(LOG_WARNING,
"Audio fade for master volume already active! New fade request will be ignored!");
return;
if (_wait) {
FadeVolume();
} else {
GetMacro()->AddHelperThread(
std::thread(&MacroActionAudio::FadeVolume, this));
}
switcher->masterAudioFadeActive = true;
switcher->audioHelperThreads.emplace_back(fadeMasterVolume, duration,
vol);
}
bool MacroActionAudio::PerformAction()
@@ -117,18 +172,11 @@ bool MacroActionAudio::PerformAction()
obs_source_set_muted(s, false);
break;
case AudioAction::SOURCE_VOLUME:
if (_fade && _duration.seconds != 0) {
startSourceFade(_duration, (float)_volume / 100.0f,
_audioSource);
} else {
obs_source_set_volume(s, (float)_volume / 100.0f);
}
break;
case AudioAction::MASTER_VOLUME:
if (_fade && _duration.seconds != 0) {
startMasterFade(_duration, (float)_volume / 100.0f);
if (_fade) {
StartFade();
} else {
obs_set_master_volume((float)_volume / 100.0f);
SetVolume((float)_volume / 100.0f);
}
break;
default:
@@ -161,7 +209,11 @@ bool MacroActionAudio::Save(obs_data_t *obj)
GetWeakSourceName(_audioSource).c_str());
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "volume", _volume);
obs_data_set_double(obj, "rate", _rate);
obs_data_set_bool(obj, "fade", _fade);
obs_data_set_int(obj, "fadeType", static_cast<int>(_fadeType));
obs_data_set_bool(obj, "wait", _wait);
obs_data_set_bool(obj, "abortActiveFade", _abortActiveFade);
return true;
}
@@ -173,7 +225,24 @@ bool MacroActionAudio::Load(obs_data_t *obj)
_audioSource = GetWeakSourceByName(audioSourceName);
_action = static_cast<AudioAction>(obs_data_get_int(obj, "action"));
_volume = obs_data_get_int(obj, "volume");
_rate = obs_data_get_double(obj, "rate");
_fade = obs_data_get_bool(obj, "fade");
if (obs_data_has_user_value(obj, "wait")) {
_wait = obs_data_get_bool(obj, "wait");
} else {
_wait = false;
}
if (obs_data_has_user_value(obj, "fadeType")) {
_fadeType = static_cast<FadeType>(
obs_data_get_int(obj, "fadeType"));
} else {
_fadeType = FadeType::DURATION;
}
if (obs_data_has_user_value(obj, "abortActiveFade")) {
_abortActiveFade = obs_data_get_bool(obj, "abortActiveFade");
} else {
_abortActiveFade = false;
}
return true;
}
@@ -192,21 +261,41 @@ static inline void populateActionSelection(QComboBox *list)
}
}
static inline void populateFadeTypeSelection(QComboBox *list)
{
for (auto entry : fadeTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionAudioEdit::MacroActionAudioEdit(
QWidget *parent, std::shared_ptr<MacroActionAudio> entryData)
: QWidget(parent)
: QWidget(parent),
_audioSources(new QComboBox),
_actions(new QComboBox),
_fadeTypes(new QComboBox),
_volumePercent(new QSpinBox),
_fade(new QCheckBox),
_duration(new DurationSelection(parent, false)),
_rate(new QDoubleSpinBox),
_wait(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.audio.fade.wait"))),
_abortActiveFade(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.audio.fade.abort"))),
_fadeTypeLayout(new QHBoxLayout),
_fadeOptionsLayout(new QVBoxLayout)
{
_audioSources = new QComboBox();
_actions = new QComboBox();
_volumePercent = new QSpinBox();
_volumePercent->setMinimum(0);
_volumePercent->setMaximum(2000);
_volumePercent->setSuffix("%");
_fade = new QCheckBox();
_duration = new DurationSelection(parent, false);
_rate->setMinimum(0.01);
_rate->setMaximum(999.);
_rate->setSuffix("%");
populateActionSelection(_actions);
populateAudioSelection(_audioSources);
populateFadeTypeSelection(_fadeTypes);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
@@ -219,21 +308,41 @@ MacroActionAudioEdit::MacroActionAudioEdit(
SLOT(FadeChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_rate, SIGNAL(valueChanged(double)), this,
SLOT(RateChanged(double)));
QWidget::connect(_wait, SIGNAL(stateChanged(int)), this,
SLOT(WaitChanged(int)));
QWidget::connect(_abortActiveFade, SIGNAL(stateChanged(int)), this,
SLOT(AbortActiveFadeChanged(int)));
QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(FadeTypeChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{audioSources}}", _audioSources}, {"{{actions}}", _actions},
{"{{volume}}", _volumePercent}, {"{{fade}}", _fade},
{"{{audioSources}}", _audioSources},
{"{{actions}}", _actions},
{"{{volume}}", _volumePercent},
{"{{fade}}", _fade},
{"{{duration}}", _duration},
{"{{rate}}", _rate},
{"{{wait}}", _wait},
{"{{abortActiveFade}}", _abortActiveFade},
{"{{fadeTypes}}", _fadeTypes},
};
QHBoxLayout *entryLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
entryLayout, widgetPlaceholders);
_fadeLayout = new QHBoxLayout;
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.fade"),
_fadeLayout, widgetPlaceholders);
_fadeTypeLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.audio.fade.duration"),
_fadeTypeLayout, widgetPlaceholders);
_fadeOptionsLayout->addLayout(_fadeTypeLayout);
_fadeOptionsLayout->addWidget(_abortActiveFade);
_fadeOptionsLayout->addWidget(_wait);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addLayout(_fadeLayout);
mainLayout->addLayout(_fadeOptionsLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -256,7 +365,42 @@ void MacroActionAudioEdit::SetWidgetVisibility()
{
_volumePercent->setVisible(hasVolumeControl(_entryData->_action));
_audioSources->setVisible(hasSourceControl(_entryData->_action));
setLayoutVisible(_fadeLayout, hasVolumeControl(_entryData->_action));
_fadeTypes->setDisabled(!_entryData->_fade);
_wait->setDisabled(!_entryData->_fade);
_abortActiveFade->setDisabled(!_entryData->_fade);
_duration->setDisabled(!_entryData->_fade);
_rate->setDisabled(!_entryData->_fade);
_fadeTypeLayout->removeWidget(_fade);
_fadeTypeLayout->removeWidget(_fadeTypes);
_fadeTypeLayout->removeWidget(_duration);
_fadeTypeLayout->removeWidget(_rate);
clearLayout(_fadeTypeLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{fade}}", _fade},
{"{{duration}}", _duration},
{"{{rate}}", _rate},
{"{{fadeTypes}}", _fadeTypes},
};
if (_entryData->_fadeType == FadeType::DURATION) {
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.action.audio.fade.duration"),
_fadeTypeLayout, widgetPlaceholders);
} else {
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.audio.fade.rate"),
_fadeTypeLayout, widgetPlaceholders);
}
_duration->setVisible(_entryData->_fadeType == FadeType::DURATION);
_rate->setVisible(_entryData->_fadeType == FadeType::RATE);
setLayoutVisible(_fadeTypeLayout,
hasVolumeControl(_entryData->_action));
setLayoutVisible(_fadeOptionsLayout,
hasVolumeControl(_entryData->_action));
adjustSize();
}
@@ -272,6 +416,10 @@ void MacroActionAudioEdit::UpdateEntryData()
_volumePercent->setValue(_entryData->_volume);
_fade->setChecked(_entryData->_fade);
_duration->SetDuration(_entryData->_duration);
_rate->setValue(_entryData->_rate);
_wait->setChecked(_entryData->_wait);
_abortActiveFade->setChecked(_entryData->_abortActiveFade);
_fadeTypes->setCurrentIndex(static_cast<int>(_entryData->_fadeType));
SetWidgetVisibility();
}
@@ -316,6 +464,7 @@ void MacroActionAudioEdit::FadeChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fade = value;
SetWidgetVisibility();
}
void MacroActionAudioEdit::DurationChanged(double seconds)
@@ -327,3 +476,44 @@ void MacroActionAudioEdit::DurationChanged(double seconds)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_duration.seconds = seconds;
}
void MacroActionAudioEdit::RateChanged(double value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_rate = value;
}
void MacroActionAudioEdit::WaitChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_wait = value;
}
void MacroActionAudioEdit::AbortActiveFadeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_abortActiveFade = value;
}
void MacroActionAudioEdit::FadeTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_fadeType = static_cast<FadeType>(value);
SetWidgetVisibility();
}

View File

@@ -54,8 +54,15 @@ std::string MacroActionFactory::GetIdByName(const QString &name)
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : MacroActionFactory::GetActionTypes()) {
list->addItem(obs_module_text(entry.second._name.c_str()));
for (auto &[_, action] : MacroActionFactory::GetActionTypes()) {
QString entry(obs_module_text(action._name.c_str()));
if (list->findText(entry) == -1) {
list->addItem(entry);
} else {
blog(LOG_WARNING,
"did not insert duplicate action entry with name \"%s\"",
entry.toStdString().c_str());
}
}
list->model()->sort(0);
}
@@ -63,13 +70,15 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData,
const std::string &id)
: MacroSegmentEdit(parent), _entryData(entryData)
: MacroSegmentEdit(switcher->macroProperties._highlightActions, parent),
_actionSelection(new QComboBox()),
_entryData(entryData)
{
_actionSelection = new QComboBox();
QWidget::connect(_actionSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ActionSelectionChanged(const QString &)));
QWidget::connect(window(), SIGNAL(HighlightActionsChanged(bool)), this,
SLOT(EnableHighlight(bool)));
populateActionSelection(_actionSelection);
@@ -77,14 +86,14 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
_section->AddHeaderWidget(_headerInfo);
QVBoxLayout *actionLayout = new QVBoxLayout;
actionLayout->setContentsMargins(0, 0, 0, 0);
actionLayout->setSpacing(0);
actionLayout->addWidget(_frame);
_highLightFrameLayout->addWidget(_section);
actionLayout->setContentsMargins({7, 7, 7, 7});
actionLayout->addWidget(_section);
_contentLayout->addLayout(actionLayout);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(actionLayout);
mainLayout->setSpacing(0);
mainLayout->addWidget(_frame);
setLayout(mainLayout);
_entryData = entryData;
@@ -103,11 +112,12 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
auto macro = _entryData->get()->GetMacro();
std::string id = MacroActionFactory::GetIdByName(text);
HeaderInfoChanged("");
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset();
*_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset();
*_entryData = MacroActionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
}
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
@@ -167,12 +177,12 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
obs_data_release(data);
}
macro->UpdateActionIndices();
actionsList->Insert(
idx,
new MacroActionEdit(this, &macro->Actions()[idx], id));
SetActionData(*macro);
}
clearLayout(actionsList->ContentLayout(), idx);
PopulateMacroActions(*macro, idx);
HighlightAction(idx);
SetActionData(*macro);
}
void AdvSceneSwitcher::on_actionAdd_clicked()
@@ -210,15 +220,15 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
{
std::lock_guard<std::mutex> lock(switcher->m);
actionsList->Remove(idx);
macro->Actions().erase(macro->Actions().begin() + idx);
switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_all();
macro->UpdateActionIndices();
SetActionData(*macro);
}
clearLayout(actionsList->ContentLayout(), idx);
PopulateMacroActions(*macro, idx);
SetActionData(*macro);
MacroActionSelectionChanged(-1);
lastInteracted = MacroSection::ACTIONS;
}
void AdvSceneSwitcher::on_actionRemove_clicked()
@@ -264,20 +274,13 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
std::lock_guard<std::mutex> lock(switcher->m);
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
m->UpdateActionIndices();
auto a1 = m->Actions().begin() + pos1;
auto a2 = m->Actions().begin() + pos2;
auto item1 = actionsList->ContentLayout()->takeAt(pos1);
auto item2 = actionsList->ContentLayout()->takeAt(pos2 - 1);
deleteLayoutItem(item1);
deleteLayoutItem(item2);
auto widget1 = new MacroActionEdit(this, &(*a1), (*a1)->GetId());
auto widget2 = new MacroActionEdit(this, &(*a2), (*a2)->GetId());
ConnectControlSignals(widget1);
ConnectControlSignals(widget2);
actionsList->ContentLayout()->insertWidget(pos1, widget1);
actionsList->ContentLayout()->insertWidget(pos2, widget2);
auto widget1 = static_cast<MacroActionEdit *>(
actionsList->ContentLayout()->takeAt(pos1)->widget());
auto widget2 = static_cast<MacroActionEdit *>(
actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
actionsList->Insert(pos1, widget2);
actionsList->Insert(pos2, widget1);
SetActionData(*m);
}
void AdvSceneSwitcher::MoveMacroActionUp(int idx)
@@ -317,14 +320,39 @@ void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
return;
}
SetSelection(actionsList, idx);
SetSelection(conditionsList, -1);
actionsList->SetSelection(idx);
conditionsList->SetSelection(-1);
if (idx < 0 || (unsigned)idx >= macro->Actions().size()) {
currentActionIdx = -1;
} else {
currentActionIdx = idx;
lastInteracted = MacroSection::ACTIONS;
}
currentConditionIdx = -1;
HighlightControls();
}
void AdvSceneSwitcher::MacroActionReorder(int to, int from)
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
if (from < 0 || from > (int)macro->Actions().size() || to < 0 ||
to > (int)macro->Actions().size()) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto action = macro->Actions().at(from);
macro->Actions().erase(macro->Actions().begin() + from);
macro->Actions().insert(macro->Actions().begin() + to, action);
macro->UpdateActionIndices();
actionsList->ContentLayout()->insertItem(
to, actionsList->ContentLayout()->takeAt(from));
SetActionData(*macro);
}
HighlightAction(to);
}

View File

@@ -16,6 +16,7 @@ const static std::map<MediaAction, std::string> actionTypes = {
{MediaAction::RESTART, "AdvSceneSwitcher.action.media.type.restart"},
{MediaAction::NEXT, "AdvSceneSwitcher.action.media.type.next"},
{MediaAction::PREVIOUS, "AdvSceneSwitcher.action.media.type.previous"},
{MediaAction::SEEK, "AdvSceneSwitcher.action.media.type.seek"},
};
bool MacroActionMedia::PerformAction()
@@ -46,6 +47,9 @@ bool MacroActionMedia::PerformAction()
case MediaAction::PREVIOUS:
obs_source_media_previous(source);
break;
case MediaAction::SEEK:
obs_source_media_set_time(source, _seek.seconds * 1000);
break;
default:
break;
}
@@ -72,6 +76,7 @@ bool MacroActionMedia::Save(obs_data_t *obj)
obs_data_set_string(obj, "mediaSource",
GetWeakSourceName(_mediaSource).c_str());
obs_data_set_int(obj, "action", static_cast<int>(_action));
_seek.Save(obj);
return true;
}
@@ -81,6 +86,7 @@ bool MacroActionMedia::Load(obs_data_t *obj)
const char *MediaSourceName = obs_data_get_string(obj, "mediaSource");
_mediaSource = GetWeakSourceByName(MediaSourceName);
_action = static_cast<MediaAction>(obs_data_get_int(obj, "action"));
_seek.Load(obj);
return true;
}
@@ -101,11 +107,11 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionMediaEdit::MacroActionMediaEdit(
QWidget *parent, std::shared_ptr<MacroActionMedia> entryData)
: QWidget(parent)
: QWidget(parent),
_mediaSources(new QComboBox()),
_actions(new QComboBox()),
_seek(new DurationSelection())
{
_mediaSources = new QComboBox();
_actions = new QComboBox();
populateActionSelection(_actions);
populateMediaSelection(_mediaSources);
@@ -114,11 +120,16 @@ MacroActionMediaEdit::MacroActionMediaEdit(
QWidget::connect(_mediaSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(_seek, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_seek, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{mediaSources}}", _mediaSources},
{"{{actions}}", _actions},
{"{{duration}}", _seek},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.media.entry"),
mainLayout, widgetPlaceholders);
@@ -138,6 +149,8 @@ void MacroActionMediaEdit::UpdateEntryData()
_mediaSources->setCurrentText(
GetWeakSourceName(_entryData->_mediaSource).c_str());
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_seek->SetDuration(_entryData->_seek);
SetWidgetVisibility();
}
void MacroActionMediaEdit::SourceChanged(const QString &text)
@@ -160,4 +173,34 @@ void MacroActionMediaEdit::ActionChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<MediaAction>(value);
SetWidgetVisibility();
}
void MacroActionMediaEdit::DurationChanged(double seconds)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_seek.seconds = seconds;
}
void MacroActionMediaEdit::DurationUnitChanged(DurationUnit unit)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_seek.displayUnit = unit;
}
void MacroActionMediaEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_seek->setVisible(_entryData->_action == MediaAction::SEEK);
adjustSize();
}

View File

@@ -1,87 +0,0 @@
#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(), true, true, true);
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

@@ -46,7 +46,7 @@ bool MacroActionRandom::PerformAction()
lastRandomMacro = macros[0];
return macros[0]->PerformActions();
}
srand((unsigned int)time(0));
size_t idx = std::rand() % (macros.size());
lastRandomMacro = macros[idx];
return macros[idx]->PerformActions();

View File

@@ -32,13 +32,8 @@ bool MacroActionReplayBuffer::PerformAction()
}
break;
case ReplayBufferAction::SAVE:
if (obs_frontend_replay_buffer_active() &&
_duration.DurationReached()) {
if (obs_frontend_replay_buffer_active()) {
obs_frontend_replay_buffer_save();
// Default buffer size is 20s so waiting for 10s before
// trying to save again seems reasonable
_duration.seconds = 10;
_duration.Reset();
}
break;
default:
@@ -82,21 +77,25 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionReplayBufferEdit::MacroActionReplayBufferEdit(
QWidget *parent, std::shared_ptr<MacroActionReplayBuffer> entryData)
: QWidget(parent)
: QWidget(parent),
_actions(new QComboBox()),
_saveWarning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.replay.saveWarn")))
{
_actions = new QComboBox();
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QHBoxLayout *mainLayout = new QHBoxLayout;
QHBoxLayout *layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.action.replay.entry"),
mainLayout, widgetPlaceholders);
layout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(layout);
mainLayout->addWidget(_saveWarning);
setLayout(mainLayout);
_entryData = entryData;
@@ -110,6 +109,8 @@ void MacroActionReplayBufferEdit::UpdateEntryData()
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_saveWarning->setVisible(_entryData->_action ==
ReplayBufferAction::SAVE);
}
void MacroActionReplayBufferEdit::ActionChanged(int value)
@@ -120,6 +121,9 @@ void MacroActionReplayBufferEdit::ActionChanged(int value)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<ReplayBufferAction>(value);
_saveWarning->setVisible(_entryData->_action ==
ReplayBufferAction::SAVE);
adjustSize();
}
#endif

View File

@@ -1,46 +0,0 @@
#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

@@ -2,6 +2,8 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
using namespace std::chrono_literals;
const std::string MacroActionSwitchScene::id = "scene_switch";
bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
@@ -9,7 +11,21 @@ bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
"AdvSceneSwitcher.action.switchScene"});
void waitForTransitionChange(int duration)
void waitForTransitionChange(OBSWeakSource &transition)
{
const auto time = 100ms;
obs_source_t *source = obs_weak_source_get_source(transition);
std::unique_lock<std::mutex> lock(switcher->m);
bool stillTransitioning = true;
while (stillTransitioning && !switcher->abortMacroWait) {
switcher->macroTransitionCv.wait_for(lock, time);
float t = obs_transition_get_time(source);
stillTransitioning = t < 1.0f && t > 0.0f;
}
obs_source_release(source);
}
void waitForTransitionChangeFixedDuration(int duration)
{
duration += 200; // It seems to be necessary to add a small buffer
auto time = std::chrono::high_resolution_clock::now() +
@@ -25,7 +41,7 @@ void waitForTransitionChange(int duration)
}
}
int getTransitionOverrideDuration(OBSWeakSource scene)
int getTransitionOverrideDuration(OBSWeakSource &scene)
{
int duration = 0;
obs_source_t *source = obs_weak_source_get_source(scene);
@@ -39,11 +55,41 @@ int getTransitionOverrideDuration(OBSWeakSource scene)
return duration;
}
int getExpectedTransitionDuration(OBSWeakSource scene, double duration)
bool isUsingFixedLengthTransition(OBSWeakSource &transition)
{
int overrideDuration = getTransitionOverrideDuration(scene);
if (overrideDuration && !switcher->transitionOverrideOverride) {
return overrideDuration;
obs_source_t *source = obs_weak_source_get_source(transition);
bool ret = obs_transition_fixed(source);
obs_source_release(source);
return ret;
}
OBSWeakSource getOverrideTransition(OBSWeakSource &scene)
{
OBSWeakSource transition;
obs_source_t *source = obs_weak_source_get_source(scene);
obs_data_t *data = obs_source_get_private_settings(source);
transition = GetWeakTransitionByName(
obs_data_get_string(data, "transition"));
obs_data_release(data);
obs_source_release(source);
return transition;
}
int getExpectedTransitionDuration(OBSWeakSource &scene, OBSWeakSource &t,
double duration)
{
OBSWeakSource transition = t;
if (!switcher->transitionOverrideOverride) {
auto overrideTransition = getOverrideTransition(scene);
if (overrideTransition) {
transition = overrideTransition;
if (!isUsingFixedLengthTransition(transition)) {
return getTransitionOverrideDuration(scene);
}
}
}
if (isUsingFixedLengthTransition(transition)) {
return -1; // no API is available to access the fixed duration
}
if (duration != 0) {
return duration * 1000;
@@ -54,12 +100,19 @@ int getExpectedTransitionDuration(OBSWeakSource scene, double duration)
bool MacroActionSwitchScene::PerformAction()
{
auto scene = _scene.GetScene();
switchScene({scene, _transition.GetTransition(),
(int)(_duration.seconds * 1000)},
auto transition = _transition.GetTransition();
switchScene({scene, transition, (int)(_duration.seconds * 1000)},
obs_frontend_preview_program_mode_active());
if (_blockUntilTransitionDone && scene) {
waitForTransitionChange(getExpectedTransitionDuration(
scene, _duration.seconds));
const int expectedTransitionDuration =
getExpectedTransitionDuration(scene, transition,
_duration.seconds);
if (expectedTransitionDuration < 0) {
waitForTransitionChange(transition);
} else {
waitForTransitionChangeFixedDuration(
expectedTransitionDuration);
}
return !switcher->abortMacroWait;
}
return true;

View File

@@ -14,6 +14,17 @@ const static std::map<StreamAction, std::string> actionTypes = {
{StreamAction::START, "AdvSceneSwitcher.action.streaming.type.start"},
};
constexpr int streamStartCooldown = 5;
std::chrono::high_resolution_clock::time_point MacroActionStream::s_lastAttempt =
std::chrono::high_resolution_clock::now();
bool MacroActionStream::CooldownDurationReached()
{
auto timePassed = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - s_lastAttempt);
return timePassed.count() >= streamStartCooldown;
}
bool MacroActionStream::PerformAction()
{
switch (_action) {
@@ -24,10 +35,10 @@ bool MacroActionStream::PerformAction()
break;
case StreamAction::START:
if (!obs_frontend_streaming_active() &&
_retryCooldown.DurationReached()) {
CooldownDurationReached()) {
obs_frontend_streaming_start();
_retryCooldown.seconds++;
_retryCooldown.Reset();
s_lastAttempt =
std::chrono::high_resolution_clock::now();
}
break;
default:

View File

@@ -0,0 +1,183 @@
#include "headers/macro-action-studio-mode.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
const std::string MacroActionSudioMode::id = "studio_mode";
bool MacroActionSudioMode::_registered = MacroActionFactory::Register(
MacroActionSudioMode::id,
{MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
// TODO: Remove in future version (backwards compatibility)
static const std::string idOld1 = "scene_swap";
static const std::string idOld2 = "preview_scene";
static bool oldRegisterd1 = MacroActionFactory::Register(
idOld1, {MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
static bool oldRegisterd2 = MacroActionFactory::Register(
idOld2, {MacroActionSudioMode::Create, MacroActionSudioModeEdit::Create,
"AdvSceneSwitcher.action.studioMode"});
const static std::map<StudioModeAction, std::string> actionTypes = {
{StudioModeAction::SWAP_SCENE,
"AdvSceneSwitcher.action.studioMode.type.swap"},
{StudioModeAction::SET_SCENE,
"AdvSceneSwitcher.action.studioMode.type.setScene"},
{StudioModeAction::ENABLE_STUDIO_MODE,
"AdvSceneSwitcher.action.studioMode.type.enable"},
{StudioModeAction::DISABLE_STUDIO_MODE,
"AdvSceneSwitcher.action.studioMode.type.disable"},
};
// Calling obs_frontend_set_preview_program_mode() directly from a thread that
// is not the main OBS UI thread will lead to undefined behaviour, so we have
// to use this helper function instead - copied from obs-websocket plugin
static void enableStudioMode(bool enable)
{
if (obs_frontend_preview_program_mode_active() != enable) {
obs_queue_task(
OBS_TASK_UI,
[](void *param) {
auto studioModeEnabled = (bool *)param;
obs_frontend_set_preview_program_mode(
*studioModeEnabled);
},
&enable, true);
}
}
bool MacroActionSudioMode::PerformAction()
{
switch (_action) {
case StudioModeAction::SWAP_SCENE:
obs_frontend_preview_program_trigger_transition();
break;
case StudioModeAction::SET_SCENE: {
auto s = obs_weak_source_get_source(_scene.GetScene());
obs_frontend_set_current_preview_scene(s);
obs_source_release(s);
break;
}
case StudioModeAction::ENABLE_STUDIO_MODE:
enableStudioMode(true);
break;
case StudioModeAction::DISABLE_STUDIO_MODE:
enableStudioMode(false);
break;
default:
break;
}
return true;
}
void MacroActionSudioMode::LogAction()
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO, "performed action \"%s\" with scene \"%s\"",
it->second.c_str(), _scene.ToString().c_str());
} else {
blog(LOG_WARNING, "ignored unknown studio mode action %d",
static_cast<int>(_action));
}
}
bool MacroActionSudioMode::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_scene.Save(obj);
return true;
}
bool MacroActionSudioMode::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_action =
static_cast<StudioModeAction>(obs_data_get_int(obj, "action"));
_scene.Load(obj);
// TODO: Remove in future version (backwards compatibility)
if (obs_data_get_string(obj, "id") == idOld2) {
_action = StudioModeAction::SET_SCENE;
}
return true;
}
std::string MacroActionSudioMode::GetShortDesc()
{
if (_action == StudioModeAction::SET_SCENE) {
return _scene.ToString();
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionSudioModeEdit::MacroActionSudioModeEdit(
QWidget *parent, std::shared_ptr<MacroActionSudioMode> entryData)
: QWidget(parent),
_actions(new QComboBox()),
_scenes(new SceneSelectionWidget(window(), true, true, true))
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
{"{{scenes}}", _scenes},
};
QHBoxLayout *mainLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.action.studioMode.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionSudioModeEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_scenes->SetScene(_entryData->_scene);
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
}
void MacroActionSudioModeEdit::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()));
}
void MacroActionSudioModeEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_action = static_cast<StudioModeAction>(value);
_scenes->setVisible(_entryData->_action == StudioModeAction::SET_SCENE);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}

View File

@@ -9,9 +9,20 @@ bool MacroActionTransition::_registered = MacroActionFactory::Register(
{MacroActionTransition::Create, MacroActionTransitionEdit::Create,
"AdvSceneSwitcher.action.transition"});
bool MacroActionTransition::PerformAction()
const static std::map<MacroActionTransition::Type, std::string> actionTypes = {
{MacroActionTransition::Type::SCENE,
"AdvSceneSwitcher.action.transition.type.scene"},
{MacroActionTransition::Type::SCENE_OVERRIDE,
"AdvSceneSwitcher.action.transition.type.sceneOverride"},
{MacroActionTransition::Type::SOURCE_SHOW,
"AdvSceneSwitcher.action.transition.type.sourceShow"},
{MacroActionTransition::Type::SOURCE_HIDE,
"AdvSceneSwitcher.action.transition.type.sourceHide"},
};
void MacroActionTransition::SetSceneTransition()
{
if (_setType) {
if (_setTransitionType) {
auto t =
obs_weak_source_get_source(_transition.GetTransition());
obs_frontend_set_current_transition(t);
@@ -20,17 +31,101 @@ bool MacroActionTransition::PerformAction()
if (_setDuration) {
obs_frontend_set_transition_duration(_duration.seconds * 1000);
}
}
void MacroActionTransition::SetTransitionOverride()
{
obs_source_t *scene = obs_weak_source_get_source(_scene.GetScene());
obs_data_t *data = obs_source_get_private_settings(scene);
if (_setTransitionType) {
obs_data_set_string(data, "transition",
_transition.ToString().c_str());
}
if (_setDuration) {
obs_data_set_int(data, "transition_duration",
_duration.seconds * 1000);
}
obs_data_release(data);
obs_source_release(scene);
}
void MacroActionTransition::SetSourceTransition(bool show)
{
#ifdef VISIBILITY_TRANSITIONS_SUPPORTED
const auto setTransitionFunc = show ? obs_sceneitem_set_show_transition
: obs_sceneitem_set_hide_transition;
const auto setDurationFunc =
show ? obs_sceneitem_set_show_transition_duration
: obs_sceneitem_set_hide_transition_duration;
obs_source_t *transition =
obs_weak_source_get_source(_transition.GetTransition());
const auto items = _source.GetSceneItems(_scene);
for (auto &item : items) {
if (_setTransitionType) {
setTransitionFunc(item, transition);
}
if (_setDuration) {
setDurationFunc(item, _duration.seconds * 1000);
}
obs_sceneitem_release(item);
}
obs_source_release(transition);
#else
blog(LOG_WARNING, "Setting hide / show transition not supported!");
#endif
}
bool MacroActionTransition::PerformAction()
{
switch (_type) {
case Type::SCENE:
SetSceneTransition();
break;
case Type::SCENE_OVERRIDE:
SetTransitionOverride();
break;
case Type::SOURCE_SHOW:
SetSourceTransition(true);
break;
case Type::SOURCE_HIDE:
SetSourceTransition(false);
break;
}
return true;
}
void MacroActionTransition::LogAction()
{
std::string msgBegin;
switch (_type) {
case Type::SCENE:
msgBegin += "set scene transition";
break;
case Type::SCENE_OVERRIDE:
msgBegin +=
"set scene override transition of " + _scene.ToString();
break;
case Type::SOURCE_SHOW:
msgBegin += "set source show transition of " +
_source.ToString() + " on scene " +
_scene.ToString();
break;
case Type::SOURCE_HIDE:
msgBegin += "set source hide transition of " +
_source.ToString() + " on scene " +
_scene.ToString();
break;
}
if (_setDuration) {
vblog(LOG_INFO, "set transition duration to %s",
vblog(LOG_INFO, "%s duration to %s", msgBegin.c_str(),
_duration.ToString().c_str());
}
if (_setType) {
vblog(LOG_INFO, "set transition type to \"%s\"",
if (_setTransitionType) {
vblog(LOG_INFO, "%s type to \"%s\"", msgBegin.c_str(),
_transition.ToString().c_str());
}
}
@@ -38,64 +133,111 @@ void MacroActionTransition::LogAction()
bool MacroActionTransition::Save(obs_data_t *obj)
{
MacroAction::Save(obj);
obs_data_set_int(obj, "actionType", static_cast<int>(_type));
_source.Save(obj);
_scene.Save(obj);
_duration.Save(obj);
_transition.Save(obj);
obs_data_set_bool(obj, "setDuration", _setDuration);
obs_data_set_bool(obj, "setType", _setType);
obs_data_set_bool(obj, "setType", _setTransitionType);
return true;
}
bool MacroActionTransition::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "actionType"));
_source.Load(obj);
_scene.Load(obj);
_duration.Load(obj);
_transition.Load(obj);
_setDuration = obs_data_get_bool(obj, "setDuration");
_setType = obs_data_get_bool(obj, "setType");
_setTransitionType = obs_data_get_bool(obj, "setType");
return true;
}
std::string MacroActionTransition::GetShortDesc()
{
return _transition.ToString();
std::string msgBegin;
switch (_type) {
case Type::SCENE:
return _transition.ToString();
case Type::SCENE_OVERRIDE:
return _scene.ToString() + " - " + _transition.ToString();
case Type::SOURCE_SHOW:
return _scene.ToString() + " - " + _source.ToString() + " - " +
_transition.ToString();
case Type::SOURCE_HIDE:
return _scene.ToString() + " - " + _source.ToString() + " - " +
_transition.ToString();
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionTransitionEdit::MacroActionTransitionEdit(
QWidget *parent, std::shared_ptr<MacroActionTransition> entryData)
: QWidget(parent)
: QWidget(parent),
_actions(new QComboBox),
_sources(new SceneItemSelectionWidget(parent, false)),
_scenes(new SceneSelectionWidget(this, false, false, true)),
_setTransition(new QCheckBox),
_setDuration(new QCheckBox),
_transitions(new TransitionSelectionWidget(this, false)),
_duration(new DurationSelection(this, false)),
_transitionLayout(new QHBoxLayout),
_durationLayout(new QHBoxLayout)
{
_transitions = new TransitionSelectionWidget(this, false);
_duration = new DurationSelection(this, false);
_setType = new QCheckBox();
_setDuration = new QCheckBox();
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
_sources, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
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(_setTransition, SIGNAL(stateChanged(int)), this,
SLOT(SetTransitionChanged(int)));
QWidget::connect(_setDuration, SIGNAL(stateChanged(int)), this,
SLOT(SetDurationChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{transitions}}", _transitions},
{"{{duration}}", _duration},
{"{{setType}}", _setType},
{"{{setTransition}}", _setTransition},
{"{{setDuration}}", _setDuration},
};
_typeLayout = new QHBoxLayout;
QHBoxLayout *typeLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line1"),
_typeLayout, widgetPlaceholders);
_durationLayout = new QHBoxLayout;
typeLayout, widgetPlaceholders);
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line2"),
_transitionLayout, widgetPlaceholders);
placeWidgets(obs_module_text(
"AdvSceneSwitcher.action.transition.entry.line3"),
_durationLayout, widgetPlaceholders);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_typeLayout);
mainLayout->addLayout(typeLayout);
mainLayout->addLayout(_transitionLayout);
mainLayout->addLayout(_durationLayout);
setLayout(mainLayout);
@@ -110,12 +252,53 @@ void MacroActionTransitionEdit::UpdateEntryData()
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem((_entryData->_source));
_setDuration->setChecked(_entryData->_setDuration);
_duration->SetDuration(_entryData->_duration);
_setType->setChecked(_entryData->_setType);
_setTransition->setChecked(_entryData->_setTransitionType);
_transitions->SetTransition(_entryData->_transition);
_transitions->setEnabled(_entryData->_setType);
_transitions->setEnabled(_entryData->_setTransitionType);
_duration->setEnabled(_entryData->_setDuration);
SetWidgetVisibility();
}
void MacroActionTransitionEdit::SourceChanged(const SceneItemSelection &item)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionTransitionEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_type = static_cast<MacroActionTransition::Type>(value);
SetWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionTransitionEdit::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()));
}
void MacroActionTransitionEdit::TransitionChanged(const TransitionSelection &t)
@@ -140,14 +323,24 @@ void MacroActionTransitionEdit::DurationChanged(double seconds)
_entryData->_duration.seconds = seconds;
}
void MacroActionTransitionEdit::SetTypeChanged(int state)
void MacroActionTransitionEdit::SetWidgetVisibility()
{
_sources->setVisible(
_entryData->_type == MacroActionTransition::Type::SOURCE_HIDE ||
_entryData->_type == MacroActionTransition::Type::SOURCE_SHOW);
_scenes->setVisible(_entryData->_type !=
MacroActionTransition::Type::SCENE);
adjustSize();
}
void MacroActionTransitionEdit::SetTransitionChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_setType = state;
_entryData->_setTransitionType = state;
_transitions->setEnabled(state);
if (state) {
emit HeaderInfoChanged(

View File

@@ -21,9 +21,9 @@ static std::default_random_engine re(rd());
bool MacroActionWait::PerformAction()
{
double sleep_duration;
double sleepDuration;
if (_waitType == WaitType::FIXED) {
sleep_duration = _duration.seconds;
sleepDuration = _duration.seconds;
} else {
double min = (_duration.seconds < _duration2.seconds)
? _duration.seconds
@@ -32,17 +32,23 @@ bool MacroActionWait::PerformAction()
? _duration2.seconds
: _duration.seconds;
std::uniform_real_distribution<double> unif(min, max);
sleep_duration = unif(re);
sleepDuration = unif(re);
}
vblog(LOG_INFO, "perform action wait with duration of %f",
sleep_duration);
sleepDuration);
std::unique_lock<std::mutex> lock(switcher->m);
auto time = std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds((int)(sleepDuration * 1000));
auto macro = GetMacro();
switcher->abortMacroWait = false;
switcher->macroWaitCv.wait_for(
lock,
std::chrono::milliseconds((long long)(sleep_duration * 1000)),
[] { return switcher->abortMacroWait.load(); });
std::unique_lock<std::mutex> lock(switcher->m);
while (!switcher->abortMacroWait && !macro->GetStop()) {
if (switcher->macroWaitCv.wait_until(lock, time) ==
std::cv_status::timeout) {
break;
}
}
return !switcher->abortMacroWait;
}

32
src/macro-action.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "headers/macro-action.hpp"
#include "headers/advanced-scene-switcher.hpp"
bool MacroAction::Save(obs_data_t *obj)
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
return true;
}
bool MacroAction::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
return true;
}
void MacroAction::LogAction()
{
vblog(LOG_INFO, "performed action %s", GetId().c_str());
}
void MacroRefAction::ResolveMacroRef()
{
_macro.UpdateRef();
}
void MultiMacroRefAction::ResolveMacroRef()
{
for (auto &m : _macros) {
m.UpdateRef();
}
}

View File

@@ -212,6 +212,10 @@ void MacroConditionAudio::SetVolumeLevel(void *data, const float *,
const float *)
{
MacroConditionAudio *c = static_cast<MacroConditionAudio *>(data);
const auto macro = c->GetMacro();
if (macro && macro->Paused()) {
return;
}
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > c->_peak) {

View File

@@ -64,17 +64,18 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionCursorEdit::MacroConditionCursorEdit(
QWidget *parent, std::shared_ptr<MacroConditionCursor> entryData)
: QWidget(parent)
: QWidget(parent),
_minX(new QSpinBox()),
_minY(new QSpinBox()),
_maxX(new QSpinBox()),
_maxY(new QSpinBox()),
_conditions(new QComboBox()),
_frameToggle(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.cursor.showFrame"))),
_xPos(new QLabel("-")),
_yPos(new QLabel("-"))
{
_conditions = new QComboBox();
_minX = new QSpinBox();
_minY = new QSpinBox();
_maxX = new QSpinBox();
_maxY = new QSpinBox();
_frameToggle = new QPushButton(
obs_module_text("AdvSceneSwitcher.condition.cursor.showFrame"));
_xPos = new QLabel("-");
_yPos = new QLabel("-");
_frame.setAttribute(Qt::WA_TransparentForMouseEvents);
populateConditionSelection(_conditions);

View File

@@ -20,6 +20,12 @@ static std::map<DateCondition, std::string> dateConditionTypes = {
"AdvSceneSwitcher.condition.date.state.between"},
};
static std::map<DateCondition, std::string> weekDateConditionTypes = {
{DateCondition::AT, "AdvSceneSwitcher.condition.date.state.at"},
{DateCondition::AFTER, "AdvSceneSwitcher.condition.date.state.after"},
{DateCondition::BEFORE, "AdvSceneSwitcher.condition.date.state.before"},
};
static std::map<DayOfWeekSelection, std::string> dayOfWeekNames = {
{DayOfWeekSelection::ANY, "AdvSceneSwitcher.condition.date.anyDay"},
{DayOfWeekSelection::MONDAY, "AdvSceneSwitcher.condition.date.monday"},
@@ -35,19 +41,33 @@ static std::map<DayOfWeekSelection, std::string> dayOfWeekNames = {
{DayOfWeekSelection::SUNDAY, "AdvSceneSwitcher.condition.date.sunday"},
};
bool MacroConditionDate::checkDayOfWeek()
bool MacroConditionDate::CheckDayOfWeek(int64_t msSinceLastCheck)
{
QDateTime cur = QDateTime::currentDateTime();
if (_dayOfWeek != DayOfWeekSelection::ANY &&
cur.date().dayOfWeek() != static_cast<int>(_dayOfWeek)) {
return false;
}
if (_ignoreTime) {
return true;
}
_dateTime.setDate(cur.date());
return _dateTime >= cur &&
_dateTime <= cur.addMSecs(switcher->interval);
switch (_condition) {
case DateCondition::AT:
return _dateTime <= cur &&
_dateTime >= cur.addMSecs(-msSinceLastCheck);
case DateCondition::AFTER:
return cur >= _dateTime;
case DateCondition::BEFORE:
return cur <= _dateTime;
default:
break;
}
return false;
}
bool MacroConditionDate::checkRegularDate()
bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
{
bool match = false;
QDateTime cur = QDateTime::currentDateTime();
@@ -62,8 +82,8 @@ bool MacroConditionDate::checkRegularDate()
switch (_condition) {
case DateCondition::AT:
match = _dateTime >= cur &&
_dateTime <= cur.addMSecs(switcher->interval);
match = _dateTime <= cur &&
_dateTime >= cur.addMSecs(-msSinceLastCheck);
break;
case DateCondition::AFTER:
match = cur >= _dateTime;
@@ -92,10 +112,15 @@ bool MacroConditionDate::checkRegularDate()
bool MacroConditionDate::CheckCondition()
{
if (_dayOfWeekCheck) {
return checkDayOfWeek();
auto m = GetMacro();
if (!m) {
return false;
}
return checkRegularDate();
auto msSinceLastCheck = m->MsSinceLastCheck();
if (_dayOfWeekCheck) {
return CheckDayOfWeek(msSinceLastCheck);
}
return CheckRegularDate(msSinceLastCheck);
}
bool MacroConditionDate::Save(obs_data_t *obj)
@@ -103,13 +128,16 @@ bool MacroConditionDate::Save(obs_data_t *obj)
MacroCondition::Save(obj);
obs_data_set_int(obj, "dayOfWeek", static_cast<int>(_dayOfWeek));
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
const auto &dateToSave = _updateOnRepeat ? _dateTime : _origDateTime;
const auto &dateToSave2 = _updateOnRepeat ? _dateTime2 : _origDateTime2;
obs_data_set_string(obj, "dateTime",
_dateTime.toString().toStdString().c_str());
dateToSave.toString().toStdString().c_str());
obs_data_set_string(obj, "dateTime2",
_dateTime2.toString().toStdString().c_str());
dateToSave2.toString().toStdString().c_str());
obs_data_set_bool(obj, "ignoreDate", _ignoreDate);
obs_data_set_bool(obj, "ignoreTime", _ignoreTime);
obs_data_set_bool(obj, "repeat", _repeat);
obs_data_set_bool(obj, "updateOnRepeat", _updateOnRepeat);
_duration.Save(obj);
obs_data_set_bool(obj, "dayOfWeekCheck", _dayOfWeekCheck);
return true;
@@ -124,11 +152,19 @@ bool MacroConditionDate::Load(obs_data_t *obj)
static_cast<DateCondition>(obs_data_get_int(obj, "condition"));
_dateTime = QDateTime::fromString(
QString::fromStdString(obs_data_get_string(obj, "dateTime")));
_origDateTime = _dateTime;
_dateTime2 = QDateTime::fromString(
QString::fromStdString(obs_data_get_string(obj, "dateTime2")));
_origDateTime2 = _dateTime2;
_ignoreDate = obs_data_get_bool(obj, "ignoreDate");
_ignoreTime = obs_data_get_bool(obj, "ignoreTime");
_repeat = obs_data_get_bool(obj, "repeat");
// TODO: Remove this fallback in future version
if (!obs_data_has_user_value(obj, "updateOnRepeat")) {
_updateOnRepeat = true;
} else {
_updateOnRepeat = obs_data_get_bool(obj, "updateOnRepeat");
}
_duration.Load(obj);
// TODO: Remove this fallback in future version
@@ -137,6 +173,11 @@ bool MacroConditionDate::Load(obs_data_t *obj)
} else {
_dayOfWeekCheck = obs_data_get_bool(obj, "dayOfWeekCheck");
}
// The following code is used to avoid issues with old save files in
// which the simple date check did not support setting _condition
if (_dayOfWeekCheck && _condition == DateCondition::BETWEEN) {
_condition = DateCondition::AT;
}
return true;
}
@@ -148,20 +189,62 @@ std::string MacroConditionDate::GetShortDesc()
return "";
}
std::string dayName = obs_module_text(it->second.c_str());
if (_ignoreTime) {
return dayName;
}
return dayName + " " +
_dateTime.time().toString().toStdString();
} else {
if (!_ignoreDate && !_ignoreTime) {
return _dateTime.toString().toStdString();
return GetDateTime1().toString().toStdString();
} else if (_ignoreDate && !_ignoreTime) {
return _dateTime.time().toString().toStdString();
return GetDateTime1().time().toString().toStdString();
} else if (!_ignoreDate && _ignoreTime) {
return _dateTime.date().toString().toStdString();
return GetDateTime1().date().toString().toStdString();
}
}
return "";
}
void MacroConditionDate::SetDate1(const QDate &date)
{
_dateTime.setDate(date);
_origDateTime.setDate(date);
}
void MacroConditionDate::SetDate2(const QDate &date)
{
_dateTime2.setDate(date);
_origDateTime2.setDate(date);
}
void MacroConditionDate::SetTime1(const QTime &time)
{
_dateTime.setTime(time);
_origDateTime.setTime(time);
}
void MacroConditionDate::SetTime2(const QTime &time)
{
_dateTime2.setTime(time);
_origDateTime2.setTime(time);
}
QDateTime MacroConditionDate::GetDateTime1()
{
return _updateOnRepeat ? _dateTime : _origDateTime;
}
QDateTime MacroConditionDate::GetDateTime2()
{
return _updateOnRepeat ? _dateTime2 : _origDateTime2;
}
QDateTime MacroConditionDate::GetNextMatchDateTime()
{
return _dateTime;
}
static inline void populateDaySelection(QComboBox *list)
{
for (auto entry : dayOfWeekNames) {
@@ -176,45 +259,64 @@ static inline void populateConditionSelection(QComboBox *list)
}
}
static inline void populateWeekConditionSelection(QComboBox *list)
{
for (auto entry : weekDateConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroConditionDateEdit::MacroConditionDateEdit(
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
: QWidget(parent)
: QWidget(parent),
_weekCondition(new QComboBox()),
_dayOfWeek(new QComboBox()),
_ignoreWeekTime(new QCheckBox()),
_weekTime(new QTimeEdit()),
_condition(new QComboBox()),
_date(new QDateEdit()),
_time(new QTimeEdit()),
_separator(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.date.separator"))),
_date2(new QDateEdit()),
_time2(new QTimeEdit()),
_ignoreDate(new QCheckBox()),
_ignoreTime(new QCheckBox()),
_repeat(new QCheckBox()),
_nextMatchDate(new QLabel()),
_updateOnRepeat(new QCheckBox()),
_duration(new DurationSelection()),
_advancedSettingsTooggle(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.date.showAdvancedSettings"))),
_simpleLayout(new QHBoxLayout()),
_advancedLayout(new QHBoxLayout()),
_repeatLayout(new QVBoxLayout()),
_repeatUpdateLayout(new QHBoxLayout())
{
_dayOfWeek = new QComboBox();
_weekTime = new QTimeEdit();
_ignoreWeekTime->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
_weekTime->setDisplayFormat("hh:mm:ss");
_condition = new QComboBox();
_date = new QDateEdit();
_date->setDisplayFormat("yyyy.MM.dd ");
_date->setCalendarPopup(true);
auto cal = _date->calendarWidget();
cal->showSelectedDate();
_time = new QTimeEdit();
_time->setDisplayFormat("hh:mm:ss");
_separator = new QLabel(
obs_module_text("AdvSceneSwitcher.condition.date.separator"));
_date2 = new QDateEdit();
_date2->setDisplayFormat("yyyy.MM.dd ");
_date2->setCalendarPopup(true);
cal = _date2->calendarWidget();
cal->showSelectedDate();
_time2 = new QTimeEdit();
_time2->setDisplayFormat("hh:mm:ss");
_ignoreDate = new QCheckBox();
_ignoreDate->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.date.ignoreDate"));
_ignoreTime = new QCheckBox();
_ignoreTime->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
_repeat = new QCheckBox();
_duration = new DurationSelection();
_advancedSettingsTooggle = new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.date.showAdvancedSettings"));
QWidget::connect(_weekCondition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_dayOfWeek, SIGNAL(currentIndexChanged(int)), this,
SLOT(DayOfWeekChanged(int)));
QWidget::connect(_ignoreWeekTime, SIGNAL(stateChanged(int)), this,
SLOT(IgnoreTimeChanged(int)));
QWidget::connect(_weekTime, SIGNAL(timeChanged(const QTime &)), this,
SLOT(TimeChanged(const QTime &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
@@ -233,6 +335,8 @@ MacroConditionDateEdit::MacroConditionDateEdit(
SLOT(IgnoreTimeChanged(int)));
QWidget::connect(_repeat, SIGNAL(stateChanged(int)), this,
SLOT(RepeatChanged(int)));
QWidget::connect(_updateOnRepeat, SIGNAL(stateChanged(int)), this,
SLOT(UpdateOnRepeatChanged(int)));
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
SLOT(DurationChanged(double)));
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
@@ -242,9 +346,12 @@ MacroConditionDateEdit::MacroConditionDateEdit(
populateDaySelection(_dayOfWeek);
populateConditionSelection(_condition);
populateWeekConditionSelection(_weekCondition);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{weekCondition}}", _weekCondition},
{"{{dayOfWeek}}", _dayOfWeek},
{"{{ignoreWeekTime}}", _ignoreWeekTime},
{"{{weekTime}}", _weekTime},
{"{{condition}}", _condition},
{"{{date}}", _date},
@@ -255,20 +362,26 @@ MacroConditionDateEdit::MacroConditionDateEdit(
{"{{ignoreDate}}", _ignoreDate},
{"{{ignoreTime}}", _ignoreTime},
{"{{repeat}}", _repeat},
{"{{updateOnRepeat}}", _updateOnRepeat},
{"{{duration}}", _duration},
};
_simpleLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.date.entry.simple"),
_simpleLayout, widgetPlaceholders);
_advancedLayout = new QHBoxLayout;
placeWidgets(obs_module_text(
"AdvSceneSwitcher.condition.date.entry.advanced"),
_advancedLayout, widgetPlaceholders);
_repeatLayout = new QHBoxLayout;
auto repeatLayout = new QHBoxLayout;
placeWidgets(
obs_module_text("AdvSceneSwitcher.condition.date.entry.repeat"),
_repeatLayout, widgetPlaceholders);
repeatLayout, widgetPlaceholders);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.date.entry.updateOnRepeat"),
_repeatUpdateLayout, widgetPlaceholders);
_repeatLayout->addLayout(repeatLayout);
_repeatLayout->addWidget(_nextMatchDate);
_repeatLayout->addLayout(_repeatUpdateLayout);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(_simpleLayout);
@@ -280,6 +393,11 @@ MacroConditionDateEdit::MacroConditionDateEdit(
mainLayout->addLayout(_advancedToggleLayout);
setLayout(mainLayout);
_timer.setInterval(3000);
QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(ShowNextMatch()));
_timer.start();
_entryData = entryData;
UpdateEntryData();
_loading = false;
@@ -316,7 +434,7 @@ void MacroConditionDateEdit::DateChanged(const QDate &date)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dateTime.setDate(date);
_entryData->SetDate1(date);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -329,7 +447,7 @@ void MacroConditionDateEdit::TimeChanged(const QTime &time)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dateTime.setTime(time);
_entryData->SetTime1(time);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
@@ -342,7 +460,7 @@ void MacroConditionDateEdit::Date2Changed(const QDate &date)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dateTime2.setDate(date);
_entryData->SetDate2(date);
}
void MacroConditionDateEdit::Time2Changed(const QTime &time)
@@ -352,7 +470,7 @@ void MacroConditionDateEdit::Time2Changed(const QTime &time)
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dateTime2.setTime(time);
_entryData->SetTime2(time);
}
void MacroConditionDateEdit::IgnoreDateChanged(int state)
@@ -386,6 +504,17 @@ void MacroConditionDateEdit::RepeatChanged(int state)
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_repeat = state;
_duration->setDisabled(!state);
SetWidgetStatus();
}
void MacroConditionDateEdit::UpdateOnRepeatChanged(int state)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_updateOnRepeat = state;
}
void MacroConditionDateEdit::DurationChanged(double seconds)
@@ -413,33 +542,53 @@ void MacroConditionDateEdit::AdvancedSettingsToggleClicked()
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
_entryData->_condition = DateCondition::AT;
}
_condition->setCurrentIndex(0);
_weekCondition->setCurrentIndex(0);
SetWidgetStatus();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionDateEdit::ShowNextMatch()
{
if (!_entryData || _entryData->_dayOfWeekCheck ||
!_entryData->_repeat) {
return;
}
QString format(obs_module_text(
"AdvSceneSwitcher.condition.date.entry.nextMatchDate"));
_nextMatchDate->setText(
format.arg(_entryData->GetNextMatchDateTime().toString()));
}
void MacroConditionDateEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_weekCondition->setCurrentIndex(
static_cast<int>(_entryData->_condition));
_dayOfWeek->setCurrentIndex(
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
_weekTime->setTime(_entryData->_dateTime.time());
_ignoreWeekTime->setChecked(!_entryData->_ignoreTime);
_weekTime->setDateTime(_entryData->GetDateTime1());
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_date->setDate(_entryData->_dateTime.date());
_time->setTime(_entryData->_dateTime.time());
_date2->setDate(_entryData->_dateTime2.date());
_time2->setTime(_entryData->_dateTime2.time());
_date->setDateTime(_entryData->GetDateTime1());
_time->setDateTime(_entryData->GetDateTime1());
_date2->setDateTime(_entryData->GetDateTime2());
_time2->setDateTime(_entryData->GetDateTime2());
_ignoreDate->setChecked(!_entryData->_ignoreDate);
_ignoreTime->setChecked(!_entryData->_ignoreTime);
_repeat->setChecked(_entryData->_repeat);
_updateOnRepeat->setChecked(_entryData->_updateOnRepeat);
_duration->SetDuration(_entryData->_duration);
_duration->setDisabled(!_entryData->_repeat);
ShowNextMatch();
SetWidgetStatus();
}
@@ -452,9 +601,15 @@ void MacroConditionDateEdit::SetWidgetStatus()
setLayoutVisible(_simpleLayout, _entryData->_dayOfWeekCheck);
setLayoutVisible(_advancedLayout, !_entryData->_dayOfWeekCheck);
setLayoutVisible(_repeatLayout, !_entryData->_dayOfWeekCheck);
_nextMatchDate->setVisible(!_entryData->_dayOfWeekCheck &&
_entryData->_repeat);
setLayoutVisible(_repeatUpdateLayout,
!_entryData->_dayOfWeekCheck && _entryData->_repeat);
if (_entryData->_dayOfWeekCheck) {
_advancedSettingsTooggle->setText(obs_module_text(
"AdvSceneSwitcher.condition.date.showAdvancedSettings"));
_weekTime->setDisabled(_entryData->_ignoreTime);
_weekCondition->setDisabled(_entryData->_ignoreTime);
} else {
_advancedSettingsTooggle->setText(obs_module_text(
"AdvSceneSwitcher.condition.date.showSimpleSettings"));
@@ -466,10 +621,8 @@ void MacroConditionDateEdit::SetWidgetStatus()
DateCondition::BETWEEN);
}
const QSignalBlocker b1(_weekTime);
const QSignalBlocker b2(_weekTime);
_weekTime->setTime(_entryData->_dateTime.time());
_weekTime->setTime(_entryData->_dateTime.time());
const QSignalBlocker b(_weekTime);
_weekTime->setTime(_entryData->GetDateTime1().time());
adjustSize();
}

View File

@@ -53,10 +53,10 @@ std::string MacroConditionFactory::GetIdByName(const QString &name)
return "";
}
bool MacroConditionFactory::UsesDurationConstraint(const std::string &id)
bool MacroConditionFactory::UsesDurationModifier(const std::string &id)
{
if (auto it = _methods.find(id); it != _methods.end()) {
return it->second._useDurationConstraint;
return it->second._useDurationModifier;
}
return false;
}
@@ -83,21 +83,109 @@ static inline void populateLogicSelection(QComboBox *list, bool root = false)
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : MacroConditionFactory::GetConditionTypes()) {
list->addItem(obs_module_text(entry.second._name.c_str()));
for (auto &[_, condition] :
MacroConditionFactory::GetConditionTypes()) {
QString entry(obs_module_text(condition._name.c_str()));
if (list->findText(entry) == -1) {
list->addItem(entry);
} else {
blog(LOG_WARNING,
"did not insert duplicate condition entry with name \"%s\"",
entry.toStdString().c_str());
}
}
list->model()->sort(0);
}
static void populateDurationModifierTypes(QComboBox *list)
{
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.none"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.more"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.equal"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.less"));
list->addItem(
obs_module_text("AdvSceneSwitcher.duration.condition.within"));
}
DurationModifierEdit::DurationModifierEdit(QWidget *parent)
{
_condition = new QComboBox(parent);
_duration = new DurationSelection(parent);
_toggle = new QPushButton(parent);
_toggle->setMaximumSize(22, 22);
_toggle->setIcon(
QIcon(QString::fromStdString(getDataFilePath("res/time.svg"))));
populateDurationModifierTypes(_condition);
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(_ModifierChanged(int)));
QObject::connect(_duration, &DurationSelection::DurationChanged, this,
&DurationModifierEdit::DurationChanged);
QObject::connect(_duration, &DurationSelection::UnitChanged, this,
&DurationModifierEdit::UnitChanged);
QWidget::connect(_toggle, SIGNAL(clicked()), this,
SLOT(ToggleClicked()));
QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(11);
layout->addWidget(_toggle);
layout->addWidget(_condition);
layout->addWidget(_duration);
setLayout(layout);
Collapse(true);
}
void DurationModifierEdit::SetValue(DurationModifier &value)
{
_duration->SetDuration(value.GetDuration());
_condition->setCurrentIndex(static_cast<int>(value.GetType()));
_duration->setVisible(value.GetType() != DurationModifier::Type::NONE);
}
void DurationModifierEdit::SetUnit(DurationUnit u)
{
_duration->SetUnit(u);
}
void DurationModifierEdit::SetDuration(const Duration &d)
{
_duration->SetDuration(d);
}
void DurationModifierEdit::_ModifierChanged(int value)
{
auto m = static_cast<DurationModifier::Type>(value);
Collapse(m == DurationModifier::Type::NONE);
emit ModifierChanged(m);
}
void DurationModifierEdit::ToggleClicked()
{
Collapse(false);
}
void DurationModifierEdit::Collapse(bool collapse)
{
_toggle->setVisible(collapse);
_duration->setVisible(!collapse);
_condition->setVisible(!collapse);
}
MacroConditionEdit::MacroConditionEdit(
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
const std::string &id, bool root)
: MacroSegmentEdit(parent), _entryData(entryData), _isRoot(root)
: MacroSegmentEdit(switcher->macroProperties._highlightConditions,
parent),
_logicSelection(new QComboBox()),
_conditionSelection(new QComboBox()),
_dur(new DurationModifierEdit()),
_entryData(entryData),
_isRoot(root)
{
_logicSelection = new QComboBox();
_conditionSelection = new QComboBox();
_dur = new DurationConstraintEdit();
QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)),
this, SLOT(LogicSelectionChanged(int)));
QWidget::connect(_conditionSelection,
@@ -107,9 +195,11 @@ MacroConditionEdit::MacroConditionEdit(
SLOT(DurationChanged(double)));
QWidget::connect(_dur, SIGNAL(UnitChanged(DurationUnit)), this,
SLOT(DurationUnitChanged(DurationUnit)));
QWidget::connect(_dur, SIGNAL(ConditionChanged(DurationCondition)),
QWidget::connect(_dur, SIGNAL(ModifierChanged(DurationModifier::Type)),
this,
SLOT(DurationConditionChanged(DurationCondition)));
SLOT(DurationModifierChanged(DurationModifier::Type)));
QWidget::connect(window(), SIGNAL(HighlightConditionsChanged(bool)),
this, SLOT(EnableHighlight(bool)));
populateLogicSelection(_logicSelection, root);
populateConditionSelection(_conditionSelection);
@@ -120,14 +210,14 @@ MacroConditionEdit::MacroConditionEdit(
_section->AddHeaderWidget(_dur);
QVBoxLayout *conditionLayout = new QVBoxLayout;
conditionLayout->setContentsMargins(0, 0, 0, 0);
conditionLayout->setSpacing(0);
conditionLayout->addWidget(_frame);
_highLightFrameLayout->addWidget(_section);
conditionLayout->setContentsMargins({7, 7, 7, 7});
conditionLayout->addWidget(_section);
_contentLayout->addLayout(conditionLayout);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(conditionLayout);
mainLayout->setSpacing(0);
mainLayout->addWidget(_frame);
setLayout(mainLayout);
UpdateEntryData(id);
@@ -156,11 +246,24 @@ bool MacroConditionEdit::IsRootNode()
return _isRoot;
}
void MacroConditionEdit::SetLogicSelection()
{
auto logic = (*_entryData)->GetLogicType();
if (IsRootNode()) {
_logicSelection->setCurrentIndex(static_cast<int>(logic));
} else {
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
logic_root_offset);
}
}
void MacroConditionEdit::SetRootNode(bool root)
{
_isRoot = root;
const QSignalBlocker blocker(_logicSelection);
_logicSelection->clear();
populateLogicSelection(_logicSelection, root);
SetLogicSelection();
}
void MacroConditionEdit::UpdateEntryData(const std::string &id)
@@ -173,18 +276,12 @@ void MacroConditionEdit::UpdateEntryData(const std::string &id)
this, SLOT(HeaderInfoChanged(const QString &)));
HeaderInfoChanged(
QString::fromStdString((*_entryData)->GetShortDesc()));
auto logic = (*_entryData)->GetLogicType();
if (IsRootNode()) {
_logicSelection->setCurrentIndex(static_cast<int>(logic));
} else {
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
logic_root_offset);
}
SetLogicSelection();
_section->SetContent(widget, (*_entryData)->GetCollapsed());
_dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id));
auto constraint = (*_entryData)->GetDurationConstraint();
_dur->SetValue(constraint);
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
auto modifier = (*_entryData)->GetDurationModifier();
_dur->SetValue(modifier);
SetFocusPolicyOfWidgets();
}
@@ -203,22 +300,23 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
auto macro = _entryData->get()->GetMacro();
std::string id = MacroConditionFactory::GetIdByName(text);
auto temp = DurationConstraint();
auto temp = DurationModifier();
_dur->SetValue(temp);
HeaderInfoChanged("");
std::lock_guard<std::mutex> lock(switcher->m);
auto logic = (*_entryData)->GetLogicType();
_entryData->reset();
*_entryData = MacroConditionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic);
{
std::lock_guard<std::mutex> lock(switcher->m);
auto logic = (*_entryData)->GetLogicType();
_entryData->reset();
*_entryData = MacroConditionFactory::Create(id, macro);
(*_entryData)->SetIndex(idx);
(*_entryData)->SetLogicType(logic);
}
auto widget =
MacroConditionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
_section->SetContent(widget);
_dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id));
_dur->setVisible(MacroConditionFactory::UsesDurationModifier(id));
SetFocusPolicyOfWidgets();
}
@@ -232,14 +330,14 @@ void MacroConditionEdit::DurationChanged(double seconds)
(*_entryData)->SetDuration(seconds);
}
void MacroConditionEdit::DurationConditionChanged(DurationCondition cond)
void MacroConditionEdit::DurationModifierChanged(DurationModifier::Type m)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
(*_entryData)->SetDurationCondition(cond);
(*_entryData)->SetDurationModifier(m);
}
void MacroConditionEdit::DurationUnitChanged(DurationUnit unit)
@@ -295,12 +393,13 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
}
(*cond)->SetLogicType(logic);
macro->UpdateConditionIndices();
conditionsList->Insert(
idx,
new MacroConditionEdit(this, &macro->Conditions()[idx],
id, idx == 0));
SetConditionData(*macro);
}
clearLayout(conditionsList->ContentLayout(), idx);
PopulateMacroConditions(*macro, idx);
HighlightCondition(idx);
SetConditionData(*macro);
}
void AdvSceneSwitcher::on_conditionAdd_clicked()
@@ -338,18 +437,17 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
{
std::lock_guard<std::mutex> lock(switcher->m);
conditionsList->Remove(idx);
macro->Conditions().erase(macro->Conditions().begin() + idx);
macro->UpdateConditionIndices();
if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(LogicType::ROOT_NONE);
}
SetConditionData(*macro);
}
clearLayout(conditionsList->ContentLayout(), idx);
PopulateMacroConditions(*macro, idx);
SetConditionData(*macro);
MacroConditionSelectionChanged(-1);
lastInteracted = MacroSection::CONDITIONS;
}
void AdvSceneSwitcher::on_conditionRemove_clicked()
@@ -408,18 +506,15 @@ void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
(*c2)->SetLogicType(logic1);
}
auto item1 = conditionsList->ContentLayout()->takeAt(pos1);
auto item2 = conditionsList->ContentLayout()->takeAt(pos2 - 1);
deleteLayoutItem(item1);
deleteLayoutItem(item2);
auto widget1 =
new MacroConditionEdit(this, &(*c1), (*c1)->GetId(), root);
auto widget2 =
new MacroConditionEdit(this, &(*c2), (*c2)->GetId(), false);
ConnectControlSignals(widget1);
ConnectControlSignals(widget2);
conditionsList->ContentLayout()->insertWidget(pos1, widget1);
conditionsList->ContentLayout()->insertWidget(pos2, widget2);
auto widget1 = static_cast<MacroConditionEdit *>(
conditionsList->ContentLayout()->takeAt(pos1)->widget());
auto widget2 = static_cast<MacroConditionEdit *>(
conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
conditionsList->Insert(pos1, widget2);
conditionsList->Insert(pos2, widget1);
SetConditionData(*m);
widget2->SetRootNode(root);
widget1->SetRootNode(false);
}
void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
@@ -459,14 +554,61 @@ void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
return;
}
SetSelection(conditionsList, idx);
SetSelection(actionsList, -1);
conditionsList->SetSelection(idx);
actionsList->SetSelection(-1);
if (idx < 0 || (unsigned)idx >= macro->Conditions().size()) {
currentConditionIdx = -1;
} else {
currentConditionIdx = idx;
lastInteracted = MacroSection::CONDITIONS;
}
currentActionIdx = -1;
HighlightControls();
}
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
if (from < 0 || from > (int)macro->Conditions().size() || to < 0 ||
to > (int)macro->Conditions().size()) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto condition = macro->Conditions().at(from);
if (to == 0) {
condition->SetLogicType(LogicType::ROOT_NONE);
static_cast<MacroConditionEdit *>(
conditionsList->WidgetAt(from))
->SetRootNode(true);
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
static_cast<MacroConditionEdit *>(
conditionsList->WidgetAt(0))
->SetRootNode(false);
}
if (from == 0) {
condition->SetLogicType(LogicType::AND);
static_cast<MacroConditionEdit *>(
conditionsList->WidgetAt(from))
->SetRootNode(false);
macro->Conditions().at(1)->SetLogicType(
LogicType::ROOT_NONE);
static_cast<MacroConditionEdit *>(
conditionsList->WidgetAt(1))
->SetRootNode(true);
}
macro->Conditions().erase(macro->Conditions().begin() + from);
macro->Conditions().insert(macro->Conditions().begin() + to,
condition);
macro->UpdateConditionIndices();
conditionsList->ContentLayout()->insertItem(
to, conditionsList->ContentLayout()->takeAt(from));
SetConditionData(*macro);
}
HighlightCondition(to);
}

View File

@@ -40,8 +40,8 @@ static std::map<MediaState, std::string> mediaStates = {
"AdvSceneSwitcher.mediaTab.states.ended"},
{MediaState::OBS_MEDIA_STATE_ERROR,
"AdvSceneSwitcher.mediaTab.states.error"},
{MediaState::PLAYED_TO_END,
"AdvSceneSwitcher.mediaTab.states.playedToEnd"},
{MediaState::PLAYLIST_ENDED,
"AdvSceneSwitcher.mediaTab.states.playlistEnd"},
{MediaState::ANY, "AdvSceneSwitcher.mediaTab.states.any"},
};
@@ -51,111 +51,113 @@ MacroConditionMedia::~MacroConditionMedia()
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
obs_source_release(mediasource);
}
bool matchTime(const int64_t currentTime, const int64_t duration,
const MediaTimeRestriction restriction, const int64_t time)
bool MacroConditionMedia::CheckTime()
{
bool matchedTimeNone =
(restriction == MediaTimeRestriction::TIME_RESTRICTION_NONE);
bool matchedTimeLonger =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_LONGER) &&
(currentTime > time);
bool matchedTimeShorter =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_SHORTER) &&
(currentTime < time);
bool matchedTimeRemainLonger =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER) &&
(duration > currentTime && duration - currentTime > time);
bool matchedTimeRemainShorter =
(restriction ==
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER) &&
(duration > currentTime && duration - currentTime < time);
obs_source_t *s = obs_weak_source_get_source(_source);
auto duration = obs_source_media_get_duration(s);
auto currentTime = obs_source_media_get_time(s);
obs_source_release(s);
return matchedTimeNone || matchedTimeLonger || matchedTimeShorter ||
matchedTimeRemainLonger || matchedTimeRemainShorter;
bool match = false;
switch (_restriction) {
case MediaTimeRestriction::TIME_RESTRICTION_NONE:
match = true;
break;
case MediaTimeRestriction::TIME_RESTRICTION_SHORTER:
match = currentTime < _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_LONGER:
match = currentTime > _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER:
match = duration > currentTime &&
duration - currentTime < _time.seconds * 1000;
break;
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER:
match = duration > currentTime &&
duration - currentTime > _time.seconds * 1000;
break;
default:
break;
}
return match;
}
bool MacroConditionMedia::CheckState()
{
obs_source_t *s = obs_weak_source_get_source(_source);
obs_media_state currentState = obs_source_media_get_state(s);
obs_source_release(s);
bool match = false;
// To be able to compare to obs_media_state more easily
int expectedState = static_cast<int>(_state);
switch (_state) {
case MediaState::OBS_MEDIA_STATE_STOPPED:
match = _stopped || currentState == OBS_MEDIA_STATE_STOPPED;
break;
case MediaState::OBS_MEDIA_STATE_ENDED:
match = _ended || currentState == OBS_MEDIA_STATE_ENDED;
break;
case MediaState::PLAYLIST_ENDED:
match = CheckPlaylistEnd(currentState);
break;
case MediaState::ANY:
match = true;
break;
case MediaState::OBS_MEDIA_STATE_NONE:
case MediaState::OBS_MEDIA_STATE_PLAYING:
case MediaState::OBS_MEDIA_STATE_OPENING:
case MediaState::OBS_MEDIA_STATE_BUFFERING:
case MediaState::OBS_MEDIA_STATE_PAUSED:
case MediaState::OBS_MEDIA_STATE_ERROR:
match = currentState == expectedState;
break;
default:
break;
}
return match;
}
bool MacroConditionMedia::CheckPlaylistEnd(const obs_media_state currentState)
{
bool consecutiveEndedStates = false;
if (_next || currentState != OBS_MEDIA_STATE_ENDED) {
_previousStateEnded = false;
}
if (currentState == OBS_MEDIA_STATE_ENDED && _previousStateEnded) {
consecutiveEndedStates = true;
}
_previousStateEnded = _ended || currentState == OBS_MEDIA_STATE_ENDED;
return consecutiveEndedStates;
}
bool MacroConditionMedia::CheckMediaMatch()
{
if (!_source) {
return false;
}
bool match = false;
obs_source_t *s = obs_weak_source_get_source(_source);
auto duration = obs_source_media_get_duration(s);
auto time = obs_source_media_get_time(s);
obs_media_state currentState = obs_source_media_get_state(s);
obs_source_release(s);
// To be able to compare to obs_media_state more easily
int expectedState = static_cast<int>(_state);
bool matchedStopped = expectedState == OBS_MEDIA_STATE_STOPPED &&
_stopped;
// The signal for the state ended is intentionally not used here
// so matchedEnded can be used to specify the end of a VLC playlist
// by two consequtive matches of OBS_MEDIA_STATE_ENDED
//
// This was done to reduce the likelyhood of interpreting a single
// OBS_MEDIA_STATE_ENDED caught by obs_source_media_get_state()
// as the end of the playlist of the VLC source, while actually being
// in the middle of switching to the next item of the playlist
//
// If there is a separate obs_media_sate in the future for the
// "end of playlist reached" signal the line below can be used
// and an additional check for this new singal can be introduced
//
// bool matchedEnded = _state == OBS_MEDIA_STATE_ENDED && _ended;
bool ended = false;
if (currentState == OBS_MEDIA_STATE_ENDED) {
ended = _previousStateEnded;
_previousStateEnded = true;
} else {
_previousStateEnded = false;
}
bool matchedEnded = ended && (expectedState == OBS_MEDIA_STATE_ENDED);
// match if playedToEnd was true in last interval
// and playback is currently ended
bool matchedPlayedToEnd = _state == MediaState::PLAYED_TO_END &&
_playedToEnd && ended;
// interval * 2 to make sure not to miss any state changes
// which happened during check of the conditions
_playedToEnd = _playedToEnd ||
(duration - time <= switcher->interval * 2);
// reset for next check
if (ended) {
_playedToEnd = false;
}
_stopped = false;
_ended = false;
bool matchedState =
(currentState == expectedState || _state == MediaState::ANY) ||
matchedStopped || matchedEnded || matchedPlayedToEnd;
bool matchedTime =
matchTime(time, duration, _restriction, _time.seconds * 1000);
bool matched = matchedState && matchedTime;
bool matched = CheckState() && CheckTime();
if (matched && !(_onlyMatchonChagne && _alreadyMatched)) {
match = true;
}
_alreadyMatched = matched;
// reset for next check
_stopped = false;
_ended = false;
_next = false;
return match;
}
@@ -195,6 +197,7 @@ bool MacroConditionMedia::Save(obs_data_t *obj)
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
_time.Save(obj);
obs_data_set_bool(obj, "matchOnChagne", _onlyMatchonChagne);
obs_data_set_int(obj, "version", 0);
return true;
}
@@ -250,11 +253,7 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
_restriction = static_cast<MediaTimeRestriction>(
obs_data_get_int(obj, "restriction"));
_time.Load(obj);
if (!obs_data_has_user_value(obj, "matchOnChagne")) {
_onlyMatchonChagne = true;
} else {
_onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne");
}
_onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne");
if (_sourceType == MediaSourceType::SOURCE) {
obs_source_t *mediasource = obs_weak_source_get_source(_source);
@@ -262,11 +261,17 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
obs_source_get_signal_handler(mediasource);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
signal_handler_connect(sh, "media_next", MediaNext, this);
obs_source_release(mediasource);
}
forMediaSourceOnSceneAddMediaCondition(_scene.GetScene(), this,
_sources);
if (!obs_data_has_user_value(obj, "version")) {
if (_state == MediaState::OBS_MEDIA_STATE_ENDED) {
_state = MediaState::PLAYLIST_ENDED;
}
}
return true;
}
@@ -304,6 +309,7 @@ void MacroConditionMedia::ClearSignalHandler()
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
obs_source_release(mediasource);
}
@@ -313,23 +319,43 @@ void MacroConditionMedia::ResetSignalHandler()
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
signal_handler_disconnect(sh, "media_next", MediaNext, this);
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
signal_handler_connect(sh, "media_ended", MediaEnded, this);
signal_handler_connect(sh, "media_next", MediaNext, this);
obs_source_release(mediasource);
}
void MacroConditionMedia::MediaStopped(void *data, calldata_t *)
{
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
const auto macro = media->GetMacro();
if (macro && macro->Paused()) {
return;
}
media->_stopped = true;
}
void MacroConditionMedia::MediaEnded(void *data, calldata_t *)
{
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
const auto macro = media->GetMacro();
if (macro && macro->Paused()) {
return;
}
media->_ended = true;
}
void MacroConditionMedia::MediaNext(void *data, calldata_t *)
{
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
const auto macro = media->GetMacro();
if (macro && macro->Paused()) {
return;
}
media->_next = true;
}
static void populateMediaTimeRestrictions(QComboBox *list)
{
for (auto entry : mediaTimeRestrictions) {
@@ -356,15 +382,18 @@ static void addAnyAndAllStates(QComboBox *list)
MacroConditionMediaEdit::MacroConditionMediaEdit(
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window())),
_mediaSources(new QComboBox()),
_states(new QComboBox()),
_timeRestrictions(new QComboBox()),
_time(new DurationSelection()),
_onChange(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.media.matchOnChange")))
{
_mediaSources = new QComboBox();
_scenes = new SceneSelectionWidget(window());
_states = new QComboBox();
_timeRestrictions = new QComboBox();
_time = new DurationSelection();
_onChange = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.media.matchOnChange"));
_states->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.media.inconsistencyInfo"));
QWidget::connect(_mediaSources,
SIGNAL(currentTextChanged(const QString &)), this,
@@ -548,6 +577,9 @@ void MacroConditionMediaEdit::OnChangeChanged(int value)
void MacroConditionMediaEdit::SetWidgetVisibility()
{
_scenes->setVisible(_entryData->_sourceType != MediaSourceType::SOURCE);
if (!_onChange->isChecked()) {
_onChange->hide();
}
}
int getIdxFromMediaState(MediaState state)

View File

@@ -57,11 +57,6 @@ MacroConditionStats::~MacroConditionStats()
os_cpu_usage_info_destroy(_cpu_info);
}
bool doubleEquals(double left, double right, double epsilon)
{
return (fabs(left - right) < epsilon);
}
bool MacroConditionStats::CheckFPS()
{
switch (_condition) {

View File

@@ -28,19 +28,20 @@ bool MacroConditionScene::CheckCondition()
switch (_type) {
case SceneType::CURRENT:
if (!_useTransitionTargetScene) {
return switcher->currentScene == _scene.GetScene(false);
} else {
bool match = false;
if (_useTransitionTargetScene) {
auto current = obs_frontend_get_current_scene();
auto weak = obs_source_get_weak_source(current);
match = weak == _scene.GetScene(false);
bool match = weak == _scene.GetScene(false);
obs_weak_source_release(weak);
obs_source_release(current);
return match;
}
return switcher->currentScene == _scene.GetScene(false);
case SceneType::PREVIOUS:
if (switcher->anySceneTransitionStarted() &&
_useTransitionTargetScene) {
return switcher->currentScene == _scene.GetScene(false);
}
return switcher->previousScene == _scene.GetScene(false);
case SceneType::CHANGED:
return sceneChanged;
@@ -168,8 +169,18 @@ void MacroConditionSceneEdit::SetWidgetVisibility()
{
_scenes->setVisible(_entryData->_type == SceneType::CURRENT ||
_entryData->_type == SceneType::PREVIOUS);
_useTransitionTargetScene->setVisible(_entryData->_type ==
SceneType::CURRENT);
_useTransitionTargetScene->setVisible(
_entryData->_type == SceneType::CURRENT ||
_entryData->_type == SceneType::PREVIOUS);
if (_entryData->_type == SceneType::PREVIOUS) {
_useTransitionTargetScene->setText(obs_module_text(
"AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour"));
}
if (_entryData->_type == SceneType::CURRENT) {
_useTransitionTargetScene->setText(obs_module_text(
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
}
adjustSize();
}

View File

@@ -36,20 +36,6 @@ bool isCurrentTransition(OBSWeakSource &t)
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 isTargetScene(OBSWeakSource &target)
{
auto source = obs_frontend_get_current_scene();
@@ -64,7 +50,7 @@ bool MacroConditionTransition::CheckCondition()
{
bool anyTransitionEnded = _lastTransitionEndTime !=
switcher->lastTransitionEndTime;
bool anyTransitionStarted = anySceneTransitionStarted();
bool anyTransitionStarted = switcher->anySceneTransitionStarted();
bool transitionStarted = false;
bool transitionEnded = false;

141
src/macro-condition.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "headers/macro-condition.hpp"
#include "headers/advanced-scene-switcher.hpp"
const std::map<LogicType, LogicTypeInfo> MacroCondition::logicTypes = {
{LogicType::NONE, {"AdvSceneSwitcher.logic.none"}},
{LogicType::AND, {"AdvSceneSwitcher.logic.and"}},
{LogicType::OR, {"AdvSceneSwitcher.logic.or"}},
{LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
void DurationModifier::Save(obs_data_t *obj, const char *condName,
const char *secondsName, const char *unitName)
{
obs_data_set_int(obj, condName, static_cast<int>(_type));
_dur.Save(obj, secondsName, unitName);
}
void DurationModifier::Load(obs_data_t *obj, const char *condName,
const char *secondsName, const char *unitName)
{
// For backwards compatability check if duration value exist without
// time constraint condition - if so assume DurationCondition::MORE
if (!obs_data_has_user_value(obj, condName) &&
obs_data_has_user_value(obj, secondsName)) {
obs_data_set_int(obj, condName, static_cast<int>(Type::MORE));
}
_type = static_cast<Type>(obs_data_get_int(obj, condName));
_dur.Load(obj, secondsName, unitName);
}
bool DurationModifier::DurationReached()
{
switch (_type) {
case DurationModifier::Type::NONE:
return true;
break;
case DurationModifier::Type::MORE:
return _dur.DurationReached();
break;
case DurationModifier::Type::EQUAL:
if (_dur.DurationReached() && !_timeReached) {
_timeReached = true;
return true;
}
break;
case DurationModifier::Type::LESS:
return !_dur.DurationReached();
break;
case DurationModifier::Type::WITHIN:
if (_dur.IsReset()) {
return false;
}
return !_dur.DurationReached();
break;
default:
break;
}
return false;
}
void DurationModifier::Reset()
{
_timeReached = false;
_dur.Reset();
}
bool MacroCondition::Save(obs_data_t *obj)
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_int(obj, "logic", static_cast<int>(_logic));
_duration.Save(obj);
return true;
}
bool MacroCondition::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
_logic = static_cast<LogicType>(obs_data_get_int(obj, "logic"));
_duration.Load(obj);
return true;
}
void MacroCondition::ResetDuration()
{
_duration.Reset();
}
void MacroCondition::CheckDurationModifier(bool &val)
{
if (_duration.GetType() != DurationModifier::Type::WITHIN && !val) {
_duration.Reset();
}
if (_duration.GetType() == DurationModifier::Type::WITHIN && val) {
_duration.Reset();
}
switch (_duration.GetType()) {
case DurationModifier::Type::NONE:
case DurationModifier::Type::MORE:
case DurationModifier::Type::EQUAL:
case DurationModifier::Type::LESS:
if (!val) {
_duration.Reset();
}
val = val && _duration.DurationReached();
return;
case DurationModifier::Type::WITHIN:
if (val) {
_duration.SetTimeRemaining(
_duration.GetDuration().seconds);
}
val = val || _duration.DurationReached();
break;
default:
break;
}
}
void MacroCondition::SetDurationModifier(DurationModifier::Type m)
{
_duration.SetModifier(m);
}
void MacroCondition::SetDurationUnit(DurationUnit u)
{
_duration.SetUnit(u);
}
void MacroCondition::SetDuration(double seconds)
{
_duration.SetValue(seconds);
}
void MacroRefCondition::ResolveMacroRef()
{
_macro.UpdateRef();
}

View File

@@ -1,15 +1,16 @@
#include "headers/macro-list-entry-widget.hpp"
#include "headers/macro.hpp"
#include "headers/utility.hpp"
MacroListEntryWidget::MacroListEntryWidget(std::shared_ptr<Macro> macro,
QWidget *parent)
: QWidget(parent), _macro(macro)
bool highlight, QWidget *parent)
: QWidget(parent),
_name(new QLabel(QString::fromStdString(macro->Name()))),
_running(new QCheckBox),
_macro(macro),
_highlightExecutedMacros(highlight)
{
_name = new QLabel(QString::fromStdString(macro->Name()));
_running = new QCheckBox();
_running->setChecked(!macro->Paused());
connect(_running, SIGNAL(stateChanged(int)), this,
SLOT(PauseChanged(int)));
setStyleSheet("\
QCheckBox { background-color: rgba(0,0,0,0); }\
@@ -21,6 +22,15 @@ MacroListEntryWidget::MacroListEntryWidget(std::shared_ptr<Macro> macro,
layout->addWidget(_name);
layout->addStretch();
setLayout(layout);
connect(_running, SIGNAL(stateChanged(int)), this,
SLOT(PauseChanged(int)));
connect(window(), SIGNAL(HighlightMacrosChanged(bool)), this,
SLOT(EnableHighlight(bool)));
_timer.setInterval(1500);
connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightExecuted()));
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
_timer.start();
}
void MacroListEntryWidget::PauseChanged(int state)
@@ -37,3 +47,25 @@ void MacroListEntryWidget::SetMacro(std::shared_ptr<Macro> &m)
{
_macro = m;
}
void MacroListEntryWidget::EnableHighlight(bool value)
{
_highlightExecutedMacros = value;
}
void MacroListEntryWidget::HighlightExecuted()
{
if (!_highlightExecutedMacros) {
return;
}
if (_macro && _macro->WasExecutedRecently()) {
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
}
}
void MacroListEntryWidget::UpdatePaused()
{
const QSignalBlocker b(_running);
_running->setChecked(!_macro->Paused());
}

92
src/macro-properties.cpp Normal file
View File

@@ -0,0 +1,92 @@
#include "headers/macro-properties.hpp"
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <obs-module.h>
void MacroProperties::Save(obs_data_t *obj)
{
auto data = obs_data_create();
obs_data_set_bool(data, "highlightExecuted", _highlightExecuted);
obs_data_set_bool(data, "highlightConditions", _highlightConditions);
obs_data_set_bool(data, "highlightActions", _highlightActions);
obs_data_set_obj(obj, "macroProperties", data);
obs_data_release(data);
}
void MacroProperties::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "macroProperties");
// TODO: Remove in future version
if (obs_data_has_user_value(obj, "highlightExecutedMacros")) {
_highlightExecuted =
obs_data_get_bool(obj, "highlightExecutedMacros");
} else {
_highlightExecuted =
obs_data_get_bool(data, "highlightExecuted");
}
_highlightConditions = obs_data_get_bool(data, "highlightConditions");
_highlightActions = obs_data_get_bool(data, "highlightActions");
obs_data_release(data);
}
MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
const MacroProperties &prop,
Macro *macro)
: QDialog(parent),
_executed(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.highlightExecutedMacros"))),
_conditions(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.highlightTrueConditions"))),
_actions(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.highlightPerformedActions"))),
_hotkeys(new QCheckBox(
obs_module_text("AdvSceneSwitcher.macroTab.disableHotkeys")))
{
setModal(true);
setWindowModality(Qt::WindowModality::WindowModal);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedWidth(555);
setMinimumHeight(100);
_executed->setChecked(prop._highlightExecuted);
_conditions->setChecked(prop._highlightConditions);
_actions->setChecked(prop._highlightActions);
if (macro) {
_hotkeys->setChecked(macro->PauseHotkeysEnabled());
} else {
_hotkeys->hide();
}
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_executed);
layout->addWidget(_conditions);
layout->addWidget(_actions);
layout->addWidget(_hotkeys);
setLayout(layout);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttonbox);
buttonbox->setCenterButtons(true);
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
MacroProperties &userInput,
Macro *macro)
{
MacroPropertiesDialog dialog(parent, userInput, macro);
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
userInput._highlightExecuted = dialog._executed->isChecked();
userInput._highlightConditions = dialog._conditions->isChecked();
userInput._highlightActions = dialog._actions->isChecked();
if (macro) {
macro->EnablePauseHotkeys(dialog._hotkeys->isChecked());
}
return true;
}

42
src/macro-ref.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "headers/macro-ref.hpp"
#include "headers/macro.hpp"
MacroRef::MacroRef(std::string name) : _name(name)
{
UpdateRef();
}
void MacroRef::UpdateRef()
{
_ref = GetMacroByName(_name.c_str());
}
void MacroRef::UpdateRef(std::string newName)
{
_name = newName;
UpdateRef();
}
void MacroRef::UpdateRef(QString newName)
{
_name = newName.toStdString();
UpdateRef();
}
void MacroRef::Save(obs_data_t *obj)
{
if (_ref) {
obs_data_set_string(obj, "macro", _ref->Name().c_str());
}
}
void MacroRef::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "macro");
UpdateRef();
}
Macro *MacroRef::get()
{
return _ref;
}
Macro *MacroRef::operator->()
{
return _ref;
}

View File

@@ -1,29 +1,67 @@
#include "headers/macro-segment-list.hpp"
#include "headers/utility.hpp"
#include <QGridLayout>
#include <QSpacerItem>
#include <QEvent>
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QScrollBar>
MacroSegmentList::MacroSegmentList(QWidget *parent) : QScrollArea(parent)
MacroSegmentList::MacroSegmentList(QWidget *parent)
: QScrollArea(parent),
_layout(new QVBoxLayout),
_contentLayout(new QVBoxLayout),
_helpMsg(new QLabel)
{
_contentLayout = new QVBoxLayout;
_helpMsg = new QLabel;
_helpMsg->setWordWrap(true);
_helpMsg->setAlignment(Qt::AlignCenter);
_contentLayout->setSpacing(0);
auto helperLayout = new QGridLayout();
helperLayout->addWidget(_helpMsg, 0, 0,
Qt::AlignHCenter | Qt::AlignVCenter);
helperLayout->addLayout(_contentLayout, 0, 0);
helperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
auto layout = new QVBoxLayout;
layout->addLayout(helperLayout, 10);
layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
_layout->addLayout(helperLayout, 10);
_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
auto wrapper = new QWidget;
wrapper->setLayout(layout);
wrapper->setLayout(_layout);
setWidget(wrapper);
setWidgetResizable(true);
setAcceptDrops(true);
}
MacroSegmentList::~MacroSegmentList()
{
if (_autoScrollThread.joinable()) {
_autoScroll = false;
_autoScrollThread.join();
}
}
int MacroSegmentList::GetDragIndex(const QPoint &pos)
{
for (int idx = 0; idx < _contentLayout->count(); ++idx) {
auto item = _contentLayout->itemAt(idx);
if (!item) {
continue;
}
const auto geo = item->geometry();
int scrollOffset = 0;
if (verticalScrollBar()) {
scrollOffset = verticalScrollBar()->value();
}
const QRect rect(
mapToGlobal(QPoint(geo.topLeft().x(),
geo.topLeft().y() - scrollOffset)),
geo.size());
if (rect.contains(pos)) {
return idx;
}
}
return -1;
}
void MacroSegmentList::SetHelpMsg(const QString &msg)
@@ -36,9 +74,348 @@ void MacroSegmentList::SetHelpMsgVisible(bool visible)
_helpMsg->setVisible(visible);
}
void MacroSegmentList::Insert(int idx, QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->insertWidget(idx, widget);
}
void MacroSegmentList::Add(QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->addWidget(widget);
}
void MacroSegmentList::Remove(int idx)
{
deleteLayoutItemWidget(_contentLayout->takeAt(idx));
}
void MacroSegmentList::Clear(int idx)
{
clearLayout(_contentLayout, idx);
}
void MacroSegmentList::Highlight(int idx)
{
auto item = _contentLayout->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
}
void MacroSegmentList::SetCollapsed(bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < _contentLayout->count(); i++) {
item = _contentLayout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void MacroSegmentList::SetSelection(int idx)
{
for (int i = 0; i < _contentLayout->count(); ++i) {
auto widget = static_cast<MacroSegmentEdit *>(
_contentLayout->itemAt(i)->widget());
if (widget) {
widget->SetSelected(i == idx);
}
}
}
bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
}
switch (event->type()) {
case QEvent::MouseButtonPress:
mousePressEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseButtonRelease:
mouseReleaseEvent(static_cast<QMouseEvent *>(event));
break;
default:
break;
}
return QWidget::eventFilter(object, event);
}
void MacroSegmentList::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit SelectionChagned(-1);
_dragPosition = GetDragIndex(event->globalPos());
emit SelectionChagned(_dragPosition);
} else {
_dragPosition = -1;
}
}
void MacroSegmentList::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton && _dragPosition != -1) {
auto item = _contentLayout->itemAt(_dragPosition);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
QDrag *drag = new QDrag(widget);
auto img = widget->grab();
auto mimedata = new QMimeData();
mimedata->setImageData(img);
drag->setMimeData(mimedata);
drag->setPixmap(img);
drag->setHotSpot(event->pos());
_autoScroll = true;
_autoScrollThread =
std::thread(&MacroSegmentList::CheckScroll, this);
drag->exec();
_autoScroll = false;
_autoScrollThread.join();
}
}
void MacroSegmentList::mouseReleaseEvent(QMouseEvent *)
{
_dragPosition = -1;
}
void MacroSegmentList::dragLeaveEvent(QDragLeaveEvent *)
{
HideLastDropLine();
}
void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData() && event->mimeData()->hasImage()) {
event->accept();
} else {
event->ignore();
}
}
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx)
{
if (idx < 0 || idx >= _contentLayout->count()) {
return nullptr;
}
auto item = _contentLayout->itemAt(idx);
if (!item) {
return nullptr;
}
return static_cast<MacroSegmentEdit *>(item->widget());
}
void MacroSegmentList::HideLastDropLine()
{
if (_dropLineIdx >= 0 && _dropLineIdx < _contentLayout->count()) {
auto widget = WidgetAt(_dropLineIdx);
if (widget) {
widget->ShowDropLine(
MacroSegmentEdit::DropLineState::NONE);
}
}
_dropLineIdx = -1;
}
bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
{
return QRect(rect.topLeft(),
QSize(rect.size().width(), rect.size().height() / 2))
.contains(pos);
}
bool widgetIsInLayout(QWidget *w, QLayout *l)
{
if (w == nullptr) {
return false;
}
for (int i = 0; i < l->count(); ++i) {
auto item = l->itemAt(i);
if (!item) {
continue;
}
if (item->widget() == w) {
return true;
}
}
return false;
}
void MacroSegmentList::dragMoveEvent(QDragMoveEvent *event)
{
auto widget = qobject_cast<QWidget *>(event->source());
if (!widgetIsInLayout(widget, _contentLayout)) {
return;
}
_dragCursorPos = (mapToGlobal(event->pos()));
CheckDropLine(_dragCursorPos);
}
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
{
auto item = _contentLayout->itemAt(idx);
if (!item) {
return {};
}
int scrollOffset = 0;
if (verticalScrollBar()) {
scrollOffset = verticalScrollBar()->value();
}
const QRect itemRect = item->geometry().marginsAdded(
_contentLayout->contentsMargins());
const QRect rect(
mapToGlobal(QPoint(itemRect.topLeft().x(),
itemRect.topLeft().y() -
_contentLayout->spacing() -
scrollOffset)),
QSize(itemRect.size().width(),
itemRect.size().height() + _contentLayout->spacing()));
return rect;
}
int MacroSegmentList::GetWidgetIdx(const QPoint &pos)
{
int idx = -1;
for (int i = 0; i < _contentLayout->count(); ++i) {
if (GetContentItemRectWithPadding(i).contains(pos)) {
idx = i;
break;
}
}
return idx;
}
void MacroSegmentList::CheckScroll()
{
while (_autoScroll) {
const int scrollTrigger = 15;
const int scrollAmount = 1;
const QRect rect(mapToGlobal(QPoint(0, 0)), size());
const QRect upperScrollTrigger(
QPoint(rect.topLeft().x(),
rect.topLeft().y() - scrollTrigger),
QSize(rect.width(), scrollTrigger * 2));
if (upperScrollTrigger.contains(_dragCursorPos)) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() - scrollAmount);
}
const QRect lowerScrollTrigger(
QPoint(rect.bottomLeft().x(),
rect.bottomLeft().y() - scrollTrigger),
QSize(rect.width(), scrollTrigger * 2));
if (lowerScrollTrigger.contains(_dragCursorPos)) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() + scrollAmount);
}
std::this_thread::sleep_for(std::chrono::microseconds(50));
}
}
void MacroSegmentList::CheckDropLine(const QPoint &pos)
{
int idx = GetWidgetIdx(pos);
if (idx == _dragPosition) {
return;
}
auto action = MacroSegmentEdit::DropLineState::ABOVE;
if (idx == -1) {
if (IsInListArea(pos)) {
idx = _contentLayout->count() - 1;
action = MacroSegmentEdit::DropLineState::BELOW;
} else {
HideLastDropLine();
return;
}
} else {
auto rect = GetContentItemRectWithPadding(idx);
if (idx == _contentLayout->count() - 1 &&
!isInUpperHalfOf(pos, rect)) {
action = MacroSegmentEdit::DropLineState::BELOW;
} else {
if (!isInUpperHalfOf(pos, rect)) {
idx++;
}
}
}
if (idx == _dragPosition ||
(idx - 1 == _dragPosition &&
action != MacroSegmentEdit::DropLineState::BELOW)) {
HideLastDropLine();
return;
}
auto widget = WidgetAt(idx);
if (!widget) {
HideLastDropLine();
return;
}
widget->ShowDropLine(action);
if (_dropLineIdx != idx) {
HideLastDropLine();
_dropLineIdx = idx;
}
}
bool MacroSegmentList::IsInListArea(const QPoint &pos)
{
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
_layout->contentsRect().size());
return layoutRect.contains(pos);
}
int MacroSegmentList::GetDropIndex(const QPoint &pos)
{
int idx = GetWidgetIdx(pos);
if (idx == _dragPosition) {
return -1;
}
if (idx == -1) {
if (IsInListArea(pos)) {
return _contentLayout->count() - 1;
}
return -1;
}
auto rect = GetContentItemRectWithPadding(idx);
if (idx == _contentLayout->count() - 1 && !isInUpperHalfOf(pos, rect)) {
return idx;
} else if (!isInUpperHalfOf(pos, rect)) {
idx++;
}
if (_dragPosition < idx) {
idx--;
}
if (idx == _dragPosition) {
return -1;
}
return idx;
}
void MacroSegmentList::dropEvent(QDropEvent *event)
{
HideLastDropLine();
auto widget = qobject_cast<QWidget *>(event->source());
if (widget && !widget->geometry().contains(event->pos()) &&
widgetIsInLayout(widget, _contentLayout)) {
int dropPosition = GetDropIndex(mapToGlobal(event->pos()));
if (dropPosition == -1) {
return;
}
emit Reorder(dropPosition, _dragPosition);
}
_dragPosition = -1;
}

View File

@@ -1,5 +1,6 @@
#include "headers/macro-segment.hpp"
#include "headers/section.hpp"
#include "headers/utility.hpp"
#include <obs.hpp>
#include <QEvent>
@@ -24,6 +25,20 @@ std::string MacroSegment::GetShortDesc()
return "";
}
void MacroSegment::SetHighlight()
{
_highlight = true;
}
bool MacroSegment::Highlight()
{
if (_highlight) {
_highlight = false;
return true;
}
return false;
}
MouseWheelWidgetAdjustmentGuard::MouseWheelWidgetAdjustmentGuard(QObject *parent)
: QObject(parent)
{
@@ -40,17 +55,48 @@ bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
return QObject::eventFilter(o, e);
}
MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
: QWidget(parent),
_section(new Section(300)),
_headerInfo(new QLabel()),
_frame(new QWidget),
_contentLayout(new QVBoxLayout),
_noBorderframe(new QFrame),
_borderFrame(new QFrame),
_dropLineAbove(new QFrame),
_dropLineBelow(new QFrame),
_showHighlight(highlight)
{
_section = new Section(300);
_headerInfo = new QLabel();
_dropLineAbove->setLineWidth(3);
_dropLineAbove->setFixedHeight(11);
_dropLineBelow->setLineWidth(3);
_dropLineBelow->setFixedHeight(11);
_frame = new QFrame;
_frame->setObjectName("segmentFrame");
_highLightFrameLayout = new QVBoxLayout;
SetSelected(false);
_frame->setLayout(_highLightFrameLayout);
// Set background transparent to avoid blocking highlight frame
_borderFrame->setObjectName("border");
_borderFrame->setStyleSheet("#border {"
"border-color: rgba(0, 0, 0, 255);"
"border-width: 2px;"
"border-style: dashed;"
"border-radius: 4px;"
"background-color: rgba(0,0,0,100);"
"}");
_noBorderframe->setObjectName("noBorder");
_noBorderframe->setStyleSheet("#noBorder {"
"border-color: rgba(0, 0, 0, 0);"
"border-width: 2px;"
"border-style: dashed;"
"border-radius: 4px;"
"background-color: rgba(0,0,0,50);"
"}");
_frame->setObjectName("frameWrapper");
_frame->setStyleSheet("#frameWrapper {"
"border-width: 2px;"
"border-radius: 4px;"
"background-color: rgba(0,0,0,0);"
"}");
// Set background of these widget types to be transparent to avoid
// blocking highlight frame background
setStyleSheet("QCheckBox { background-color: rgba(0,0,0,0); }"
"QLabel { background-color: rgba(0,0,0,0); }"
"QSlider { background-color: rgba(0,0,0,0); }");
@@ -61,7 +107,6 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
QWidget::connect(_section, &Section::Collapsed, this,
&MacroSegmentEdit::Collapsed);
// Macro signals
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,
SIGNAL(MacroAdded(const QString &)));
@@ -71,7 +116,6 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
SIGNAL(MacroRenamed(const QString &, const QString)),
this,
SIGNAL(MacroRenamed(const QString &, const QString)));
// Scene group signals
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
SIGNAL(SceneGroupAdded(const QString &)));
@@ -81,6 +125,26 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
parent,
SIGNAL(SceneGroupRenamed(const QString &, const QString)), this,
SIGNAL(SceneGroupRenamed(const QString &, const QString)));
auto frameLayout = new QGridLayout;
frameLayout->setContentsMargins(0, 0, 0, 0);
frameLayout->addLayout(_contentLayout, 0, 0);
frameLayout->addWidget(_noBorderframe, 0, 0);
frameLayout->addWidget(_borderFrame, 0, 0);
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(_dropLineAbove);
layout->addLayout(frameLayout);
layout->addWidget(_dropLineBelow);
_frame->setLayout(layout);
SetSelected(false);
ShowDropLine(DropLineState::NONE);
_timer.setInterval(1500);
connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start();
}
void MacroSegmentEdit::HeaderInfoChanged(const QString &text)
@@ -96,11 +160,20 @@ void MacroSegmentEdit::Collapsed(bool collapsed)
}
}
void MacroSegmentEdit::mousePressEvent(QMouseEvent *event)
void MacroSegmentEdit::Highlight()
{
if (event->button() == Qt::LeftButton && Data()) {
emit SelectionChagned(Data()->GetIndex());
if (!Data()) {
return;
}
if (_showHighlight && Data()->Highlight()) {
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
}
}
void MacroSegmentEdit::EnableHighlight(bool value)
{
_showHighlight = value;
}
void MacroSegmentEdit::SetFocusPolicyOfWidgets()
@@ -124,21 +197,31 @@ void MacroSegmentEdit::SetCollapsed(bool collapsed)
void MacroSegmentEdit::SetSelected(bool selected)
{
if (selected) {
_frame->setStyleSheet("#segmentFrame {"
"border-color: rgba(0, 0, 0, 255);"
"border-width: 2px;"
"border-style: dashed;"
"border-radius: 4px;"
"background-color: rgba(0,0,0,100);"
"}");
} else {
_frame->setStyleSheet("#segmentFrame {"
"border-color: rgba(0, 0, 0, 0);"
"border-width: 2px;"
"border-style: dashed;"
"border-radius: 4px;"
"background-color: rgba(0,0,0,50);"
"}");
_borderFrame->setVisible(selected);
_noBorderframe->setVisible(!selected);
}
void MacroSegmentEdit::ShowDropLine(DropLineState state)
{
switch (state) {
case MacroSegmentEdit::DropLineState::NONE:
_dropLineAbove->setFrameShadow(QFrame::Plain);
_dropLineAbove->setFrameShape(QFrame::NoFrame);
_dropLineBelow->hide();
break;
case MacroSegmentEdit::DropLineState::ABOVE:
_dropLineAbove->setFrameShadow(QFrame::Sunken);
_dropLineAbove->setFrameShape(QFrame::Panel);
_dropLineBelow->hide();
break;
case MacroSegmentEdit::DropLineState::BELOW:
_dropLineAbove->setFrameShadow(QFrame::Plain);
_dropLineAbove->setFrameShape(QFrame::NoFrame);
_dropLineBelow->setFrameShadow(QFrame::Sunken);
_dropLineBelow->setFrameShape(QFrame::Panel);
_dropLineBelow->show();
break;
default:
break;
}
}

View File

@@ -4,6 +4,7 @@
#include "headers/macro-condition-edit.hpp"
#include "headers/advanced-scene-switcher.hpp"
#include "headers/name-dialog.hpp"
#include "headers/macro-properties.hpp"
#include "headers/utility.hpp"
#include <QColor>
@@ -11,8 +12,8 @@
#include <QGraphicsOpacityEffect>
#include <QPropertyAnimation>
static QTimer highlightMatchTimer;
static QMetaObject::Connection addPulse;
static QTimer onChangeHighlightTimer;
bool macroNameExists(std::string name)
{
@@ -67,7 +68,8 @@ QListWidgetItem *AddNewMacroListEntry(QListWidget *list,
{
QListWidgetItem *item = new QListWidgetItem(list);
item->setData(Qt::UserRole, QString::fromStdString(macro->Name()));
auto listEntry = new MacroListEntryWidget(macro, list);
auto listEntry = new MacroListEntryWidget(
macro, switcher->macroProperties._highlightExecuted, list);
item->setSizeHint(listEntry->minimumSizeHint());
list->setItemWidget(item, listEntry);
return item;
@@ -96,12 +98,13 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
if (!item) {
return;
}
int idx = ui->macros->currentRow();
delete item;
QString name;
{
std::lock_guard<std::mutex> lock(switcher->m);
switcher->abortMacroWait = true;
switcher->macroWaitCv.notify_all();
int idx = ui->macros->currentRow();
QString::fromStdString(switcher->macros[idx]->Name());
switcher->macros.erase(switcher->macros.begin() + idx);
for (auto &m : switcher->macros) {
@@ -109,8 +112,6 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
}
}
delete item;
if (ui->macros->count() == 0) {
ui->macroHelp->setVisible(true);
}
@@ -241,8 +242,7 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
for (; afterIdx < actions.size(); afterIdx++) {
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
ConnectControlSignals(newEntry);
actionsList->ContentLayout()->addWidget(newEntry);
actionsList->Add(newEntry);
}
actionsList->SetHelpMsgVisible(actions.size() == 0);
}
@@ -255,8 +255,7 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
auto newEntry = new MacroConditionEdit(
this, &conditions[afterIdx],
conditions[afterIdx]->GetId(), root);
ConnectControlSignals(newEntry);
conditionsList->ContentLayout()->addWidget(newEntry);
conditionsList->Add(newEntry);
root = false;
}
conditionsList->SetHelpMsgVisible(conditions.size() == 0);
@@ -305,54 +304,39 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
ui->runMacroInParallel->setChecked(m.RunInParallel());
ui->runMacroOnChange->setChecked(m.MatchOnChange());
}
clearLayout(conditionsList->ContentLayout());
clearLayout(actionsList->ContentLayout());
conditionsList->Clear();
actionsList->Clear();
m.ResetUIHelpers();
PopulateMacroConditions(m);
PopulateMacroActions(m);
ui->macroEdit->setDisabled(false);
SetMacroEditAreaDisabled(false);
currentActionIdx = -1;
currentConditionIdx = -1;
HighlightControls();
}
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
{
ui->macroName->setDisabled(disable);
ui->runMacro->setDisabled(disable);
ui->runMacroInParallel->setDisabled(disable);
ui->runMacroOnChange->setDisabled(disable);
ui->macroActions->setDisabled(disable);
ui->macroConditions->setDisabled(disable);
ui->macroSplitter->setDisabled(disable);
}
void AdvSceneSwitcher::HighlightAction(int idx)
{
auto item = actionsList->ContentLayout()->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
actionsList->Highlight(idx);
}
void AdvSceneSwitcher::HighlightCondition(int idx)
{
auto item = conditionsList->ContentLayout()->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
}
void AdvSceneSwitcher::ConnectControlSignals(MacroActionEdit *a)
{
connect(a, &MacroActionEdit::SelectionChagned, this,
&AdvSceneSwitcher::MacroActionSelectionChanged);
}
void AdvSceneSwitcher::ConnectControlSignals(MacroConditionEdit *c)
{
connect(c, &MacroActionEdit::SelectionChagned, this,
&AdvSceneSwitcher::MacroConditionSelectionChanged);
conditionsList->Highlight(idx);
}
Macro *AdvSceneSwitcher::getSelectedMacro()
@@ -374,7 +358,11 @@ void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
}
if (idx == -1) {
ui->macroEdit->setDisabled(true);
SetMacroEditAreaDisabled(true);
conditionsList->Clear();
actionsList->Clear();
conditionsList->SetHelpMsgVisible(true);
actionsList->SetHelpMsgVisible(true);
return;
}
@@ -406,6 +394,34 @@ void AdvSceneSwitcher::MacroDragDropReorder(QModelIndex, int from, int,
}
}
void AdvSceneSwitcher::HighlightOnChange()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
if (switcher->macroProperties._highlightExecuted &&
macro->OnChangePreventedActionsRecently()) {
PulseWidget(ui->runMacroOnChange, Qt::yellow, Qt::transparent,
true);
}
}
void AdvSceneSwitcher::on_macroProperties_clicked()
{
MacroProperties prop = switcher->macroProperties;
bool accepted = MacroPropertiesDialog::AskForSettings(
this, prop, getSelectedMacro());
if (!accepted) {
return;
}
switcher->macroProperties = prop;
emit HighlightMacrosChanged(prop._highlightExecuted);
emit HighlightActionsChanged(prop._highlightActions);
emit HighlightConditionsChanged(prop._highlightConditions);
}
void AdvSceneSwitcher::setupMacroTab()
{
const QSignalBlocker signalBlocker(ui->macros);
@@ -435,6 +451,8 @@ void AdvSceneSwitcher::setupMacroTab()
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
connect(conditionsList, &MacroSegmentList::SelectionChagned, this,
&AdvSceneSwitcher::MacroConditionSelectionChanged);
connect(conditionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroConditionReorder);
ui->macroConditionsLayout->insertWidget(0, conditionsList);
delete actionsList;
@@ -443,6 +461,8 @@ void AdvSceneSwitcher::setupMacroTab()
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
connect(actionsList, &MacroSegmentList::SelectionChagned, this,
&AdvSceneSwitcher::MacroActionSelectionChanged);
connect(actionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroActionReorder);
ui->macroActionsLayout->insertWidget(0, actionsList);
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -455,15 +475,24 @@ void AdvSceneSwitcher::setupMacroTab()
connect(conditionsList, &QWidget::customContextMenuRequested, this,
&AdvSceneSwitcher::ShowMacroConditionsContextMenu);
ui->macroEdit->setDisabled(true);
SetMacroEditAreaDisabled(true);
ui->macroPriorityWarning->setVisible(
switcher->functionNamesByPriority[0] != macro_func);
highlightMatchTimer.setInterval(1000);
connect(&highlightMatchTimer, &QTimer::timeout, this,
&AdvSceneSwitcher::HighlightMatchedMacros);
highlightMatchTimer.start();
onChangeHighlightTimer.setInterval(1500);
connect(&onChangeHighlightTimer, SIGNAL(timeout()), this,
SLOT(HighlightOnChange()));
onChangeHighlightTimer.start();
// Move condition controls into splitter handle layout
auto handle = ui->macroSplitter->handle(1);
auto item = ui->macroConditionsLayout->takeAt(1);
if (item) {
auto layout = item->layout();
layout->setContentsMargins(7, 7, 7, 7);
handle->setLayout(layout);
ui->macroSplitter->setHandleWidth(38);
}
}
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
@@ -530,25 +559,13 @@ void AdvSceneSwitcher::CopyMacro()
ui->macros->setCurrentItem(item);
}
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < layout->count(); i++) {
item = layout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void AdvSceneSwitcher::ExpandAllActions()
{
auto m = getSelectedMacro();
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(actionsList->ContentLayout(), false);
actionsList->SetCollapsed(false);
}
void AdvSceneSwitcher::ExpandAllConditions()
@@ -557,7 +574,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(conditionsList->ContentLayout(), false);
conditionsList->SetCollapsed(false);
}
void AdvSceneSwitcher::CollapseAllActions()
@@ -566,7 +583,7 @@ void AdvSceneSwitcher::CollapseAllActions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(actionsList->ContentLayout(), true);
actionsList->SetCollapsed(true);
}
void AdvSceneSwitcher::CollapseAllConditions()
@@ -575,7 +592,7 @@ void AdvSceneSwitcher::CollapseAllConditions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(conditionsList->ContentLayout(), true);
conditionsList->SetCollapsed(true);
}
void AdvSceneSwitcher::MinimizeActions()
@@ -598,41 +615,130 @@ void AdvSceneSwitcher::MinimizeConditions()
ui->macroSplitter->setSizes(sizes);
}
void AdvSceneSwitcher::HighlightMatchedMacros()
bool AdvSceneSwitcher::MacroTabIsInFocus()
{
if (loading || !(switcher && switcher->highlightExecutedMacros)) {
return isActiveWindow() && isAncestorOf(focusWidget()) &&
(ui->tabWidget->currentWidget()->objectName() == "macroTab");
}
void AdvSceneSwitcher::UpMacroSegementHotkey()
{
if (!MacroTabIsInFocus()) {
return;
}
for (int idx = 0; idx < (int)switcher->macros.size(); idx++) {
if (switcher->macros[idx]->WasExecutedRecently()) {
auto item = ui->macros->item(idx);
if (!item) {
continue;
auto macro = getSelectedMacro();
if (!macro) {
return;
}
int actionSize = macro->Actions().size();
int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) {
if (conditionSize == 0) {
MacroActionSelectionChanged(0);
} else {
MacroConditionSelectionChanged(0);
}
auto widget = ui->macros->itemWidget(item);
if (!widget) {
continue;
} else {
if (actionSize == 0) {
MacroConditionSelectionChanged(0);
} else {
MacroActionSelectionChanged(0);
}
PulseWidget(widget, Qt::green, QColor(0, 0, 0, 0),
true);
}
return;
}
if (currentActionIdx > 0) {
MacroActionSelectionChanged(currentActionIdx - 1);
return;
}
if (currentConditionIdx > 0) {
MacroConditionSelectionChanged(currentConditionIdx - 1);
return;
}
if (currentActionIdx == 0) {
if (conditionSize == 0) {
MacroActionSelectionChanged(actionSize - 1);
} else {
MacroConditionSelectionChanged(conditionSize - 1);
}
return;
}
if (currentConditionIdx == 0) {
if (actionSize == 0) {
MacroConditionSelectionChanged(conditionSize - 1);
} else {
MacroActionSelectionChanged(actionSize - 1);
}
return;
}
}
void AdvSceneSwitcher::SetSelection(MacroSegmentList *list, int idx)
void AdvSceneSwitcher::DownMacroSegementHotkey()
{
for (int i = 0; i < list->ContentLayout()->count(); ++i) {
auto widget = static_cast<MacroSegmentEdit *>(
list->ContentLayout()->itemAt(i)->widget());
if (widget) {
widget->SetSelected(i == idx);
if (!MacroTabIsInFocus()) {
return;
}
auto macro = getSelectedMacro();
if (!macro) {
return;
}
int actionSize = macro->Actions().size();
int conditionSize = macro->Conditions().size();
if (currentActionIdx == -1 && currentConditionIdx == -1) {
if (lastInteracted == MacroSection::CONDITIONS) {
if (conditionSize == 0) {
MacroActionSelectionChanged(0);
} else {
MacroConditionSelectionChanged(0);
}
} else {
if (actionSize == 0) {
MacroConditionSelectionChanged(0);
} else {
MacroActionSelectionChanged(0);
}
}
return;
}
if (currentActionIdx < actionSize - 1) {
MacroActionSelectionChanged(currentActionIdx + 1);
return;
}
if (currentConditionIdx < conditionSize - 1) {
MacroConditionSelectionChanged(currentConditionIdx + 1);
return;
}
if (currentActionIdx == actionSize - 1) {
if (conditionSize == 0) {
MacroActionSelectionChanged(0);
} else {
MacroConditionSelectionChanged(0);
}
return;
}
if (currentConditionIdx == conditionSize - 1) {
if (actionSize == 0) {
MacroConditionSelectionChanged(0);
} else {
MacroActionSelectionChanged(0);
}
return;
}
}
void AdvSceneSwitcher::DeleteMacroSegementHotkey()
{
if (!MacroTabIsInFocus()) {
return;
}
if (currentActionIdx != -1) {
RemoveMacroAction(currentActionIdx);
} else if (currentConditionIdx != -1) {
@@ -642,25 +748,33 @@ void AdvSceneSwitcher::DeleteMacroSegementHotkey()
void fade(QWidget *widget, bool fadeOut)
{
const double fadeOutOpacity = 0.3;
// Don't use exactly 1.0 as for some reason this causes buttons in
// macroSplitter handle layout to not be redrawn unless mousing over
// them
const double fadeInOpacity = 0.99;
auto curEffect = widget->graphicsEffect();
if (curEffect) {
auto curOpacity =
dynamic_cast<QGraphicsOpacityEffect *>(curEffect);
if (curOpacity && ((fadeOut && curOpacity->opacity() == 0.3) ||
(!fadeOut && curOpacity->opacity() == 1))) {
if (curOpacity &&
((fadeOut && doubleEquals(curOpacity->opacity(),
fadeOutOpacity, 0.0001)) ||
(!fadeOut && doubleEquals(curOpacity->opacity(),
fadeInOpacity, 0.0001)))) {
return;
}
} else if (!fadeOut) {
return;
}
delete curEffect;
QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect();
widget->setGraphicsEffect(opacityEffect);
QPropertyAnimation *animation =
new QPropertyAnimation(opacityEffect, "opacity");
animation->setDuration(350);
animation->setStartValue(fadeOut ? 1 : 0.3);
animation->setEndValue(fadeOut ? .3 : 1);
animation->setStartValue(fadeOut ? fadeInOpacity : fadeOutOpacity);
animation->setEndValue(fadeOut ? fadeOutOpacity : fadeInOpacity);
animation->setEasingCurve(QEasingCurve::OutQuint);
animation->start(QPropertyAnimation::DeleteWhenStopped);
}

View File

@@ -11,28 +11,15 @@
constexpr int perfLogThreshold = 300;
const std::map<LogicType, LogicTypeInfo> MacroCondition::logicTypes = {
{LogicType::NONE, {"AdvSceneSwitcher.logic.none"}},
{LogicType::AND, {"AdvSceneSwitcher.logic.and"}},
{LogicType::OR, {"AdvSceneSwitcher.logic.or"}},
{LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}},
{LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}},
{LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}},
{LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}},
};
Macro::Macro(const std::string &name)
{
SetupHotkeys();
SetName(name);
}
Macro::~Macro()
{
_die = true;
if (_thread.joinable()) {
_thread.join();
}
Stop();
ClearHotkeys();
}
@@ -56,10 +43,7 @@ bool Macro::CeckMatch()
ms.count(), c->GetId().c_str(), Name().c_str());
}
if (!cond) {
c->ResetDuration();
}
cond = cond && c->DurationReached();
c->CheckDurationModifier(cond);
switch (c->GetLogicType()) {
case LogicType::NONE:
@@ -67,24 +51,40 @@ bool Macro::CeckMatch()
"ignoring condition check 'none' for '%s'",
_name.c_str());
continue;
break;
case LogicType::AND:
_matched = _matched && cond;
if (cond) {
c->SetHighlight();
}
break;
case LogicType::OR:
_matched = _matched || cond;
if (cond) {
c->SetHighlight();
}
break;
case LogicType::AND_NOT:
_matched = _matched && !cond;
if (!cond) {
c->SetHighlight();
}
break;
case LogicType::OR_NOT:
_matched = _matched || !cond;
if (!cond) {
c->SetHighlight();
}
break;
case LogicType::ROOT_NONE:
_matched = cond;
if (cond) {
c->SetHighlight();
}
break;
case LogicType::ROOT_NOT:
_matched = !cond;
if (!cond) {
c->SetHighlight();
}
break;
default:
blog(LOG_WARNING,
@@ -102,6 +102,7 @@ bool Macro::CeckMatch()
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
_name.c_str());
_matched = false;
SetOnChangeHighlight();
}
_lastMatched = newLastMatched;
@@ -111,7 +112,7 @@ bool Macro::CeckMatch()
if (_matched && _count != std::numeric_limits<int>::max()) {
_count++;
}
_lastCheckTime = std::chrono::high_resolution_clock::now();
return _matched;
}
@@ -125,10 +126,10 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
_done = false;
bool ret = true;
if (_runInParallel || forceParallel) {
if (_thread.joinable()) {
_thread.join();
if (_backgroundThread.joinable()) {
_backgroundThread.join();
}
_thread = std::thread(
_backgroundThread = std::thread(
[this, ignorePause] { RunActions(ignorePause); });
} else {
RunActions(ret, ignorePause);
@@ -137,6 +138,18 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
return ret;
}
int64_t Macro::MsSinceLastCheck()
{
if (_lastCheckTime.time_since_epoch().count() == 0) {
return 0;
}
const auto timePassed =
std::chrono::high_resolution_clock::now() - _lastCheckTime;
return std::chrono::duration_cast<std::chrono::milliseconds>(timePassed)
.count() +
1;
}
void Macro::SetName(const std::string &name)
{
_name = name;
@@ -148,6 +161,7 @@ void Macro::ResetTimers()
for (auto &c : _conditions) {
c->ResetDuration();
}
_lastCheckTime = {};
}
void Macro::RunActions(bool &retVal, bool ignorePause)
@@ -158,9 +172,9 @@ void Macro::RunActions(bool &retVal, bool ignorePause)
ret = ret && a->PerformAction();
if (!ret || (_paused && !ignorePause) || _stop || _die) {
retVal = ret;
_done = true;
return;
break;
}
a->SetHighlight();
}
_done = true;
}
@@ -171,6 +185,11 @@ void Macro::RunActions(bool ignorePause)
RunActions(unused, ignorePause);
}
void Macro::SetOnChangeHighlight()
{
_onChangeTriggered = true;
}
void Macro::SetPaused(bool pause)
{
if (_paused && !pause) {
@@ -179,6 +198,31 @@ void Macro::SetPaused(bool pause)
_paused = pause;
}
void Macro::AddHelperThread(std::thread &&newThread)
{
for (unsigned int i = 0; i < _helperThreads.size(); i++) {
if (!_helperThreads[i].joinable()) {
_helperThreads[i] = std::move(newThread);
return;
}
}
_helperThreads.push_back(std::move(newThread));
}
void Macro::Stop()
{
_stop = true;
switcher->macroWaitCv.notify_all();
for (auto &t : _helperThreads) {
if (t.joinable()) {
t.join();
}
}
if (_backgroundThread.joinable()) {
_backgroundThread.join();
}
}
void Macro::UpdateActionIndices()
{
int idx = 0;
@@ -204,6 +248,7 @@ bool Macro::Save(obs_data_t *obj)
obs_data_set_bool(obj, "parallel", _runInParallel);
obs_data_set_bool(obj, "onChange", _matchOnChange);
obs_data_set_bool(obj, "registerHotkeys", _registerHotkeys);
obs_data_array_t *pauseHotkey = obs_hotkey_save(_pauseHotkey);
obs_data_set_array(obj, "pauseHotkey", pauseHotkey);
obs_data_array_release(pauseHotkey);
@@ -283,6 +328,11 @@ bool Macro::Load(obs_data_t *obj)
_runInParallel = obs_data_get_bool(obj, "parallel");
_matchOnChange = obs_data_get_bool(obj, "onChange");
obs_data_set_default_bool(obj, "registerHotkeys", true);
_registerHotkeys = obs_data_get_bool(obj, "registerHotkeys");
if (_registerHotkeys) {
SetupHotkeys();
}
obs_data_array_t *pauseHotkey = obs_data_get_array(obj, "pauseHotkey");
obs_hotkey_load(_pauseHotkey, pauseHotkey);
obs_data_array_release(pauseHotkey);
@@ -294,7 +344,6 @@ bool Macro::Load(obs_data_t *obj)
obs_data_get_array(obj, "togglePauseHotkey");
obs_hotkey_load(_togglePauseHotkey, togglePauseHotkey);
obs_data_array_release(togglePauseHotkey);
SetHotkeysDesc();
bool root = true;
@@ -392,6 +441,45 @@ bool Macro::WasExecutedRecently()
return false;
}
bool Macro::OnChangePreventedActionsRecently()
{
if (_onChangeTriggered) {
_onChangeTriggered = false;
return true;
}
return false;
}
void Macro::ResetUIHelpers()
{
_onChangeTriggered = false;
for (auto c : _conditions) {
c->Highlight();
}
for (auto a : _actions) {
a->Highlight();
}
}
void Macro::EnablePauseHotkeys(bool value)
{
if (_registerHotkeys == value) {
return;
}
if (_registerHotkeys) {
ClearHotkeys();
} else {
SetupHotkeys();
}
_registerHotkeys = value;
}
bool Macro::PauseHotkeysEnabled()
{
return _registerHotkeys;
}
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
{
if (pressed) {
@@ -478,63 +566,10 @@ void Macro::SetHotkeysDesc()
_name, _togglePauseHotkey);
}
bool MacroCondition::Save(obs_data_t *obj)
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_int(obj, "logic", static_cast<int>(_logic));
_duration.Save(obj);
return true;
}
bool MacroCondition::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
_logic = static_cast<LogicType>(obs_data_get_int(obj, "logic"));
_duration.Load(obj);
return true;
}
void MacroCondition::SetDurationConstraint(const DurationConstraint &dur)
{
_duration = dur;
}
void MacroCondition::SetDurationCondition(DurationCondition cond)
{
_duration.SetCondition(cond);
}
void MacroCondition::SetDurationUnit(DurationUnit u)
{
_duration.SetUnit(u);
}
void MacroCondition::SetDuration(double seconds)
{
_duration.SetValue(seconds);
}
bool MacroAction::Save(obs_data_t *obj)
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
return true;
}
bool MacroAction::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
return true;
}
void MacroAction::LogAction()
{
vblog(LOG_INFO, "performed action %s", GetId().c_str());
}
void SwitcherData::saveMacros(obs_data_t *obj)
{
switcher->macroProperties.Save(obj);
obs_data_array_t *macroArray = obs_data_array_create();
for (auto &m : macros) {
obs_data_t *array_obj = obs_data_create();
@@ -550,8 +585,9 @@ void SwitcherData::saveMacros(obs_data_t *obj)
void SwitcherData::loadMacros(obs_data_t *obj)
{
macros.clear();
switcher->macroProperties.Load(obj);
macros.clear();
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
size_t count = obs_data_array_count(macroArray);
@@ -616,60 +652,3 @@ Macro *GetMacroByQString(const QString &name)
{
return GetMacroByName(name.toUtf8().constData());
}
MacroRef::MacroRef(std::string name) : _name(name)
{
UpdateRef();
}
void MacroRef::UpdateRef()
{
_ref = GetMacroByName(_name.c_str());
}
void MacroRef::UpdateRef(std::string newName)
{
_name = newName;
UpdateRef();
}
void MacroRef::UpdateRef(QString newName)
{
_name = newName.toStdString();
UpdateRef();
}
void MacroRef::Save(obs_data_t *obj)
{
if (_ref) {
obs_data_set_string(obj, "macro", _ref->Name().c_str());
}
}
void MacroRef::Load(obs_data_t *obj)
{
_name = obs_data_get_string(obj, "macro");
UpdateRef();
}
Macro *MacroRef::get()
{
return _ref;
}
Macro *MacroRef::operator->()
{
return _ref;
}
void MacroRefCondition::ResolveMacroRef()
{
_macro.UpdateRef();
}
void MacroRefAction::ResolveMacroRef()
{
_macro.UpdateRef();
}
void MultiMacroRefAction::ResolveMacroRef()
{
for (auto &m : _macros) {
m.UpdateRef();
}
}

View File

@@ -19,9 +19,9 @@ void GetWindowList(std::vector<std::string> &windows)
windows.resize(0);
@autoreleasepool {
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
@@ -37,14 +37,17 @@ void GetWindowList(std::vector<std::string> &windows)
if (!name.empty() &&
find(windows.begin(), windows.end(), name) ==
windows.end())
windows.end()) {
windows.emplace_back(name);
}
if (!owner.empty() &&
find(windows.begin(), windows.end(), owner) ==
windows.end())
windows.end()) {
windows.emplace_back(owner);
}
}
apps = nil;
CFRelease(cfApps);
}
}
@@ -61,12 +64,10 @@ void GetWindowList(QStringList &windows)
void GetCurrentWindowTitle(std::string &title)
{
title.resize(0);
@autoreleasepool {
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly,
kCGNullWindowID);
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
int layer =
[[app objectForKey:@"kCGWindowLayer"] intValue];
@@ -85,14 +86,16 @@ void GetCurrentWindowTitle(std::string &title)
lengthOfBytesUsingEncoding:
NSUTF8StringEncoding]);
if (!name.empty())
if (!name.empty()) {
title = name;
else if (!owner.empty())
} else if (!owner.empty()) {
title = owner;
}
break;
}
}
apps = nil;
CFRelease(cfApps);
}
}
@@ -165,9 +168,9 @@ bool isMaximized(const std::string &title)
{
@autoreleasepool {
NSArray *screens = [NSScreen screens];
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
@@ -190,11 +193,15 @@ bool isMaximized(const std::string &title)
screen) &&
isWindowMaximizedOnScreen(app,
screen)) {
apps = nil;
CFRelease(cfApps);
return true;
}
}
}
}
apps = nil;
CFRelease(cfApps);
}
return false;
}
@@ -214,9 +221,9 @@ bool isFullscreen(const std::string &title)
{
@autoreleasepool {
NSArray *screens = [NSScreen screens];
NSMutableArray *apps =
(__bridge NSMutableArray *)CGWindowListCopyWindowInfo(
kCGWindowListOptionAll, kCGNullWindowID);
CFArrayRef cfApps = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSMutableArray *apps = (__bridge NSMutableArray *)cfApps;
for (NSDictionary *app in apps) {
std::string name([[app objectForKey:@"kCGWindowName"]
UTF8String],
@@ -237,12 +244,17 @@ bool isFullscreen(const std::string &title)
for (NSScreen *screen in screens) {
if (isWindowOriginOnScreen(app, screen,
true) &&
isWindowFullscreenOnScreen(app,
screen))
isWindowFullscreenOnScreen(
app, screen)) {
apps = nil;
CFRelease(cfApps);
return true;
}
}
}
}
apps = nil;
CFRelease(cfApps);
}
return false;
}
@@ -264,12 +276,14 @@ void GetProcessList(QStringList &list)
NSArray *array = [ws runningApplications];
for (NSRunningApplication *app in array) {
NSString *name = app.localizedName;
if (!name)
if (!name) {
continue;
}
const char *str = name.UTF8String;
if (str && *str)
if (str && *str) {
list << (str);
}
}
}
}
@@ -412,89 +426,6 @@ void PressKeys(const std::vector<HotkeyType> keys, int duration)
// on MacOS
canSimulateKeyPresses = false;
return;
// Check premissions
NSDictionary *options =
@{(__bridge id)kAXTrustedCheckOptionPrompt: @NO};
if (!AXIsProcessTrustedWithOptions((CFDictionaryRef)options)) {
NSString *urlString =
@"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString:urlString]];
canSimulateKeyPresses = false;
return;
}
long modifierFlags = 0;
auto source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!source) {
canSimulateKeyPresses = false;
return;
}
// Press keys
for (auto &key : keys) {
auto it = keyTable.find(key);
if (it == keyTable.end()) {
continue;
}
CGKeyCode inputKeyCode = it->second;
CGEventRef keyPress =
CGEventCreateKeyboardEvent(source, inputKeyCode, true);
if (!keyPress) {
canSimulateKeyPresses = false;
CFRelease(source);
return;
}
CGEventSetFlags(keyPress, modifierFlags);
CGEventPost(kCGAnnotatedSessionEventTap, keyPress);
CFRelease(keyPress);
switch (it->first) {
case HotkeyType::Key_Shift_L:
case HotkeyType::Key_Shift_R:
modifierFlags |= NX_SHIFTMASK;
break;
case HotkeyType::Key_Control_L:
case HotkeyType::Key_Control_R:
modifierFlags |= NX_CONTROLMASK;
break;
case HotkeyType::Key_Alt_L:
case HotkeyType::Key_Alt_R:
modifierFlags |= NX_ALTERNATEMASK;
break;
case HotkeyType::Key_Win_L:
case HotkeyType::Key_Win_R:
modifierFlags |= NX_COMMANDMASK;
break;
default:
break;
}
}
// When instantly releasing the key presses OBS might miss them
std::this_thread::sleep_for(std::chrono::milliseconds(300));
// Release keys
for (auto &key : keys) {
auto it = keyTable.find(key);
if (it == keyTable.end()) {
continue;
}
CGKeyCode inputKeyCode = it->second;
CGEventRef keyRelease =
CGEventCreateKeyboardEvent(source, inputKeyCode, false);
if (!keyRelease) {
canSimulateKeyPresses = false;
CFRelease(source);
return;
}
CGEventPost(kCGAnnotatedSessionEventTap, keyRelease);
CFRelease(keyRelease);
}
CFRelease(source);
}
void PlatformCleanup() {}

View File

@@ -58,6 +58,7 @@ OBSWeakSource SceneGroup::getNextSceneRandom()
}
int rIdx = lastRandomScene;
std::srand(std::time(0));
do {
rIdx = rand() % scenes.size();
} while (rIdx == lastRandomScene);

View File

@@ -125,6 +125,7 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
QWidget::connect(_idx, SIGNAL(currentIndexChanged(int)), this,
SLOT(IdxChanged(int)));
auto layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_idx);
layout->addWidget(_sceneItems);
setLayout(layout);

View File

@@ -204,12 +204,13 @@ void placeWidgets(std::string text, QBoxLayout *layout,
}
}
void deleteLayoutItem(QLayoutItem *item)
void deleteLayoutItemWidget(QLayoutItem *item)
{
if (item) {
auto widget = item->widget();
if (widget) {
widget->setVisible(false);
widget->deleteLater();
}
delete item;
}
@@ -223,20 +224,24 @@ void clearLayout(QLayout *layout, int afterIdx)
clearLayout(item->layout());
delete item->layout();
}
if (item->widget()) {
delete item->widget();
}
delete item;
deleteLayoutItemWidget(item);
}
}
void setLayoutVisible(QLayout *layout, bool visible)
{
if (!layout) {
return;
}
for (int i = 0; i < layout->count(); ++i) {
QWidget *widget = layout->itemAt(i)->widget();
if (widget != NULL) {
QLayout *nestedLayout = layout->itemAt(i)->layout();
if (widget) {
widget->setVisible(visible);
}
if (nestedLayout) {
setLayoutVisible(nestedLayout, visible);
}
}
}
@@ -595,7 +600,8 @@ void populateAudioSelection(QComboBox *sel, bool addSelect)
sel->setCurrentIndex(0);
}
void populateVideoSelection(QComboBox *sel, bool addScenes, bool addSelect)
void populateVideoSelection(QComboBox *sel, bool addMainOutput, bool addScenes,
bool addSelect)
{
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
@@ -623,6 +629,10 @@ void populateVideoSelection(QComboBox *sel, bool addScenes, bool addSelect)
}
sel->model()->sort(0);
if (addMainOutput) {
sel->insertItem(
0, obs_module_text("AdvSceneSwitcher.OBSVideoOutput"));
}
if (addSelect) {
addSelectionEntry(
sel,
@@ -989,3 +999,8 @@ void setHeightToContentHeight(QListWidget *list)
2 * list->frameWidth());
}
}
bool doubleEquals(double left, double right, double epsilon)
{
return (fabs(left - right) < epsilon);
}