SceneSwitcher/plugins/video/macro-condition-video.cpp
WarmUpTill 874b9b86e2 Refactor locking of macro segments
This should avoid crashes when actions or conditions are performed in
parallel to the main macro loop and will improve the UI responsiveness
in some scenarios
2025-06-20 22:29:35 +02:00

1667 lines
51 KiB
C++

#include "macro-condition-video.hpp"
#include "screenshot-dialog.hpp"
#include <layout-helpers.hpp>
#include <macro-condition-edit.hpp>
#include <plugin-state-helpers.hpp>
#include <QBuffer>
#include <QColorDialog>
#include <QDesktopServices>
#include <QFileDialog>
#include <QMessageBox>
#include <QtGlobal>
#include <QToolTip>
#include <ui-helpers.hpp>
#include <selection-helpers.hpp>
namespace advss {
const std::string MacroConditionVideo::id = "video";
bool MacroConditionVideo::_registered = MacroConditionFactory::Register(
MacroConditionVideo::id,
{MacroConditionVideo::Create, MacroConditionVideoEdit::Create,
"AdvSceneSwitcher.condition.video"});
const static std::map<VideoCondition, std::string> conditionTypes = {
{VideoCondition::MATCH,
"AdvSceneSwitcher.condition.video.condition.match"},
{VideoCondition::DIFFER,
"AdvSceneSwitcher.condition.video.condition.differ"},
{VideoCondition::HAS_NOT_CHANGED,
"AdvSceneSwitcher.condition.video.condition.hasNotChanged"},
{VideoCondition::HAS_CHANGED,
"AdvSceneSwitcher.condition.video.condition.hasChanged"},
{VideoCondition::NO_IMAGE,
"AdvSceneSwitcher.condition.video.condition.noImage"},
{VideoCondition::PATTERN,
"AdvSceneSwitcher.condition.video.condition.pattern"},
{VideoCondition::OBJECT,
"AdvSceneSwitcher.condition.video.condition.object"},
{VideoCondition::BRIGHTNESS,
"AdvSceneSwitcher.condition.video.condition.brightness"},
#ifdef OCR_SUPPORT
{VideoCondition::OCR, "AdvSceneSwitcher.condition.video.condition.ocr"},
#endif
{VideoCondition::COLOR,
"AdvSceneSwitcher.condition.video.condition.color"},
};
const static std::map<VideoInput::Type, std::string> videoInputTypes = {
{VideoInput::Type::OBS_MAIN_OUTPUT,
"AdvSceneSwitcher.condition.video.type.main"},
{VideoInput::Type::SOURCE,
"AdvSceneSwitcher.condition.video.type.source"},
{VideoInput::Type::SCENE,
"AdvSceneSwitcher.condition.video.type.scene"},
};
const static std::map<cv::TemplateMatchModes, std::string> patternMatchModes = {
{cv::TemplateMatchModes::TM_CCOEFF_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient"},
{cv::TemplateMatchModes::TM_CCORR_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation"},
{cv::TemplateMatchModes::TM_SQDIFF_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference"},
};
const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
{tesseract::PageSegMode::PSM_SINGLE_COLUMN,
"AdvSceneSwitcher.condition.video.ocrMode.singleColumn"},
{tesseract::PageSegMode::PSM_SINGLE_BLOCK_VERT_TEXT,
"AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText"},
{tesseract::PageSegMode::PSM_SINGLE_BLOCK,
"AdvSceneSwitcher.condition.video.ocrMode.singleBlock"},
{tesseract::PageSegMode::PSM_SINGLE_LINE,
"AdvSceneSwitcher.condition.video.ocrMode.singleLine"},
{tesseract::PageSegMode::PSM_SINGLE_WORD,
"AdvSceneSwitcher.condition.video.ocrMode.singleWord"},
{tesseract::PageSegMode::PSM_CIRCLE_WORD,
"AdvSceneSwitcher.condition.video.ocrMode.circleWord"},
{tesseract::PageSegMode::PSM_SINGLE_CHAR,
"AdvSceneSwitcher.condition.video.ocrMode.singleChar"},
{tesseract::PageSegMode::PSM_SPARSE_TEXT,
"AdvSceneSwitcher.condition.video.ocrMode.sparseText"},
{tesseract::PageSegMode::PSM_SPARSE_TEXT_OSD,
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
};
static bool requiresFileInput(VideoCondition t)
{
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER ||
t == VideoCondition::PATTERN;
}
bool MacroConditionVideo::CheckShouldBeSkipped()
{
if (_condition != VideoCondition::PATTERN &&
_condition != VideoCondition::OBJECT &&
_condition != VideoCondition::HAS_CHANGED &&
_condition != VideoCondition::HAS_NOT_CHANGED) {
return false;
}
if (_throttleEnabled) {
if (_runCount <= _throttleCount) {
_runCount++;
return true;
} else {
_runCount = 0;
}
}
return false;
}
MacroConditionVideo::MacroConditionVideo(Macro *m)
: QObject(),
MacroCondition(m, true)
{
}
bool MacroConditionVideo::CheckCondition()
{
if (!_video.ValidSelection()) {
return false;
}
bool match = false;
if (CheckShouldBeSkipped()) {
return _lastMatchResult;
}
if (!FileInputIsUpToDate()) {
LoadImageFromFile();
}
if (_blockUntilScreenshotDone) {
GetScreenshot(true);
}
if (_screenshotData.IsDone()) {
match = Compare();
_lastMatchResult = match;
if (!requiresFileInput(_condition)) {
_matchImage = std::move(_screenshotData.GetImage());
}
_getNextScreenshot = true;
} else {
match = _lastMatchResult;
}
if (!_blockUntilScreenshotDone && _getNextScreenshot) {
GetScreenshot();
}
return match;
}
bool MacroConditionVideo::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_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, "blockUntilScreenshotDone",
_blockUntilScreenshotDone);
_brightnessThreshold.Save(obj, "brightnessThreshold");
_patternMatchParameters.Save(obj);
_objMatchParameters.Save(obj);
_ocrParameters.Save(obj);
_colorParameters.Save(obj);
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
obs_data_set_int(obj, "throttleCount", _throttleCount);
_areaParameters.Save(obj);
return true;
}
bool MacroConditionVideo::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_video.Load(obj);
SetCondition(static_cast<VideoCondition>(
obs_data_get_int(obj, "condition")));
_file = obs_data_get_string(obj, "filePath");
_blockUntilScreenshotDone =
obs_data_get_bool(obj, "blockUntilScreenshotDone");
// TODO: Remove this fallback in a future version
if (obs_data_has_user_value(obj, "brightness")) {
_brightnessThreshold = obs_data_get_double(obj, "brightness");
} else {
_brightnessThreshold.Load(obj, "brightnessThreshold");
}
_patternMatchParameters.Load(obj);
_objMatchParameters.Load(obj);
_ocrParameters.Load(obj);
_colorParameters.Load(obj);
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
_throttleCount = obs_data_get_int(obj, "throttleCount");
_areaParameters.Load(obj);
if (requiresFileInput(_condition)) {
(void)LoadImageFromFile();
}
return true;
}
std::string MacroConditionVideo::GetShortDesc() const
{
return _video.ToString();
}
void MacroConditionVideo::GetScreenshot(bool blocking)
{
auto source = obs_weak_source_get_source(_video.GetVideo());
_screenshotData.~Screenshot();
QRect screenshotArea;
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
screenshotArea.setRect(_areaParameters.area.x,
_areaParameters.area.y,
_areaParameters.area.width,
_areaParameters.area.height);
}
const int timeout = GetIntervalValue() < 300 ? 300 : GetIntervalValue();
new (&_screenshotData)
Screenshot(source, screenshotArea, blocking, timeout);
obs_source_release(source);
_getNextScreenshot = false;
}
bool MacroConditionVideo::LoadImageFromFile()
{
const QFileInfo info(QString::fromStdString(_file));
_loadedFileLastModified = info.lastModified();
_loadedFile = _file;
if (!_matchImage.load(info.absoluteFilePath())) {
blog(LOG_WARNING, "Cannot load image data from file '%s'",
_file.c_str());
(&_matchImage)->~QImage();
new (&_matchImage) QImage();
_patternImageData = {};
return false;
}
_matchImage =
_matchImage.convertToFormat(QImage::Format::Format_RGBA8888);
_patternMatchParameters.image = _matchImage;
_patternImageData = CreatePatternData(_matchImage);
emit InputFileChanged();
return true;
}
void MacroConditionVideo::SetPageSegMode(tesseract::PageSegMode mode)
{
_ocrParameters.SetPageMode(mode);
}
bool MacroConditionVideo::SetLanguageCode(const std::string &language)
{
return _ocrParameters.SetLanguageCode(language);
}
bool MacroConditionVideo::SetTesseractBaseDir(const std::string &dir)
{
return _ocrParameters.SetTesseractBasePath(dir);
}
void MacroConditionVideo::SetCondition(VideoCondition condition)
{
_condition = condition;
SetupTempVars();
}
bool MacroConditionVideo::ScreenshotContainsPattern()
{
cv::Mat result;
MatchPattern(_screenshotData.GetImage(), _patternImageData,
_patternMatchParameters.threshold, result, nullptr,
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
if (result.total() == 0) {
SetTempVarValue("patternCount", "0");
return false;
}
const auto count = countNonZero(result);
SetTempVarValue("patternCount", std::to_string(count));
return count > 0;
}
bool MacroConditionVideo::FileInputIsUpToDate() const
{
if (!requiresFileInput(_condition)) {
return true;
}
const QFileInfo info(QString::fromStdString(_file));
return (_loadedFileLastModified == info.lastModified()) &&
(_file == _loadedFile);
}
bool MacroConditionVideo::OutputChanged()
{
if (!_patternMatchParameters.useForChangedCheck) {
return _screenshotData.GetImage() != _matchImage;
}
cv::Mat result;
_patternImageData = CreatePatternData(_matchImage);
MatchPattern(_screenshotData.GetImage(), _patternImageData,
_patternMatchParameters.threshold, result, nullptr,
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
if (result.total() == 0) {
return false;
}
return countNonZero(result) == 0;
}
bool MacroConditionVideo::ScreenshotContainsObject()
{
auto model = _objMatchParameters.GetModel();
if (!model) {
return false;
}
auto objects = MatchObject(_screenshotData.GetImage(), *model,
_objMatchParameters.scaleFactor,
_objMatchParameters.minNeighbors,
_objMatchParameters.minSize.CV(),
_objMatchParameters.maxSize.CV());
const auto count = objects.size();
SetTempVarValue("objectCount", std::to_string(count));
return count > 0;
}
bool MacroConditionVideo::CheckBrightnessThreshold()
{
_currentBrightness =
GetAvgBrightness(_screenshotData.GetImage()) / 255.;
SetTempVarValue("brightness", std::to_string(_currentBrightness));
return _currentBrightness > _brightnessThreshold;
}
bool MacroConditionVideo::CheckOCR()
{
if (!_ocrParameters.Initialized()) {
return false;
}
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.GetImage(),
_ocrParameters.color, _ocrParameters.colorThreshold);
if (!text) {
return false;
}
SetVariableValue(*text);
SetTempVarValue("text", *text);
if (!_ocrParameters.regex.Enabled()) {
return text == std::string(_ocrParameters.text);
}
return _ocrParameters.regex.Matches(*text, _ocrParameters.text);
}
bool MacroConditionVideo::CheckColor()
{
const bool ret = ContainsPixelsInColorRange(
_screenshotData.GetImage(), _colorParameters.color,
_colorParameters.colorThreshold,
_colorParameters.matchThreshold);
// Way too slow for now
//SetTempVarValue("dominantColor", GetDominantColor(_screenshotData.image, 3)
// .name(QColor::HexArgb)
// .toStdString());
SetTempVarValue("color", GetAverageColor(_screenshotData.GetImage())
.name(QColor::HexArgb)
.toStdString());
return ret;
}
bool MacroConditionVideo::Compare()
{
if (_condition != VideoCondition::OCR) {
SetVariableValue("");
}
switch (_condition) {
case VideoCondition::MATCH:
return _screenshotData.GetImage() == _matchImage;
case VideoCondition::DIFFER:
return _screenshotData.GetImage() != _matchImage;
case VideoCondition::HAS_CHANGED:
return OutputChanged();
case VideoCondition::HAS_NOT_CHANGED:
return !OutputChanged();
case VideoCondition::NO_IMAGE:
return _screenshotData.GetImage().isNull();
case VideoCondition::PATTERN:
return ScreenshotContainsPattern();
case VideoCondition::OBJECT:
return ScreenshotContainsObject();
case VideoCondition::BRIGHTNESS:
return CheckBrightnessThreshold();
case VideoCondition::OCR:
return CheckOCR();
case VideoCondition::COLOR:
return CheckColor();
default:
break;
}
return false;
}
void MacroConditionVideo::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_condition) {
case VideoCondition::PATTERN:
AddTempvar(
"patternCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount.description"));
break;
case VideoCondition::OBJECT:
AddTempvar(
"objectCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount.description"));
break;
case VideoCondition::BRIGHTNESS:
AddTempvar(
"brightness",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness.description"));
break;
case VideoCondition::OCR:
AddTempvar(
"text",
obs_module_text("AdvSceneSwitcher.tempVar.video.text"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.text.description"));
break;
case VideoCondition::COLOR:
AddTempvar(
"color",
obs_module_text("AdvSceneSwitcher.tempVar.video.color"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.color.description"));
break;
case VideoCondition::MATCH:
case VideoCondition::DIFFER:
case VideoCondition::HAS_NOT_CHANGED:
case VideoCondition::HAS_CHANGED:
case VideoCondition::NO_IMAGE:
default:
break;
}
}
static inline void populateVideoInputSelection(QComboBox *list)
{
for (const auto &[_, name] : videoInputTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
static inline void populateConditionSelection(QComboBox *list)
{
for (auto &[value, name] : conditionTypes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static inline void populatePageSegModeSelection(QComboBox *list)
{
for (const auto &[mode, name] : pageSegModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(mode));
}
}
static inline void populatePatternMatchModeSelection(QComboBox *list)
{
for (const auto &[mode, name] : patternMatchModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(mode));
}
}
BrightnessEdit::BrightnessEdit(QWidget *parent,
const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent),
_threshold(new SliderSpinBox(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
_current(new QLabel),
_entryData(data)
{
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_threshold);
layout->addWidget(_current);
setLayout(layout);
QWidget::connect(
_threshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(BrightnessThresholdChanged(
const NumberVariable<double> &)));
QWidget::connect(&_timer, &QTimer::timeout, this,
&BrightnessEdit::UpdateCurrentBrightness);
_timer.start(1000);
_threshold->SetDoubleValue(_entryData->_brightnessThreshold);
_loading = false;
}
void BrightnessEdit::UpdateCurrentBrightness()
{
QString text = obs_module_text(
"AdvSceneSwitcher.condition.video.currentBrightness");
_current->setText(text.arg(_entryData->GetCurrentBrightness()));
}
void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_brightnessThreshold = value;
}
static void openFileInEditor(const std::string &filepath)
{
const auto path = QString::fromStdString(filepath);
const QFileInfo fileInfo(path);
if (!fileInfo.exists()) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed"));
return;
}
file.close();
}
QUrl fileUrl = QUrl::fromLocalFile(path);
if (!QDesktopServices::openUrl(fileUrl)) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed"));
}
}
OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent),
_matchText(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(this)),
_textColor(new QLabel),
_selectColor(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectColor"))),
_colorThreshold(new SliderSpinBox(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
true)),
_pageSegMode(new QComboBox()),
_tesseractBaseDir(new FileSelection(FileSelection::Type::FOLDER)),
_languageCode(new VariableLineEdit(this)),
_useConfig(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrUseConfigFile"))),
_configFile(new FileSelection(FileSelection::Type::WRITE, this)),
_openConfigFile(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrOpenConfigFile"))),
_reloadConfig(new QPushButton()),
_configLayout(new QHBoxLayout()),
_previewDialog(previewDialog),
_entryData(data)
{
populatePageSegModeSelection(_pageSegMode);
_reloadConfig->setMaximumWidth(22);
SetButtonIcon(_reloadConfig, GetThemeTypeName() == "Light"
? ":res/images/refresh.svg"
: "theme:Dark/refresh.svg");
_reloadConfig->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrConfigReload"));
QWidget::connect(_selectColor, SIGNAL(clicked()), this,
SLOT(SelectColorClicked()));
QWidget::connect(
_colorThreshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged()));
QWidget::connect(_regex,
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
SLOT(RegexChanged(const RegexConfig &)));
QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this,
SLOT(PageSegModeChanged(int)));
QWidget::connect(_tesseractBaseDir,
SIGNAL(PathChanged(const QString &)), this,
SLOT(TesseractBaseDirChanged(const QString &)));
QWidget::connect(_languageCode, SIGNAL(editingFinished()), this,
SLOT(LanguageChanged()));
QWidget::connect(_useConfig, SIGNAL(stateChanged(int)), this,
SLOT(UseConfigChanged(int)));
QWidget::connect(_configFile, SIGNAL(PathChanged(const QString &)),
this, SLOT(ConfigFileChanged(const QString &)));
QWidget::connect(_openConfigFile, &QPushButton::clicked, [this](bool) {
openFileInEditor(
_entryData->_ocrParameters.GetCustomConfigFile());
});
QWidget::connect(_reloadConfig, &QPushButton::clicked, [this](bool) {
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.EnableCustomConfig(true);
_previewDialog->OCRParametersChanged(
_entryData->_ocrParameters);
});
auto configFileHint = new QLabel();
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
configFileHint->setPixmap(pixmap);
configFileHint->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrConfigHint"));
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{textColor}}", _textColor},
{"{{selectColor}}", _selectColor},
{"{{textType}}", _pageSegMode},
{"{{tesseractBaseDir}}", _tesseractBaseDir},
{"{{languageCode}}", _languageCode},
{"{{configFile}}", _configFile},
{"{{openConfigFile}}", _openConfigFile},
{"{{reloadConfig}}", _reloadConfig},
{"{{configFileHint}}", configFileHint},
};
auto layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
auto textLayout = new QHBoxLayout();
textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(_matchText);
textLayout->addWidget(_regex);
layout->addLayout(textLayout);
auto pageModeSegLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrTextType"),
pageModeSegLayout, widgetPlaceholders);
layout->addLayout(pageModeSegLayout);
auto baseDirLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrBaseDir"),
baseDirLayout, widgetPlaceholders, false);
layout->addLayout(baseDirLayout);
auto languageLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrLanguage"),
languageLayout, widgetPlaceholders);
layout->addLayout(languageLayout);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrConfig"),
_configLayout, widgetPlaceholders, false);
layout->addWidget(_useConfig);
layout->addLayout(_configLayout);
auto colorPickLayout = new QHBoxLayout();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.ocrColorPick"),
colorPickLayout, widgetPlaceholders);
layout->addLayout(colorPickLayout);
layout->addWidget(_colorThreshold);
setLayout(layout);
_matchText->setPlainText(_entryData->_ocrParameters.text);
_regex->SetRegexConfig(_entryData->_ocrParameters.regex);
SetupColorLabel(_entryData->_ocrParameters.color);
_colorThreshold->SetDoubleValue(
_entryData->_ocrParameters.colorThreshold);
_pageSegMode->setCurrentIndex(_pageSegMode->findData(
static_cast<int>(_entryData->_ocrParameters.GetPageMode())));
_tesseractBaseDir->SetPath(
_entryData->_ocrParameters.GetTesseractBasePath());
_languageCode->setText(_entryData->_ocrParameters.GetLanguageCode());
_useConfig->setChecked(
_entryData->_ocrParameters.CustomConfigIsEnabled());
_configFile->SetPath(_entryData->_ocrParameters.GetCustomConfigFile());
SetLayoutVisible(_configLayout,
_entryData->_ocrParameters.CustomConfigIsEnabled());
_loading = false;
}
void OCREdit::SetupColorLabel(const QColor &color)
{
_textColor->setText(color.name());
_textColor->setPalette(QPalette(color));
_textColor->setAutoFillBackground(true);
}
void OCREdit::SelectColorClicked()
{
if (_loading || !_entryData) {
return;
}
const QColor color = QColorDialog::getColor(
_entryData->_ocrParameters.color, this,
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
QColorDialog::ColorDialogOption());
if (!color.isValid()) {
return;
}
SetupColorLabel(color);
auto lock = LockContext();
_entryData->_ocrParameters.color = color;
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::ColorThresholdChanged(const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.colorThreshold = value;
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::MatchTextChanged()
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.text =
_matchText->toPlainText().toUtf8().constData();
adjustSize();
updateGeometry();
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::RegexChanged(const RegexConfig &conf)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.regex = conf;
adjustSize();
updateGeometry();
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::PageSegModeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetPageSegMode(static_cast<tesseract::PageSegMode>(
_pageSegMode->itemData(idx).toInt()));
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::TesseractBaseDirChanged(const QString &path)
{
GUARD_LOADING_AND_LOCK();
if (!_entryData->SetTesseractBaseDir(path.toStdString())) {
const QString message(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
const QDir dataDir(path);
const QString fileName(_languageCode->text() + ".traineddata");
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
// Reset to previous value
const QSignalBlocker b(this);
_tesseractBaseDir->SetPath(
_entryData->_ocrParameters.GetTesseractBasePath());
return;
}
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::LanguageChanged()
{
GUARD_LOADING_AND_LOCK();
if (!_entryData->SetLanguageCode(_languageCode->text().toStdString())) {
const QString message(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
const QDir dataDir(QString::fromStdString(
_entryData->_ocrParameters.GetTesseractBasePath()));
const QString fileName(_languageCode->text() + ".traineddata");
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
// Reset to previous value
const QSignalBlocker b(this);
_languageCode->setText(
_entryData->_ocrParameters.GetLanguageCode());
return;
}
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::UseConfigChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.EnableCustomConfig(value);
SetLayoutVisible(_configLayout, value);
adjustSize();
updateGeometry();
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
void OCREdit::ConfigFileChanged(const QString &path)
{
GUARD_LOADING_AND_LOCK();
_entryData->_ocrParameters.SetCustomConfigFile(path.toStdString());
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
}
ObjectDetectEdit::ObjectDetectEdit(
QWidget *parent, PreviewDialog *previewDialog,
const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent),
_modelDataPath(new FileSelection()),
_objectScaleThreshold(new SliderSpinBox(
1.1, 5.,
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"))),
_minNeighbors(new QSpinBox()),
_minNeighborsDescription(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.video.minNeighborDescription"))),
_minSize(new SizeSelection(0, 1024)),
_maxSize(new SizeSelection(0, 4096)),
_previewDialog(previewDialog),
_entryData(data)
{
_minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors);
QWidget::connect(
_objectScaleThreshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(ObjectScaleThresholdChanged(
const NumberVariable<double> &)));
QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this,
SLOT(MinNeighborsChanged(int)));
QWidget::connect(_minSize, SIGNAL(SizeChanged(Size)), this,
SLOT(MinSizeChanged(Size)));
QWidget::connect(_maxSize, SIGNAL(SizeChanged(Size)), this,
SLOT(MaxSizeChanged(Size)));
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
this, SLOT(ModelPathChanged(const QString &)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{minNeighbors}}", _minNeighbors},
{"{{minSize}}", _minSize},
{"{{maxSize}}", _maxSize},
{"{{modelDataPath}}", _modelDataPath},
};
auto pathLayout = new QHBoxLayout;
pathLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.modelPath"),
pathLayout, widgetPlaceholders);
auto neighborsLayout = new QHBoxLayout;
neighborsLayout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.minNeighbor"),
neighborsLayout, widgetPlaceholders);
auto 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);
auto sizeLayout = new QHBoxLayout;
sizeLayout->setContentsMargins(0, 0, 0, 0);
sizeLayout->addLayout(sizeGrid);
sizeLayout->addStretch();
auto layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(pathLayout);
layout->addLayout(neighborsLayout);
layout->addLayout(sizeLayout);
setLayout(layout);
_modelDataPath->SetPath(_entryData->_objMatchParameters.GetModelPath());
_objectScaleThreshold->SetDoubleValue(
_entryData->_objMatchParameters.scaleFactor);
_minNeighbors->setValue(_entryData->_objMatchParameters.minNeighbors);
_minSize->SetSize(_entryData->_objMatchParameters.minSize);
_maxSize->SetSize(_entryData->_objMatchParameters.maxSize);
_loading = false;
}
void ObjectDetectEdit::ObjectScaleThresholdChanged(const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_objMatchParameters.scaleFactor = value;
_previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
}
void ObjectDetectEdit::MinNeighborsChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_objMatchParameters.minNeighbors = value;
_previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
}
void ObjectDetectEdit::MinSizeChanged(advss::Size value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_objMatchParameters.minSize = value;
_previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
}
void ObjectDetectEdit::MaxSizeChanged(advss::Size value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_objMatchParameters.maxSize = value;
_previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
}
void ObjectDetectEdit::ModelPathChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
bool dataLoaded = false;
{
auto lock = LockContext();
std::string path = text.toStdString();
dataLoaded = _entryData->_objMatchParameters.SetModelPath(path);
}
if (!dataLoaded) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.condition.video.modelLoadFail"));
}
_previewDialog->ObjDetectParametersChanged(
_entryData->_objMatchParameters);
}
ColorEdit::ColorEdit(QWidget *parent,
const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent),
_matchThreshold(new SliderSpinBox(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.colorMatchThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.colorMatchThresholdDescription"),
true)),
_colorThreshold(new SliderSpinBox(
0., 1.,
obs_module_text(
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
obs_module_text(
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
true)),
_color(new QLabel),
_selectColor(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectColor"))),
_entryData(data)
{
QWidget::connect(_selectColor, SIGNAL(clicked()), this,
SLOT(SelectColorClicked()));
QWidget::connect(
_matchThreshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(MatchThresholdChanged(const NumberVariable<double> &)));
QWidget::connect(
_colorThreshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{color}}", _color},
{"{{selectColor}}", _selectColor},
};
auto colorLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.video.layout.color"),
colorLayout, widgetPlaceholders);
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(colorLayout);
layout->addWidget(_colorThreshold);
layout->addWidget(_matchThreshold);
setLayout(layout);
_matchThreshold->SetDoubleValue(
_entryData->_colorParameters.matchThreshold);
_colorThreshold->SetDoubleValue(
_entryData->_colorParameters.colorThreshold);
SetupColorLabel(_entryData->_colorParameters.color);
_loading = false;
}
void ColorEdit::SetupColorLabel(const QColor &color)
{
_color->setText(color.name());
_color->setPalette(QPalette(color));
_color->setAutoFillBackground(true);
}
void ColorEdit::MatchThresholdChanged(const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_colorParameters.matchThreshold = value;
}
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_colorParameters.colorThreshold = value;
}
void ColorEdit::SelectColorClicked()
{
if (_loading || !_entryData) {
return;
}
const QColor color = QColorDialog::getColor(
_entryData->_colorParameters.color, this,
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
QColorDialog::ColorDialogOption());
if (!color.isValid()) {
return;
}
SetupColorLabel(color);
auto lock = LockContext();
_entryData->_colorParameters.color = color;
}
AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
const std::shared_ptr<MacroConditionVideo> &data)
: QWidget(parent),
_checkAreaEnable(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.layout.checkAreaEnable"))),
_checkArea(new AreaSelection(0, 99999)),
_selectArea(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea"))),
_previewDialog(previewDialog),
_entryData(data)
{
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
SLOT(CheckAreaEnableChanged(int)));
QWidget::connect(_checkArea, SIGNAL(AreaChanged(Area)), this,
SLOT(CheckAreaChanged(Area)));
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
SLOT(SelectAreaClicked()));
QWidget::connect(_previewDialog, SIGNAL(SelectionAreaChanged(QRect)),
this, SLOT(CheckAreaChanged(QRect)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{checkAreaEnable}}", _checkAreaEnable},
{"{{checkArea}}", _checkArea},
{"{{selectArea}}", _selectArea},
};
auto layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.checkArea"),
layout, widgetPlaceholders);
setLayout(layout);
_checkAreaEnable->setChecked(_entryData->_areaParameters.enable);
_checkArea->SetArea(_entryData->_areaParameters.area);
SetWidgetVisibility();
_loading = false;
}
void AreaEdit::SetWidgetVisibility()
{
_checkArea->setVisible(_entryData->_areaParameters.enable);
_selectArea->setVisible(_entryData->_areaParameters.enable);
adjustSize();
updateGeometry();
}
void AreaEdit::SelectAreaClicked()
{
_previewDialog->show();
_previewDialog->raise();
_previewDialog->activateWindow();
_previewDialog->SelectArea();
}
void AreaEdit::CheckAreaEnableChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_areaParameters.enable = value;
SetWidgetVisibility();
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
emit Resized();
}
void AreaEdit::CheckAreaChanged(Area value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_areaParameters.area = value;
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
}
void AreaEdit::CheckAreaChanged(QRect rect)
{
const QSignalBlocker b(_checkArea);
Area area{rect.topLeft().x(), rect.y(), rect.width(), rect.height()};
_checkArea->SetArea(area);
CheckAreaChanged(area);
}
static QStringList getVideoSourcesList()
{
auto sources = GetVideoSourceNames();
sources.sort();
return sources;
}
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent),
_videoInputTypes(new QComboBox()),
_scenes(new SceneSelectionWidget(this, true, false, true, true,
true)),
_sources(new SourceSelectionWidget(this, getVideoSourcesList, true)),
_condition(new QComboBox()),
_reduceLatency(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency"))),
_usePatternForChangedCheck(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
_imagePath(new FileSelection()),
_patternThreshold(new SliderSpinBox(
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"))),
_patternMatchModeLayout(new QHBoxLayout()),
_patternMatchMode(new QComboBox()),
_showMatch(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.showMatch"))),
_previewDialog(this),
_brightness(new BrightnessEdit(this, entryData)),
_ocr(new OCREdit(this, &_previewDialog, entryData)),
_objectDetect(new ObjectDetectEdit(this, &_previewDialog, entryData)),
_color(new ColorEdit(this, entryData)),
_area(new AreaEdit(this, &_previewDialog, entryData)),
_throttleControlLayout(new QHBoxLayout),
_throttleEnable(new QCheckBox()),
_throttleCount(new QSpinBox())
{
_reduceLatency->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.reduceLatency.tooltip"));
_imagePath->Button()->disconnect();
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternMatchMode->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode.tip"));
populatePatternMatchModeSelection(_patternMatchMode);
_throttleCount->setMinimum(1 * GetIntervalValue());
_throttleCount->setMaximum(10 * GetIntervalValue());
_throttleCount->setSingleStep(GetIntervalValue());
_brightness->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_ocr->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_objectDetect->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_color->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
_area->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
QWidget::connect(_videoInputTypes, SIGNAL(currentIndexChanged(int)),
this, SLOT(VideoInputTypeChanged(int)));
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_reduceLatency, SIGNAL(stateChanged(int)), this,
SLOT(ReduceLatencyChanged(int)));
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
SLOT(ImagePathChanged(const QString &)));
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
SLOT(ImageBrowseButtonClicked()));
QWidget::connect(_usePatternForChangedCheck, SIGNAL(stateChanged(int)),
this, SLOT(UsePatternForChangedCheckChanged(int)));
QWidget::connect(
_patternThreshold,
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
this,
SLOT(PatternThresholdChanged(const NumberVariable<double> &)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_patternMatchMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(PatternMatchModeChanged(int)));
QWidget::connect(_throttleEnable, SIGNAL(stateChanged(int)), this,
SLOT(ThrottleEnableChanged(int)));
QWidget::connect(_throttleCount, SIGNAL(valueChanged(int)), this,
SLOT(ThrottleCountChanged(int)));
QWidget::connect(_showMatch, SIGNAL(clicked()), this,
SLOT(ShowMatchClicked()));
QWidget::connect(this,
SIGNAL(VideoSelectionChanged(const VideoInput &)),
&_previewDialog,
SLOT(VideoSelectionChanged(const VideoInput &)));
QWidget::connect(_area, SIGNAL(Resized()), this, SLOT(Resize()));
QWidget::connect(entryData.get(),
&MacroConditionVideo::InputFileChanged, this,
[this]() {
UpdatePreviewTooltip();
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
});
populateVideoInputSelection(_videoInputTypes);
populateConditionSelection(_condition);
_patternMatchModeLayout->setContentsMargins(0, 0, 0, 0);
_throttleControlLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *entryLine1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoInputTypes}}", _videoInputTypes},
{"{{sources}}", _sources},
{"{{scenes}}", _scenes},
{"{{condition}}", _condition},
{"{{reduceLatency}}", _reduceLatency},
{"{{imagePath}}", _imagePath},
{"{{throttleEnable}}", _throttleEnable},
{"{{throttleCount}}", _throttleCount},
{"{{patternMatchingModes}}", _patternMatchMode},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.condition.video.layout"),
entryLine1Layout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode"),
_patternMatchModeLayout, widgetPlaceholders);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.layout.throttle"),
_throttleControlLayout, widgetPlaceholders);
QHBoxLayout *showMatchLayout = new QHBoxLayout;
showMatchLayout->addWidget(_showMatch);
showMatchLayout->addStretch();
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLine1Layout);
mainLayout->addWidget(_usePatternForChangedCheck);
mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addLayout(_patternMatchModeLayout);
mainLayout->addWidget(_brightness);
mainLayout->addWidget(_ocr);
mainLayout->addWidget(_objectDetect);
mainLayout->addWidget(_color);
mainLayout->addLayout(_throttleControlLayout);
mainLayout->addWidget(_area);
mainLayout->addWidget(_reduceLatency);
mainLayout->addLayout(showMatchLayout);
setLayout(mainLayout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionVideoEdit::UpdatePreviewTooltip()
{
if (!_entryData) {
return;
}
if (!requiresFileInput(_entryData->GetCondition())) {
this->setToolTip("");
return;
}
QImage preview = _entryData->GetMatchImage().scaled(
{300, 300}, Qt::KeepAspectRatio);
QByteArray data;
QBuffer buffer(&data);
if (!preview.save(&buffer, "PNG")) {
return;
}
QString html =
QString("<html><img src='data:image/png;base64, %0'/></html>")
.arg(QString(data.toBase64()));
this->setToolTip(html);
}
void MacroConditionVideoEdit::SourceChanged(const SourceSelection &source)
{
GUARD_LOADING_AND_LOCK();
_entryData->_video.source = source;
HandleVideoInputUpdate();
}
void MacroConditionVideoEdit::SceneChanged(const SceneSelection &scene)
{
GUARD_LOADING_AND_LOCK();
_entryData->_video.scene = scene;
HandleVideoInputUpdate();
}
void MacroConditionVideoEdit::VideoInputTypeChanged(int type)
{
GUARD_LOADING_AND_LOCK();
_entryData->_video.type = static_cast<VideoInput::Type>(type);
HandleVideoInputUpdate();
SetWidgetVisibility();
}
void MacroConditionVideoEdit::HandleVideoInputUpdate()
{
_entryData->ResetLastMatch();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
emit VideoSelectionChanged(_entryData->_video);
}
void MacroConditionVideoEdit::ConditionChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->SetCondition(
static_cast<VideoCondition>(_condition->itemData(idx).toInt()));
_entryData->ResetLastMatch();
SetWidgetVisibility();
// Reload image data to avoid incorrect matches.
//
// Condition type HAS_NOT_CHANGED will use matchImage to store previous
// frame of video source, which will differ from the image stored at
// specified file location.
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
SetupPreviewDialogParams();
}
void MacroConditionVideoEdit::ImagePathChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
_entryData->_file = text.toUtf8().constData();
_entryData->ResetLastMatch();
if (_entryData->LoadImageFromFile()) {
UpdatePreviewTooltip();
}
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::ImageBrowseButtonClicked()
{
if (_loading || !_entryData) {
return;
}
QString path;
bool useExistingFile = false;
// Ask whether to create screenshot or to select existing file
if (_entryData->_video.ValidSelection()) {
QMessageBox msgBox(
QMessageBox::Question,
obs_module_text("AdvSceneSwitcher.windowTitle"),
obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction"),
QMessageBox::Yes | QMessageBox::No |
QMessageBox::Cancel);
auto yes = msgBox.button(QMessageBox::StandardButton::Yes);
yes->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.file"));
auto no = msgBox.button(QMessageBox::StandardButton::No);
no->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.askFileAction.screenshot"));
msgBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint |
Qt::CustomizeWindowHint);
const auto result = msgBox.exec();
if (result == QMessageBox::Cancel) {
return;
}
useExistingFile = result == QMessageBox::Yes;
}
if (useExistingFile) {
path = QFileDialog::getOpenFileName(
this, "",
FileSelection::ValidPathOrDesktop(
QString::fromStdString(_entryData->_file)));
if (path.isEmpty()) {
return;
}
} else {
path = QFileDialog::getSaveFileName(
this, "",
FileSelection::ValidPathOrDesktop(
QString::fromStdString(_entryData->_file)),
"*.png");
if (path.isEmpty()) {
return;
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return;
}
auto screenshot = ScreenshotDialog::AskForScreenshot(
_entryData->_video, _entryData->_areaParameters);
if (!screenshot) {
return;
}
screenshot->save(path);
}
_imagePath->SetPath(path);
ImagePathChanged(path);
}
void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_patternMatchParameters.useForChangedCheck = value;
SetWidgetVisibility();
}
void MacroConditionVideoEdit::PatternThresholdChanged(
const DoubleVariable &value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_patternMatchParameters.threshold = value;
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::ReduceLatencyChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_blockUntilScreenshotDone = value;
}
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_patternMatchParameters.useAlphaAsMask = value;
_entryData->LoadImageFromFile();
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::PatternMatchModeChanged(int idx)
{
GUARD_LOADING_AND_LOCK();
_entryData->_patternMatchParameters.matchMode =
static_cast<cv::TemplateMatchModes>(
_patternMatchMode->itemData(idx).toInt());
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::ThrottleEnableChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_throttleEnabled = value;
_throttleCount->setEnabled(value);
}
void MacroConditionVideoEdit::ThrottleCountChanged(int value)
{
GUARD_LOADING_AND_LOCK();
_entryData->_throttleCount = value / GetIntervalValue();
}
void MacroConditionVideoEdit::ShowMatchClicked()
{
_previewDialog.show();
_previewDialog.raise();
_previewDialog.activateWindow();
_previewDialog.ShowMatch();
}
static bool needsShowMatch(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT || cond == VideoCondition::OCR;
}
static bool needsThrottleControls(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT ||
cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
static bool needsThreshold(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
static bool patternControlIsOptional(VideoCondition cond)
{
return cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
static bool needsAreaControls(VideoCondition cond)
{
return cond != VideoCondition::NO_IMAGE;
}
void MacroConditionVideoEdit::SetWidgetVisibility()
{
_sources->setVisible(_entryData->_video.type ==
VideoInput::Type::SOURCE);
_scenes->setVisible(_entryData->_video.type == VideoInput::Type::SCENE);
_imagePath->setVisible(requiresFileInput(_entryData->GetCondition()));
_usePatternForChangedCheck->setVisible(
patternControlIsOptional(_entryData->GetCondition()));
_patternThreshold->setVisible(
needsThreshold(_entryData->GetCondition()));
_useAlphaAsMask->setVisible(_entryData->GetCondition() ==
VideoCondition::PATTERN);
SetLayoutVisible(_patternMatchModeLayout,
_entryData->GetCondition() == VideoCondition::PATTERN);
_brightness->setVisible(_entryData->GetCondition() ==
VideoCondition::BRIGHTNESS);
_showMatch->setVisible(needsShowMatch(_entryData->GetCondition()));
_ocr->setVisible(_entryData->GetCondition() == VideoCondition::OCR);
_objectDetect->setVisible(_entryData->GetCondition() ==
VideoCondition::OBJECT);
_color->setVisible(_entryData->GetCondition() == VideoCondition::COLOR);
SetLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->GetCondition()));
_area->setVisible(needsAreaControls(_entryData->GetCondition()));
if (_entryData->GetCondition() == VideoCondition::HAS_CHANGED ||
_entryData->GetCondition() == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_patternMatchParameters.useForChangedCheck);
SetLayoutVisible(
_patternMatchModeLayout,
_entryData->_patternMatchParameters.useForChangedCheck);
}
// TODO:
// Remove "reduce matching latency" and "reduce cpu load" options as they
// have become superfluous with short circuit evaluation
if (!_entryData->_throttleEnabled) {
SetLayoutVisible(_throttleControlLayout, false);
}
if (_entryData->_blockUntilScreenshotDone) {
_reduceLatency->hide();
}
Resize();
}
void MacroConditionVideoEdit::Resize()
{
adjustSize();
updateGeometry();
}
void MacroConditionVideoEdit::SetupPreviewDialogParams()
{
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);
_previewDialog.ObjDetectParametersChanged(
_entryData->_objMatchParameters);
_previewDialog.OCRParametersChanged(_entryData->_ocrParameters);
_previewDialog.VideoSelectionChanged(_entryData->_video);
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
_previewDialog.ConditionChanged(
static_cast<int>(_entryData->GetCondition()));
}
void MacroConditionVideoEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_videoInputTypes->setCurrentIndex(
static_cast<int>(_entryData->_video.type));
_scenes->SetScene(_entryData->_video.scene);
_sources->SetSource(_entryData->_video.source);
_condition->setCurrentIndex(_condition->findData(
static_cast<int>(_entryData->GetCondition())));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
_entryData->_patternMatchParameters.useForChangedCheck);
_patternThreshold->SetDoubleValue(
_entryData->_patternMatchParameters.threshold);
_useAlphaAsMask->setChecked(
_entryData->_patternMatchParameters.useAlphaAsMask);
_patternMatchMode->setCurrentIndex(_patternMatchMode->findData(
_entryData->_patternMatchParameters.matchMode));
_throttleEnable->setChecked(_entryData->_throttleEnabled);
_throttleCount->setValue(_entryData->_throttleCount *
GetIntervalValue());
UpdatePreviewTooltip();
SetupPreviewDialogParams();
SetWidgetVisibility();
}
} // namespace advss