mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
The "throttle" and "reduce matching latency" optiohns were introduced before "short circuit evaluation" was available and are now outdated. Both don't behave as expected with this option enabled. The throttling effect can be better achieved with an additional "Timer" condition.
1628 lines
47 KiB
C++
1628 lines
47 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 <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"},
|
|
};
|
|
|
|
cv::CascadeClassifier initObjectCascade(std::string &path)
|
|
{
|
|
cv::CascadeClassifier cascade;
|
|
try {
|
|
cascade.load(path);
|
|
} catch (...) {
|
|
blog(LOG_WARNING, "failed to load model data \"%s\"",
|
|
path.c_str());
|
|
}
|
|
return cascade;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
if (_condition == VideoCondition::OBJECT) {
|
|
LoadModelData(_objMatchParameters.modelPath);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool MacroConditionVideo::LoadModelData(std::string &path)
|
|
{
|
|
_objMatchParameters.modelPath = path;
|
|
_objMatchParameters.cascade = initObjectCascade(path);
|
|
return !_objMatchParameters.cascade.empty();
|
|
}
|
|
|
|
std::string MacroConditionVideo::GetModelDataPath() const
|
|
{
|
|
return _objMatchParameters.modelPath;
|
|
}
|
|
|
|
void MacroConditionVideo::SetPageSegMode(tesseract::PageSegMode mode)
|
|
{
|
|
_ocrParameters.SetPageMode(mode);
|
|
}
|
|
|
|
bool MacroConditionVideo::SetLanguage(const std::string &language)
|
|
{
|
|
return _ocrParameters.SetLanguageCode(language);
|
|
}
|
|
|
|
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 objects = MatchObject(_screenshotData.GetImage(),
|
|
_objMatchParameters.cascade,
|
|
_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);
|
|
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),
|
|
_data(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(_data->_brightnessThreshold);
|
|
_loading = false;
|
|
}
|
|
|
|
void BrightnessEdit::UpdateCurrentBrightness()
|
|
{
|
|
QString text = obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.currentBrightness");
|
|
_current->setText(text.arg(_data->GetCurrentBrightness()));
|
|
}
|
|
|
|
void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_brightnessThreshold = value;
|
|
}
|
|
|
|
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()),
|
|
_languageCode(new VariableLineEdit(this)),
|
|
_previewDialog(previewDialog),
|
|
_data(data)
|
|
{
|
|
populatePageSegModeSelection(_pageSegMode);
|
|
|
|
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(_languageCode, SIGNAL(editingFinished()), this,
|
|
SLOT(LanguageChanged()));
|
|
|
|
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
|
{"{{textColor}}", _textColor},
|
|
{"{{selectColor}}", _selectColor},
|
|
{"{{textType}}", _pageSegMode},
|
|
{"{{languageCode}}", _languageCode},
|
|
};
|
|
|
|
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.entry.orcTextType"),
|
|
pageModeSegLayout, widgetPlaceholders);
|
|
layout->addLayout(pageModeSegLayout);
|
|
auto languageLayout = new QHBoxLayout();
|
|
PlaceWidgets(
|
|
obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.entry.orcLanguage"),
|
|
languageLayout, widgetPlaceholders);
|
|
layout->addLayout(languageLayout);
|
|
auto colorPickLayout = new QHBoxLayout();
|
|
PlaceWidgets(
|
|
obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.entry.orcColorPick"),
|
|
colorPickLayout, widgetPlaceholders);
|
|
layout->addLayout(colorPickLayout);
|
|
layout->addWidget(_colorThreshold);
|
|
setLayout(layout);
|
|
|
|
_matchText->setPlainText(_data->_ocrParameters.text);
|
|
_regex->SetRegexConfig(_data->_ocrParameters.regex);
|
|
SetupColorLabel(_data->_ocrParameters.color);
|
|
_colorThreshold->SetDoubleValue(_data->_ocrParameters.colorThreshold);
|
|
_pageSegMode->setCurrentIndex(_pageSegMode->findData(
|
|
static_cast<int>(_data->_ocrParameters.GetPageMode())));
|
|
_languageCode->setText(_data->_ocrParameters.GetLanguageCode());
|
|
_loading = false;
|
|
}
|
|
|
|
void OCREdit::SetupColorLabel(const QColor &color)
|
|
{
|
|
_textColor->setText(color.name());
|
|
_textColor->setPalette(QPalette(color));
|
|
_textColor->setAutoFillBackground(true);
|
|
}
|
|
|
|
void OCREdit::SelectColorClicked()
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
const QColor color = QColorDialog::getColor(
|
|
_data->_ocrParameters.color, this,
|
|
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
|
|
QColorDialog::ColorDialogOption());
|
|
|
|
if (!color.isValid()) {
|
|
return;
|
|
}
|
|
|
|
SetupColorLabel(color);
|
|
auto lock = LockContext();
|
|
_data->_ocrParameters.color = color;
|
|
|
|
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
|
|
}
|
|
|
|
void OCREdit::ColorThresholdChanged(const DoubleVariable &value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_ocrParameters.colorThreshold = value;
|
|
|
|
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
|
|
}
|
|
|
|
void OCREdit::MatchTextChanged()
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_ocrParameters.text =
|
|
_matchText->toPlainText().toUtf8().constData();
|
|
|
|
adjustSize();
|
|
updateGeometry();
|
|
|
|
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
|
|
}
|
|
|
|
void OCREdit::RegexChanged(const RegexConfig &conf)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_ocrParameters.regex = conf;
|
|
adjustSize();
|
|
updateGeometry();
|
|
|
|
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
|
|
}
|
|
|
|
void OCREdit::PageSegModeChanged(int idx)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->SetPageSegMode(static_cast<tesseract::PageSegMode>(
|
|
_pageSegMode->itemData(idx).toInt()));
|
|
|
|
_previewDialog->OCRParametersChanged(_data->_ocrParameters);
|
|
}
|
|
|
|
void OCREdit::LanguageChanged()
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
if (!_data->SetLanguage(_languageCode->text().toStdString())) {
|
|
const QString message(obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
|
|
const QDir dataDir(
|
|
obs_get_module_data_path(obs_current_module()));
|
|
const QString fileName(_languageCode->text() + ".traineddata");
|
|
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
|
|
|
|
// Reset to previous value
|
|
const QSignalBlocker b(this);
|
|
_languageCode->setText(_data->_ocrParameters.GetLanguageCode());
|
|
return;
|
|
}
|
|
_previewDialog->OCRParametersChanged(_data->_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),
|
|
_data(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.entry.modelPath"),
|
|
pathLayout, widgetPlaceholders);
|
|
|
|
auto neighborsLayout = new QHBoxLayout;
|
|
neighborsLayout->setContentsMargins(0, 0, 0, 0);
|
|
PlaceWidgets(
|
|
obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.entry.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(_data->GetModelDataPath());
|
|
_objectScaleThreshold->SetDoubleValue(
|
|
_data->_objMatchParameters.scaleFactor);
|
|
_minNeighbors->setValue(_data->_objMatchParameters.minNeighbors);
|
|
_minSize->SetSize(_data->_objMatchParameters.minSize);
|
|
_maxSize->SetSize(_data->_objMatchParameters.maxSize);
|
|
_loading = false;
|
|
}
|
|
|
|
void ObjectDetectEdit::ObjectScaleThresholdChanged(const DoubleVariable &value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_objMatchParameters.scaleFactor = value;
|
|
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
|
|
}
|
|
|
|
void ObjectDetectEdit::MinNeighborsChanged(int value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_objMatchParameters.minNeighbors = value;
|
|
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
|
|
}
|
|
|
|
void ObjectDetectEdit::MinSizeChanged(advss::Size value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_objMatchParameters.minSize = value;
|
|
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
|
|
}
|
|
|
|
void ObjectDetectEdit::MaxSizeChanged(advss::Size value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_objMatchParameters.maxSize = value;
|
|
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
|
|
}
|
|
|
|
void ObjectDetectEdit::ModelPathChanged(const QString &text)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
bool dataLoaded = false;
|
|
{
|
|
auto lock = LockContext();
|
|
std::string path = text.toStdString();
|
|
dataLoaded = _data->LoadModelData(path);
|
|
}
|
|
if (!dataLoaded) {
|
|
DisplayMessage(obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.modelLoadFail"));
|
|
}
|
|
_previewDialog->ObjDetectParametersChanged(_data->_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"))),
|
|
_data(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.entry.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(_data->_colorParameters.matchThreshold);
|
|
_colorThreshold->SetDoubleValue(_data->_colorParameters.colorThreshold);
|
|
SetupColorLabel(_data->_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)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_colorParameters.matchThreshold = value;
|
|
}
|
|
|
|
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_colorParameters.colorThreshold = value;
|
|
}
|
|
|
|
void ColorEdit::SelectColorClicked()
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
const QColor color = QColorDialog::getColor(
|
|
_data->_colorParameters.color, this,
|
|
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
|
|
QColorDialog::ColorDialogOption());
|
|
|
|
if (!color.isValid()) {
|
|
return;
|
|
}
|
|
|
|
SetupColorLabel(color);
|
|
auto lock = LockContext();
|
|
_data->_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.entry.checkAreaEnable"))),
|
|
_checkArea(new AreaSelection(0, 99999)),
|
|
_selectArea(new QPushButton(obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.selectArea"))),
|
|
_previewDialog(previewDialog),
|
|
_data(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.entry.checkArea"),
|
|
layout, widgetPlaceholders);
|
|
setLayout(layout);
|
|
|
|
_checkAreaEnable->setChecked(_data->_areaParameters.enable);
|
|
_checkArea->SetArea(_data->_areaParameters.area);
|
|
SetWidgetVisibility();
|
|
_loading = false;
|
|
}
|
|
|
|
void AreaEdit::SetWidgetVisibility()
|
|
{
|
|
_checkArea->setVisible(_data->_areaParameters.enable);
|
|
_selectArea->setVisible(_data->_areaParameters.enable);
|
|
adjustSize();
|
|
updateGeometry();
|
|
}
|
|
|
|
void AreaEdit::SelectAreaClicked()
|
|
{
|
|
_previewDialog->show();
|
|
_previewDialog->raise();
|
|
_previewDialog->activateWindow();
|
|
_previewDialog->SelectArea();
|
|
}
|
|
|
|
void AreaEdit::CheckAreaEnableChanged(int value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_areaParameters.enable = value;
|
|
SetWidgetVisibility();
|
|
_previewDialog->AreaParametersChanged(_data->_areaParameters);
|
|
emit Resized();
|
|
}
|
|
|
|
void AreaEdit::CheckAreaChanged(Area value)
|
|
{
|
|
if (_loading || !_data) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_data->_areaParameters.area = value;
|
|
_previewDialog->AreaParametersChanged(_data->_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);
|
|
}
|
|
|
|
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, QStringList(), 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);
|
|
|
|
auto sources = GetVideoSourceNames();
|
|
sources.sort();
|
|
_sources->SetSourceNameList(sources);
|
|
|
|
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.entry"),
|
|
entryLine1Layout, widgetPlaceholders);
|
|
PlaceWidgets(
|
|
obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.patternMatchMode"),
|
|
_patternMatchModeLayout, widgetPlaceholders);
|
|
PlaceWidgets(obs_module_text(
|
|
"AdvSceneSwitcher.condition.video.entry.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)
|
|
{
|
|
if (_loading || !_entryData) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_entryData->_video.source = source;
|
|
HandleVideoInputUpdate();
|
|
}
|
|
|
|
void MacroConditionVideoEdit::SceneChanged(const SceneSelection &scene)
|
|
{
|
|
if (_loading || !_entryData) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_entryData->_video.scene = scene;
|
|
HandleVideoInputUpdate();
|
|
}
|
|
|
|
void MacroConditionVideoEdit::VideoInputTypeChanged(int type)
|
|
{
|
|
if (_loading || !_entryData) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_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)
|
|
{
|
|
if (_loading || !_entryData) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_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);
|
|
|
|
if (_entryData->GetCondition() == VideoCondition::OBJECT) {
|
|
auto path = _entryData->GetModelDataPath();
|
|
_entryData->_objMatchParameters.cascade =
|
|
initObjectCascade(path);
|
|
}
|
|
|
|
SetupPreviewDialogParams();
|
|
}
|
|
|
|
void MacroConditionVideoEdit::ImagePathChanged(const QString &text)
|
|
{
|
|
if (_loading || !_entryData) {
|
|
return;
|
|
}
|
|
|
|
auto lock = LockContext();
|
|
_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
|